All topics
Mobile · Learning hub

Swift notes for developers

Master Swift with a curated set of 5 developer notes — core concepts, patterns, and interview prep. Maintained by the DevRecall team.

Save this stack to your DevRecallMore Mobile notes
Swift

Language Basics

Swift: Language Basics Swift is Apple's modern, type-safe language for iOS, macOS, watchOS, and tvOS. It emphasizes safety, performance, and expressive syntax.

Swift: Language Basics

Swift is Apple's modern, type-safe language for iOS, macOS, watchOS, and tvOS. It emphasizes safety, performance, and expressive syntax. Swift uses ARC (Automatic Reference Counting) for memory management.

Variables, Constants & Types

// Constants (prefer let by default)
let name = "Alice"          // type inferred: String
let age: Int = 30
let pi: Double = 3.14159

// Variables
var score = 0
score += 10

// Type aliases
typealias UserID = UUID

// Optionals — a value or nil
var nickname: String? = nil
nickname = "ally"

// Unwrapping optionals
if let nick = nickname {
    print("Nickname: \(nick)")
}

// Optional chaining
let upper = nickname?.uppercased()   // String? — nil if nickname is nil

// Nil coalescing
let display = nickname ?? "Anonymous"

// Guard — early exit
func greet(_ name: String?) {
    guard let name = name else { return }
    print("Hello, \(name)!")
}

// Force unwrap (use sparingly — crashes on nil)
let forced = nickname!

Collections

// Array
var fruits = ["apple", "banana", "cherry"]
fruits.append("date")
fruits.insert("avocado", at: 0)
fruits.remove(at: 1)
fruits.count         // Int
fruits.first         // String? (optional)
fruits.contains("cherry")  // Bool
let sorted = fruits.sorted()
let upper = fruits.map { $0.uppercased() }
let long = fruits.filter { $0.count > 5 }

// Dictionary
var scores: [String: Int] = ["Alice": 95, "Bob": 82]
scores["Carol"] = 91
scores["Bob"]          // Int? — optional
scores["Bob", default: 0]  // Int — non-optional with default
scores.keys            // Dictionary.Keys
for (name, score) in scores { }

// Set
var tags: Set<String> = ["swift", "ios", "apple"]
tags.insert("xcode")
tags.contains("ios")   // true
let union = tags.union(["macos", "swift"])

Control Flow & Pattern Matching

// Switch — must be exhaustive, no fallthrough by default
switch score {
case 90...100:
    print("A")
case 80..<90:
    print("B")
case let n where n < 0:
    print("Invalid: \(n)")
default:
    print("C or below")
}

// For-in loops
for i in 1...5 { print(i) }
for i in stride(from: 0, to: 10, by: 2) { }  // 0, 2, 4, 6, 8

// Tuples
let coordinates = (x: 3.0, y: 4.0)
print(coordinates.x)   // 3.0
let (x, y) = coordinates

// Enums with associated values
enum Result<T> {
    case success(T)
    case failure(Error)
}

enum Direction {
    case north, south, east, west

    var opposite: Direction {
        switch self {
        case .north: return .south
        case .south: return .north
        case .east: return .west
        case .west: return .east
        }
    }
}

let dir = Direction.north
if case .north = dir { print("Going north") }

Functions & Closures

// Functions
func add(_ a: Int, to b: Int) -> Int {   // external: _, internal: a
    return a + b
}
add(3, to: 4)   // named parameter syntax

// Multiple return values
func minMax(_ array: [Int]) -> (min: Int, max: Int) {
    return (array.min()!, array.max()!)
}
let result = minMax([3, 1, 4, 1, 5])
result.min   // 1

// Closures
let multiply = { (a: Int, b: Int) -> Int in a * b }
multiply(3, 4)   // 12

// Trailing closure syntax
[1, 2, 3].forEach { print($0) }
let doubled = [1, 2, 3].map { $0 * 2 }

// @escaping — closure called after function returns
func fetchData(completion: @escaping (Data?) -> Void) {
    URLSession.shared.dataTask(with: url) { data, _, _ in
        completion(data)
    }.resume()
}

// Higher-order functions
let numbers = [5, 2, 8, 1, 9, 3]
numbers.sorted()                         // [1, 2, 3, 5, 8, 9]
numbers.sorted { $0 > $1 }              // [9, 8, 5, 3, 2, 1]
numbers.reduce(0, +)                     // 28
numbers.compactMap { $0 > 5 ? $0 : nil }  // [8, 9]
Swift

OOP, Protocols & Extensions

Swift: OOP, Protocols & Extensions Classes vs Structs Struct: value type — copied on assignment. Prefer structs for data models (String, Array, Int are all stru

Swift: OOP, Protocols & Extensions

Classes vs Structs

  • Struct: value type — copied on assignment. Prefer structs for data models (String, Array, Int are all structs)

  • Class: reference type — shared reference. Use for identity-based models, inheritance, Objective-C interop

  • Struct is default choice in Swift — safer, no retain cycles, better with SwiftUI

  • Mutating methods: structs need `mutating` keyword on methods that modify self

// Struct (value type)
struct Point {
    var x: Double
    var y: Double

    mutating func translate(dx: Double, dy: Double) {
        x += dx
        y += dy
    }

    func distanceTo(_ other: Point) -> Double {
        let dx = x - other.x
        let dy = y - other.y
        return (dx*dx + dy*dy).squareRoot()
    }
}

// Class (reference type)
class Person {
    let id: UUID
    var name: String
    private(set) var age: Int   // public get, private set

    init(name: String, age: Int) {
        self.id = UUID()
        self.name = name
        self.age = age
    }

    deinit {
        print("\(name) deallocated")
    }
}

class Employee: Person {
    var department: String

    init(name: String, age: Int, department: String) {
        self.department = department
        super.init(name: name, age: age)
    }

    override var description: String {
        "\(name) in \(department)"
    }
}

Protocols

// Protocol — like interface in other languages
protocol Drawable {
    var color: String { get }
    func draw() -> String
    func area() -> Double
}

protocol Identifiable {
    var id: UUID { get }
}

// Protocol composition
typealias DrawableIdentifiable = Drawable & Identifiable

// Protocol with default implementation (via extension)
protocol Greetable {
    var name: String { get }
    func greet() -> String
}

extension Greetable {
    func greet() -> String {
        "Hello, I am \(name)"
    }
}

// Conformance
struct Circle: Drawable {
    let radius: Double
    var color: String

    func draw() -> String { "Circle(r=\(radius))" }
    func area() -> Double { .pi * radius * radius }
}

// Protocol as type (existential)
let shapes: [any Drawable] = [Circle(radius: 5, color: "red")]

// Generic constraint
func printArea<T: Drawable>(_ shape: T) {
    print("Area: \(shape.area())")
}

Extensions

// Add methods to existing types
extension String {
    var isPalindrome: Bool {
        self == String(self.reversed())
    }

    func truncated(to length: Int, ellipsis: String = "...") -> String {
        guard self.count > length else { return self }
        return String(self.prefix(length)) + ellipsis
    }
}

"racecar".isPalindrome   // true
"Hello, World!".truncated(to: 5)  // "Hello..."

// Retroactive conformance
extension Int: Drawable {
    var color: String { "black" }
    func draw() -> String { "\(self)" }
    func area() -> Double { 0 }
}

// Computed properties on Collection
extension Collection {
    var isNotEmpty: Bool { !isEmpty }
}

// Constrained extensions
extension Array where Element: Comparable {
    func isSorted() -> Bool {
        zip(self, dropFirst()).allSatisfy { $0 <= $1 }
    }
}

Property Wrappers & Result Builders

// Property wrappers (power SwiftUI @State, @Published, etc.)
@propertyWrapper
struct Clamped<T: Comparable> {
    private var value: T
    let range: ClosedRange<T>

    var wrappedValue: T {
        get { value }
        set { value = min(max(newValue, range.lowerBound), range.upperBound) }
    }

    init(wrappedValue: T, _ range: ClosedRange<T>) {
        self.range = range
        self.value = min(max(wrappedValue, range.lowerBound), range.upperBound)
    }
}

struct Player {
    @Clamped(0...100) var health: Int = 100
}

var player = Player()
player.health = 150   // clamped to 100
player.health = -10   // clamped to 0

// Combine — reactive framework
import Combine

class ViewModel: ObservableObject {
    @Published var searchText = ""
    @Published var results: [String] = []
    private var cancellables = Set<AnyCancellable>()

    init() {
        $searchText
            .debounce(for: .milliseconds(300), scheduler: RunLoop.main)
            .removeDuplicates()
            .sink { [weak self] text in
                self?.search(text)
            }
            .store(in: &cancellables)
    }
}
Swift

SwiftUI Fundamentals

Swift: SwiftUI Fundamentals SwiftUI is Apple's declarative UI framework (iOS 13+). Views are value types (structs) — the framework diffs and redraws efficiently

Swift: SwiftUI Fundamentals

SwiftUI is Apple's declarative UI framework (iOS 13+). Views are value types (structs) — the framework diffs and redraws efficiently. State changes drive UI updates automatically.

Views & Layout

import SwiftUI

// Every SwiftUI view is a struct conforming to View
struct ContentView: View {
    var body: some View {
        VStack(alignment: .leading, spacing: 16) {
            Text("Hello, World!")
                .font(.title)
                .fontWeight(.bold)
                .foregroundColor(.primary)

            HStack {
                Image(systemName: "star.fill")
                    .foregroundColor(.yellow)
                Text("4.8 rating")
            }

            Spacer()

            Button("Get Started") {
                // action
            }
            .buttonStyle(.borderedProminent)
            .frame(maxWidth: .infinity)
        }
        .padding()
        .background(Color(.systemBackground))
    }
}

// List view
struct UserListView: View {
    let users: [User]

    var body: some View {
        List(users, id: \.id) { user in
            NavigationLink(destination: UserDetailView(user: user)) {
                Label(user.name, systemImage: "person.circle")
            }
        }
        .navigationTitle("Users")
    }
}

State & Data Flow

// @State — local view state
struct CounterView: View {
    @State private var count = 0

    var body: some View {
        VStack {
            Text("Count: \(count)")
            Button("+") { count += 1 }
        }
    }
}

// @Binding — pass state to child view
struct ToggleView: View {
    @Binding var isOn: Bool

    var body: some View {
        Toggle("Enable", isOn: $isOn)
    }
}

// @StateObject — own an ObservableObject
// @ObservedObject — receive an ObservableObject from parent
// @EnvironmentObject — inject from ancestor

class CartStore: ObservableObject {
    @Published var items: [CartItem] = []

    func add(_ item: CartItem) {
        items.append(item)
    }
}

struct ShopView: View {
    @StateObject private var cart = CartStore()

    var body: some View {
        NavigationStack {
            ProductListView()
                .environmentObject(cart)
        }
    }
}

struct ProductListView: View {
    @EnvironmentObject var cart: CartStore
    // ...
}

Navigation & Sheets

// NavigationStack (iOS 16+ — replaces NavigationView)
struct AppView: View {
    @State private var path = NavigationPath()

    var body: some View {
        NavigationStack(path: $path) {
            HomeView()
                .navigationDestination(for: User.self) { user in
                    UserDetailView(user: user)
                }
                .navigationDestination(for: Post.self) { post in
                    PostDetailView(post: post)
                }
        }
    }
}

// Sheet (modal)
struct MainView: View {
    @State private var showingSheet = false
    @State private var showingFullScreen = false

    var body: some View {
        Button("Show Sheet") { showingSheet = true }
            .sheet(isPresented: $showingSheet) {
                DetailSheet()
                    .presentationDetents([.medium, .large])  // iOS 16+
            }
            .fullScreenCover(isPresented: $showingFullScreen) {
                FullView()
            }
    }
}

// Alert
.alert("Delete item?", isPresented: $showingAlert) {
    Button("Delete", role: .destructive) { deleteItem() }
    Button("Cancel", role: .cancel) { }
}

Async Image, Task & ViewModifiers

// AsyncImage (built-in async image loading)
AsyncImage(url: URL(string: user.avatarURL)) { image in
    image
        .resizable()
        .aspectRatio(contentMode: .fill)
        .frame(width: 60, height: 60)
        .clipShape(Circle())
} placeholder: {
    ProgressView()
        .frame(width: 60, height: 60)
}

// Task — run async work tied to view lifecycle
struct PostsView: View {
    @State private var posts: [Post] = []
    @State private var isLoading = false

    var body: some View {
        List(posts, id: \.id) { Text($0.title) }
            .task {
                isLoading = true
                posts = await PostService.fetchAll()
                isLoading = false
            }
    }
}

// Custom ViewModifier
struct CardStyle: ViewModifier {
    func body(content: Content) -> some View {
        content
            .padding()
            .background(Color(.secondarySystemBackground))
            .cornerRadius(12)
            .shadow(radius: 2)
    }
}

extension View {
    func cardStyle() -> some View {
        modifier(CardStyle())
    }
}

// Usage
Text("Hello").cardStyle()
Swift

Concurrency: async/await & Actors

Swift: Concurrency — async/await & Actors Swift 5.5 introduced structured concurrency: async/await, actors, and Tasks. It replaces callback-based and Combine-ba

Swift: Concurrency — async/await & Actors

Swift 5.5 introduced structured concurrency: async/await, actors, and Tasks. It replaces callback-based and Combine-based async patterns with safer, more readable code.

async/await Basics

import Foundation

// Async function — can be called with await
func fetchUser(id: String) async throws -> User {
    let url = URL(string: "https://api.example.com/users/\(id)")!
    let (data, response) = try await URLSession.shared.data(from: url)

    guard let httpResponse = response as? HTTPURLResponse,
          httpResponse.statusCode == 200 else {
        throw APIError.badResponse
    }

    return try JSONDecoder().decode(User.self, from: data)
}

// Call an async function
func loadUser() async {
    do {
        let user = try await fetchUser(id: "123")
        print("Loaded: \(user.name)")
    } catch APIError.badResponse {
        print("Bad response")
    } catch {
        print("Error: \(error)")
    }
}

// Bridge from sync context
Task {
    await loadUser()
}

// Async sequence — like AsyncStream
for try await line in url.lines {
    print(line)
}

Structured Concurrency

// async let — parallel execution
async let user = fetchUser(id: "123")
async let posts = fetchPosts(userId: "123")
async let stats = fetchStats(userId: "123")

// Await all three simultaneously (not sequentially)
let (u, p, s) = try await (user, posts, stats)

// TaskGroup — dynamic parallelism
func fetchAllUsers(ids: [String]) async throws -> [User] {
    try await withThrowingTaskGroup(of: User.self) { group in
        for id in ids {
            group.addTask { try await fetchUser(id: id) }
        }

        var users: [User] = []
        for try await user in group {
            users.append(user)
        }
        return users
    }
}

// Task — unstructured concurrency (fire and forget or detached)
let task = Task {
    try await longRunningWork()
}

// Cancel a task
task.cancel()

// Check for cancellation
func work() async throws {
    for item in items {
        try Task.checkCancellation()  // throws CancellationError if cancelled
        await process(item)
    }
}

Actors

// Actor — reference type that protects mutable state
// Only one caller can access actor internals at a time
actor BankAccount {
    private var balance: Double = 0

    func deposit(_ amount: Double) {
        balance += amount
    }

    func withdraw(_ amount: Double) throws {
        guard balance >= amount else {
            throw BankError.insufficientFunds
        }
        balance -= amount
    }

    func getBalance() -> Double {
        balance
    }
}

let account = BankAccount()
await account.deposit(100)
let balance = await account.getBalance()  // must await to access actor

// @MainActor — runs on the main thread (for UI updates)
@MainActor
class ViewModel: ObservableObject {
    @Published var items: [Item] = []

    func load() async {
        let fetched = await fetchItems()
        items = fetched  // safe — on MainActor
    }
}

// Nonisolated — opt out of actor protection for stateless methods
actor DataProcessor {
    nonisolated func formatDate(_ date: Date) -> String {
        date.formatted()  // no actor isolation needed
    }
}

Sendable & Data Race Safety

// Sendable — safe to cross actor boundaries
// Value types (structs, enums) are implicitly Sendable
// Classes must be explicitly marked (or @unchecked)

struct Message: Sendable {  // value type — Sendable
    let id: UUID
    let text: String
}

// @Sendable closure
func process(items: [Int], handler: @Sendable (Int) -> Void) {
    Task {
        items.forEach { handler($0) }
    }
}

// AsyncStream — bridge callback-based APIs to async/await
func listenForNotifications() -> AsyncStream<Notification> {
    AsyncStream { continuation in
        let observer = NotificationCenter.default.addObserver(
            forName: .someNotification,
            object: nil,
            queue: nil
        ) { notification in
            continuation.yield(notification)
        }

        continuation.onTermination = { _ in
            NotificationCenter.default.removeObserver(observer)
        }
    }
}

// Consume
Task {
    for await notification in listenForNotifications() {
        handleNotification(notification)
    }
}
Swift

iOS Essentials: Networking, CoreData & UIKit

Swift: iOS Essentials URLSession & Networking import Foundation // Decodable model struct Post: Codable, Identifiable { let id: Int let title: String let body:

Swift: iOS Essentials

URLSession & Networking

import Foundation

// Decodable model
struct Post: Codable, Identifiable {
    let id: Int
    let title: String
    let body: String
    let userId: Int
}

// Generic API client
struct APIClient {
    let baseURL = URL(string: "https://api.example.com")!

    func fetch<T: Decodable>(_ path: String) async throws -> T {
        let url = baseURL.appendingPathComponent(path)
        var request = URLRequest(url: url)
        request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")

        let (data, response) = try await URLSession.shared.data(for: request)

        guard let http = response as? HTTPURLResponse,
              (200...299).contains(http.statusCode) else {
            throw URLError(.badServerResponse)
        }

        return try JSONDecoder().decode(T.self, from: data)
    }

    func post<T: Encodable, R: Decodable>(
        _ path: String,
        body: T
    ) async throws -> R {
        var request = URLRequest(url: baseURL.appendingPathComponent(path))
        request.httpMethod = "POST"
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        request.httpBody = try JSONEncoder().encode(body)

        let (data, _) = try await URLSession.shared.data(for: request)
        return try JSONDecoder().decode(R.self, from: data)
    }
}

CoreData

import CoreData

// PersistenceController (boilerplate for CoreData stack)
struct PersistenceController {
    static let shared = PersistenceController()

    let container: NSPersistentContainer

    init(inMemory: Bool = false) {
        container = NSPersistentContainer(name: "MyApp")
        if inMemory {
            container.persistentStoreDescriptions.first?.url = URL(fileURLWithPath: "/dev/null")
        }
        container.loadPersistentStores { _, error in
            if let error { fatalError("CoreData error: \(error)") }
        }
        container.viewContext.automaticallyMergesChangesFromParent = true
    }
}

// Fetch & save (in SwiftUI view)
struct NoteListView: View {
    @Environment(\.managedObjectContext) private var context
    @FetchRequest(
        sortDescriptors: [NSSortDescriptor(keyPath: \Note.createdAt, ascending: false)],
        animation: .default
    ) private var notes: FetchedResults<Note>

    var body: some View {
        List(notes) { note in
            Text(note.title ?? "")
        }
        .toolbar {
            Button("Add") { createNote() }
        }
    }

    func createNote() {
        let note = Note(context: context)
        note.id = UUID()
        note.title = "New Note"
        note.createdAt = Date()
        try? context.save()
    }
}

UIKit Essentials

import UIKit

// ViewController lifecycle
class MyViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        setupUI()
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
    }

    private func setupUI() {
        view.backgroundColor = .systemBackground
        let label = UILabel()
        label.text = "Hello"
        label.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(label)

        NSLayoutConstraint.activate([
            label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            label.centerYAnchor.constraint(equalTo: view.centerYAnchor),
        ])
    }
}

// TableView with diffable data source
class UsersViewController: UIViewController {
    enum Section { case main }

    var dataSource: UITableViewDiffableDataSource<Section, User>!
    let tableView = UITableView()

    func configureDataSource() {
        dataSource = UITableViewDiffableDataSource(tableView: tableView) { tableView, indexPath, user in
            let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
            cell.textLabel?.text = user.name
            return cell
        }
    }

    func applySnapshot(users: [User]) {
        var snapshot = NSDiffableDataSourceSnapshot<Section, User>()
        snapshot.appendSections([.main])
        snapshot.appendItems(users)
        dataSource.apply(snapshot, animatingDifferences: true)
    }
}

App Architecture & Best Practices

  • MVVM: View (SwiftUI/UIKit) → ViewModel (ObservableObject) → Model + Services. Most common SwiftUI pattern.

  • Dependency injection: pass services via init or environment — avoid singletons for testability

  • Combine vs async/await: prefer async/await for new code; Combine still useful for reactive chains (debounce, publishers)

  • Memory management: use [weak self] in closures to prevent retain cycles — especially in delegates and callbacks

  • UserDefaults: for small preferences. CoreData or SwiftData for structured persistent data.

  • SwiftData (iOS 17+): new macro-based ORM — @Model class replaces CoreData .xcdatamodel setup

  • App groups: share data between app and extensions (widgets, share extensions) via shared container

  • Instruments: use for profiling memory leaks (Allocations), CPU usage, and network activity

  • TestFlight: distribute betas; App Store Connect for release management

Keep your Swift knowledge sharp.

Save this stack to your personal DevRecall — add your own notes, track what you're learning, and share what you know with the community.

Get started — free forever