DS DevShelfHub Projects · AI tools
Cheatsheets / Swift
Cheatsheet · Languages

Swift 5.9 Cheatsheet: Optionals, Protocols, Generics and Concurrency

By DevShelfHub

Optionals, value vs reference types, protocols, generics, async/await, actors, result builders, SwiftUI basics — the modern Swift 5.9+ surface.

119 items ◷ 8 min Optionals Protocols Actors

Start hereQuick start · 6 you’ll reach for daily

Bindingslet x = 1 · var y = 2
Unwrapguard let v = opt else { return }
Structstruct User: Identifiable { … }
Protocolprotocol Repo { func all() async throws → […] }
Asynclet v = try await fetch()
Concurrentasync let a = f() ; async let b = g()

Target versions · paceVersions

Targets: swift ≥ 5.9 SwiftPM ≥ 5.9 iOS / macOS / Linux / Windows Foundation, Concurrency, SwiftData

Swift 5.9 introduced macros, parameter packs, and the if / switch as expressions. Swift 5.10 tightens strict concurrency — a single binary deploys across Apple platforms; Linux + Windows are first-class. Foundation has been re-implemented in pure Swift for non-Apple targets (Open Source swift-corelibs-foundation). For Apple-platform UI, SwiftUI is the modern path; UIKit / AppKit are still supported.

Install · SwiftPMSetup

bash
# macOS — Swift ships with the Xcode command-line tools
xcode-select --install

# Linux — install the official tarball from swift.org/install
# (or use swiftly, the toolchain manager)
curl -O https://download.swift.org/swift-5.10-release/ubuntu2204/...
tar xzf swift-*.tar.gz

# New SwiftPM project
swift package init --type executable --name MyApp
swift run                              # build + run
swift build -c release                 # optimized binary in .build/release/

# Daily commands
swift test                             # XCTest
swift package update                   # bump deps
swift-format --in-place --recursive Sources/

# Open the same package in Xcode
xed .                                  # opens current dir in Xcode

Where things liveCommon imports

import FoundationDates, URLs, networking, JSON. Standard for any non-trivial program.
import SwiftUIDeclarative UI for Apple platforms.
import CombineReactive streams (mostly superseded by AsyncSequence).
import OSLogStructured logging via Logger.
import SwiftDataPersistent stores via @Model macros.
import Observation@Observable macro replaces ObservableObject.
import XCTestBuilt-in unit testing.
@preconcurrency import LegacySuppress strict-concurrency warnings on an older import.

let · var · primitivesVariables & types

Bindings

let x = 5Immutable. Default. Use whenever possible.
var y = 5Mutable.
let x: Int = 5Explicit type. Inferred otherwise.
let (a, b) = (1, 2)Tuple destructuring.
let v: lazy Int = compute()Legacy Old syntax. Modern is lazy var in a type.
typealias UserID = StringType alias.

Built-in types

Int, Int8, Int16, Int32, Int64Signed integers. Int is 64-bit on 64-bit platforms.
UInt, UInt8, UInt16, UInt32, UInt64Unsigned variants. Use only at FFI boundaries.
Double, Float, Float16Prefer Double. Float16 for ML interop.
Bool, String, CharacterCharacter = grapheme cluster, not a code point.
Array<T> / [T]Dynamic array. Value type with COW.
Dictionary<K, V> / [K: V]Hash map.
Set<T>Hash set.
(a: Int, b: String)Tuple. Named fields are optional.
String interpolation: "x = \(x)"Backslash-paren, not ${}.
"""raw"""Multi-line string. Indentation is trimmed to the closing """.

The big ideaOptionals

var name: String? = "Ada"Nullable. Sugar for Optional<String>.
if let name = name { … }Bind when non-nil. Shadows the outer name.
guard let name else { return }Preferred Early exit. Keeps happy path flat.
name ?? "anon"Nil-coalescing. Right side is the default.
name?.uppercased()Optional chaining. Short-circuits on nil.
name!Force-unwrap. Crashes on nil. Avoid.
var name: String!Implicitly-unwrapped optional. For lazy / DI props.
.flatMap { v in … }Optional’s map that unwraps a returned optional.

Worked example

swift
// Optionals — every wrapped value must be unwrapped before use

let name: String? = "Ada"
let email: String? = nil

// 1 · if-let — bind only when non-nil
if let name = name {
    print("name = \(name)")
}

// 2 · guard-let — early exit, keep happy path flat
func greet(_ name: String?) {
    guard let name, !name.isEmpty else {
        print("anon"); return
    }
    print("hi \(name)")        // unwrapped from here on
}

// 3 · nil-coalescing — default when missing
let display = email ?? "no email"

// 4 · optional chaining — short-circuits on nil
let upper = email?.uppercased()      // String?

// 5 · throwing variant — bridges Result/throws
func parsePort(_ s: String?) throws -> Int {
    guard let s, let n = Int(s) else {
        throw URLError(.badURL)
    }
    return n
}

print(display)
greet(nil)
greet("Grace")
print(try parsePort("8080"))

if · switch · forControl flow

if x > 0 { … } else { … }Braces required. No parens.
if x > 0, let v = opt { … }Multiple conditions, comma-separated.
let v = if x > 0 { "+" } else { "-" }if as an expression (5.9+).
switch x { case …: … default: … }Exhaustive. Each case auto-breaks.
case let .point(x, y): …Pattern bind on associated values.
case 0..<10: …Range matching.
case let p where p.x > 0: …Where-clause guard.
for i in 0..<10Half-open range.
for (i, v) in xs.enumerated()Index + value.
while cond { … } / repeat { … } while condPre / post test.

Labels · closuresFunctions & closures

func add(_ a: Int, _ b: Int) -> Int { a + b }Single-expression body returns implicitly.
func send(to: String, subject: String = "hi")Default args. Labels are public by default.
send(to: "x@y", subject: "z")Call-site labels — this is why APIs read like sentences.
func first<T>(_ xs: [T]) -> T?Generic.
func first<C: Collection>(_ c: C) -> C.Element?Constraint via the type system.
func parse() throws -> ValueThrowing function. Callers use try.
func go() async throws -> TAsync + throwing. Combine freely.
let sum = { (a: Int, b: Int) in a + b }Closure literal.
xs.filter { $0 > 0 }$0, $1: implicit args.
@escaping closureRequired when a closure outlives the call (stored, async).
@Sendable closureSafe to cross actors. Required for Task in strict concurrency.

Value · reference · taggedStructs, classes, enums

Structs (value types)

struct Point { var x: Int; var y: Int }Memberwise init synthesized.
struct Point: Equatable, Hashable, Codable {}Protocol conformance auto-synthesized.
extension Point { func mag() -> Double { … } }Add methods without modifying the type.
mutating func scale(by k: Int)Methods that mutate a struct must be marked.
Use struct by defaultValue semantics + COW = simpler concurrency story.

Classes (reference types)

final class Service { … }final prevents subclassing — almost always what you want.
class Sub: Base { override func … }Single inheritance + protocols.
deinit { … }Destructor. Runs on the last reference release.
weak var owner: Owner?Break reference cycles. Optional by definition.
unowned let owner: OwnerNon-optional weak. Crashes if accessed after deallocation.

Enums

enum Direction { case north, south, east, west }Plain enum.
enum Result { case success(T), failure(Error) }Associated values per case — tagged union.
enum HTTP: Int { case ok = 200, notFound = 404 }Raw values. HTTP(rawValue: 200) → Optional.
indirect enum Tree { case leaf(Int); case node(Tree, Tree) }Recursive enums need indirect.

The polymorphism storyProtocols & generics

protocol Greeter { func hello() -> String }Define a contract. Methods + props + associated types.
extension Greeter { func hello() -> String { "hi" } }Default implementations.
associatedtype ElementType-level slot. The classic example: Sequence.Element.
some ProtocolOpaque return type. One concrete type known at compile time.
any ProtocolExistential. Heterogeneous box. Slower.
where Element == IntConstrain associated types in generics.
protocol Identifiable { associatedtype ID: Hashable; var id: ID { get } }Stdlib protocol.
Equatable, Hashable, Comparable, CodableStandard conformances. Often auto-synthesized.

Worked example

swift
// Protocol with associated type + default implementation
protocol Repository {
    associatedtype Item: Identifiable
    func all() async throws -> [Item]
    func find(_ id: Item.ID) async throws -> Item?
}

extension Repository {
    func count() async throws -> Int {            // default impl
        try await all().count
    }
}

// Value type that conforms
struct User: Identifiable, Codable, Equatable {
    let id: String
    var name: String
}

actor InMemoryUsers: Repository {                 // actor — serial access
    private var store: [String: User] = [:]

    func upsert(_ u: User) { store[u.id] = u }
    func all() -> [User] { Array(store.values) }
    func find(_ id: String) -> User? { store[id] }
}

// Generic constraint via where-clause
func print(all repo: some Repository) async throws {
    let xs = try await repo.all()
    print("loaded \(xs.count)")
}

throws · Result · do/tryErrors

enum AppError: Error { case missing, invalid(String) }Any type conforming to Error can be thrown.
func parse() throws -> IntThrowing function signature.
do { try parse() } catch { print(error) }Generic catch. error is the implicit binding.
catch let e as AppErrorTyped catch.
catch .missing { … }Match on a specific case (5.9+).
try? parse()Returns Int? — nil on error.
try! parse()Force. Crashes on error. Avoid.
Result<T, E>When you want a value type instead of throws.
throws(SpecificError)Typed throws (5.9+). Constrains what may be thrown.

async/await · actors · tasksConcurrency

async func fetch() async throws -> TAsync signature.
let v = try await fetch()Suspension point. The compiler enforces both keywords.
async let a = f(); async let b = g()Parallel decomposition. Awaited at use.
Task { try await go() }Detached top-level task. Returns Task<T, Error>.
Task.detached { … }No parent. Doesn’t inherit priority / actor.
await task.valueWait for a Task’s result.
task.cancel()Cooperative cancellation. Check Task.isCancelled.
withTaskGroup(of: T.self) { group in … }Dynamic fan-out. Iterate for await result in group.
actor Counter { var n = 0; func inc() { n += 1 } }Serialize access to mutable state.
await counter.inc()All cross-actor calls are await.
@MainActor func updateUI()Run on the main thread.
@SendableSafe to share across actors. Compiler-checked.
AsyncSequence / AsyncStreamAsync iteration. for await x in stream.

Array · Dictionary · SetCollections

var xs = [1, 2, 3]Array literal.
xs.append(4) / xs += [5, 6]Mutate.
xs[0]; xs.first; xs.lastIndex returns the value; first / last are optionals.
xs.map { $0 * 2 }.filter { $0 > 5 }Eager pipeline. .lazy for lazy.
xs.reduce(0, +)Reduce with operator.
xs.compactMap { Int($0) }Map + drop nils. Idiomatic for parsing.
xs.flatMap { $0 }Flatten one level.
Dictionary(grouping: xs, by: { $0.role })GroupBy → [K: [V]].
m["k", default: 0] += 1Idiomatic increment with default.
xs.sorted { $0.score > $1.score }Sort with a comparator. Returns a new array.
xs.contains(where: { … }), allSatisfy, firstIndex(of:)Predicate scans.

Concurrent HTTP · ~45 linesEnd-to-end · Async HTTP

Pure Foundation + Codable. TaskGroup fans out three GitHub API requests, each individually wrapped in Result so one failure doesn’t cancel the rest. No external dependencies.

swift
// Package.swift dependencies:
// .package(url: "https://github.com/apple/swift-async-algorithms.git", from: "1.0.0"),

import Foundation

struct Repo: Decodable {
    let name: String
    let stargazersCount: Int
    enum CodingKeys: String, CodingKey {
        case name, stargazersCount = "stargazers_count"
    }
}

func fetchRepos(owner: String) async throws -> [Repo] {
    var req = URLRequest(url: URL(string: "https://api.github.com/users/\(owner)/repos?per_page=5")!)
    req.setValue("devshelf", forHTTPHeaderField: "User-Agent")

    let (data, resp) = try await URLSession.shared.data(for: req)
    guard let http = resp as? HTTPURLResponse, (200..<300).contains(http.statusCode) else {
        throw URLError(.badServerResponse)
    }
    return try JSONDecoder().decode([Repo].self, from: data)
}

@main
struct Main {
    static func main() async {
        let owners = ["apple", "vapor", "pointfreeco"]

        // Concurrent fan-out via async let
        await withTaskGroup(of: (String, Result<[Repo], Error>).self) { group in
            for owner in owners {
                group.addTask {
                    do    { return (owner, .success(try await fetchRepos(owner: owner))) }
                    catch { return (owner, .failure(error)) }
                }
            }
            for await (owner, result) in group {
                switch result {
                case .success(let repos):
                    for r in repos { print("\(owner)/\(r.name) ★\(r.stargazersCount)") }
                case .failure(let err):
                    print("\(owner): \(err.localizedDescription)")
                }
            }
        }
    }
}

Best practiceGood to know

Use structs by default; reach for classes only when you need identity. Value types make local reasoning easy — no shared mutable state, no surprise aliasing. Classes are right for things with an inherent identity (a network session, a controller).
Mark types final unless they need to be subclassed. Final classes get devirtualized by the optimizer and lock down your API surface. Inherited classes are usually a refactor smell.
Adopt strict concurrency early in a project. The compiler flags unsafe sharing of mutable state at compile time. Turning it on later means fixing dozens of warnings at once.

Common trapsWatch out for

! is force-unwrap, not assertion. It crashes the process on nil. Tempting in the moment, painful in production logs. Use guard let or ?? instead.
Closures capture by reference, not by value. A [weak self] capture list is mandatory anywhere a closure outlives self — especially in long-running async tasks and Combine subscriptions.
Calling synchronous blocking work inside an actor stalls every other caller. Actors serialize. A heavy disk read or sync HTTP call blocks every other message. Move CPU/IO work off-actor via Task.detached or async APIs.

Go deeperSee also

Swift FAQ

What are optionals in Swift?

An optional is a type that can hold either a value or nil. Declare with a ? suffix: var name: String? = nil. Unwrap safely with if let, guard let, or the nil-coalescing operator ??. Forced unwrapping with ! crashes at runtime if nil, so use it only when you have a logical guarantee the value exists. Optionals eliminate null-pointer exceptions by making nil-ability explicit in the type system.

What is the difference between struct and class in Swift?

Structs are value types: each assignment or function pass creates a copy. Classes are reference types: multiple variables can refer to the same instance. Swift prefers structs for model data (they are thread-safe by default and benefit from copy-on-write optimisation) and classes for objects that need identity, inheritance, or Objective-C interoperability like UIViewController subclasses.

What are Swift protocols?

A protocol defines a blueprint of methods, properties, and requirements that a conforming type must implement. Swift uses protocols for polymorphism instead of class inheritance: a struct, enum, or class can conform to multiple protocols simultaneously. Protocol extensions let you provide default implementations, turning a protocol into a powerful mixin pattern without runtime overhead.

How does async/await work in Swift?

Mark a function async to allow it to suspend without blocking a thread: func fetchData() async throws -> Data. Call it with await fetchData(). The Swift concurrency runtime suspends the current task at await points and resumes it when the result is ready, freeing the thread for other work. Use async let for parallel execution and Task {} to call async code from synchronous contexts.

What are Swift actors?

An actor is a reference type that serialises access to its mutable state, preventing data races without manual locking. Declare with actor MyActor { var count = 0 }. Access to actor-isolated properties and methods requires await, which signals that the call may need to wait for the actor to become available. Use @MainActor to pin UI-updating code to the main thread automatically.