Swift Cheatsheet

Errors & Concurrency

Use this Swift reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.

Defining and throwing errors

enum ParseError: Error {
    case empty
    case badNumber(String)
}

func parse(_ text: String) throws -> Int {
    guard !text.isEmpty else { throw ParseError.empty }
    guard let value = Int(text) else { throw ParseError.badNumber(text) }
    return value
}

Typed throws (Swift 6) constrain the error type in the signature:

func parseStrict(_ text: String) throws(ParseError) -> Int {
    guard let v = Int(text) else { throw .badNumber(text) }
    return v
}

do / catch and the try variants

do {
    let value = try parse("42a")
    print(value)
} catch ParseError.empty {
    print("nothing to parse")
} catch ParseError.badNumber(let text) {
    print("not a number: \(text)")
} catch {
    print("unexpected: \(error)")        // implicit `error` binding
}

let maybe = try? parse("42")             // Int?, nil on any error
let forced = try! parse("42")            // crashes on error, known-good input only
FormResult
tryPropagates, requires do/catch or a throws function
try?Converts errors to nil
try!Crashes on error

defer

func process(_ path: String) throws {
    let handle = try openFile(path)
    defer { handle.close() }             // runs on every exit: return, throw, fall-through
    try handle.readAll()
}

Multiple defer blocks run in reverse order of declaration. Use them for cleanup that must happen no matter how the scope exits.

Result

func parseResult(_ text: String) -> Result<Int, ParseError> {
    guard let v = Int(text) else { return .failure(.badNumber(text)) }
    return .success(v)
}

switch parseResult("7") {
case .success(let v): print(v)
case .failure(let err): print(err)
}

let value = try? parseResult("7").get()   // bridge back into throws / optionals
let doubled = parseResult("7").map { $0 * 2 }

Result makes an outcome a storable, passable value. Useful for collecting outcomes and for callback APIs that predate async/await.

async / await

func fetchScore(id: Int) async throws -> Int {
    try await Task.sleep(for: .milliseconds(100))   // stand-in for real async work
    return id * 10
}

func show() async {                      // call site: try + await
    do {
        let score = try await fetchScore(id: 7)
        print(score)
    } catch {
        print("failed: \(error)")
    }
}

Task {                                   // bridge from synchronous code
    await show()
}

Top-level await works directly in main.swift. await marks every suspension point where other code may run.

async let and TaskGroup

// Fixed number of concurrent child tasks
async let a = fetchScore(id: 1)
async let b = fetchScore(id: 2)
let combined = try await a + b           // both were already running in parallel

// Dynamic fan-out
let scores = try await withThrowingTaskGroup(of: Int.self) { group in
    for id in 1...20 {
        group.addTask { try await fetchScore(id: id) }
    }
    var results: [Int] = []
    for try await score in group { results.append(score) }
    return results
}

Child tasks are structured: they cannot outlive the scope, and cancellation propagates automatically (check with Task.checkCancellation()).

Actors and @MainActor

actor ScoreBoard {
    private var scores: [String: Int] = [:]

    func add(_ n: Int, for player: String) {
        scores[player, default: 0] += n
    }
    func total() -> Int { scores.values.reduce(0, +) }
}

let board = ScoreBoard()
await board.add(10, for: "ada")          // cross-actor calls are awaited

@MainActor
final class ViewModel {                  // every member runs on the main thread
    var items: [String] = []
    func refresh() async {
        items = await loadItems()        // safe to touch UI-facing state here
    }
}

Actors serialize access to their mutable state, eliminating data races on it. UI state belongs on @MainActor.

Sendable and Swift 6 strict concurrency

struct Payload: Sendable {               // safe to send across concurrency domains
    let id: Int
    let tags: [String]
}

Value types whose members are all Sendable conform implicitly. Classes qualify only if final with immutable state, or @unchecked Sendable with hand-written locking. The Swift 6 language mode turns data-race violations (non-Sendable values crossing Task/actor boundaries, shared mutable globals) into compile errors. Opt in from Swift 5 mode with -strict-concurrency=complete.