Swift 6.3 · the layout, the retains, the dispatch and the isolation · 534 entries

Written for someone who already knows a systems language and wants the parts of Swift that are actually peculiar, not a syntax tour. The through-line is what the compiler emits: what a value type costs and when copy-on-write stops being free, where ARC puts its retains and which ones the optimiser removes, which of the four dispatch mechanisms a call gets and why a method defined in a protocol extension quietly picks the wrong one, what an existential box really is — any P measures 40 bytes and this sheet says which five words those are — and how Swift’s isolation rules turn data races into compile errors. Cards 1–24 are the guide; the rest is a filterable index of the language, the standard library, Foundation and the toolchain, with a dot on every entry saying which Swift it needs — 48 of the 534 entries here want Swift 6 or newer. Press / to jump to the filter box; hover a clipped row for the whole entry.

Needs: Swift 5-era Swift 6.0 / 6.1 Swift 6.2 / 6.3 deprecated / removed
Sources: The Swift Programming Language, the Swift standard library and Foundation reference documentation, the accepted Swift Evolution proposals, and the swift-driver and SwiftPM manuals. Every layout number, dispatch claim and diagnostic on this page was produced by compiling it with Apple Swift 6.3.3 on macOS 26. Hover a clipped row for the whole entry.

The Working Guide

Swift as the compiler actually implements it — the layout, the retains, the dispatch and the isolation

What Swift Actually Is

the bargain, and what it costs

Swift is a value-semantics language with automatic reference counting underneath it, and almost everything surprising follows from that one sentence. There is no tracing collector, so there is no pause and no nondeterminism — and there is instead a retain/release traffic problem and a cycle problem. Types are checked statically and generics are specialised rather than boxed, so generic code can be as fast as hand-written code — and when the compiler cannot see across a module boundary it falls back to a boxed, table-driven representation that is not.

The four things that are genuinely not C, C++ or Java

Value semantics + COWarrays, strings and dictionaries are values that copy on assignment — but only physically when one of the copies is writtenOptional as a typenullability is in the type system, not a convention; a non-optional reference cannot be nil, and the compiler enforces itProtocol witnessesconformance is a separate record, not a bit in the object header, so a struct or an enum conforms as cheaply as a classIsolationsince Swift 6 the concurrency domain of a value is part of its type-checking, and a data race is a compile error

What each abstraction actually costs

ConstructRuntime costHidden cost
struct of trivial fieldsnone — registers, like a C structcopies are memberwise; no field reordering (see the layout card)
class instanceheap allocation, 16-byte header, refcount trafficevery copy of the reference is an atomic increment
Array, String, Dictionaryone word; the storage is a class instancea write checks uniqueness; if shared, the whole buffer is copied
any P existential40 bytes: 3-word buffer + metadata + witness tablea payload larger than 3 words is heap-boxed on every copy
some P opaque typezero — one concrete type, resolved at compile timenone; this is the one to reach for
generic func f<T>zero when specialised, a witness-table call when notspecialisation stops at a module boundary without @inlinable
a closuretwo words: function pointer + context@escaping forces the context onto the heap
throwsa register and a branch — no unwinder, no tablesalmost nothing; this is not a C++ exception
awaita possible suspension: the frame moves to a heap-allocated async framestate after the await may have changed — see actor reentrancy
The rule that pays for itself. If the compiler can see the concrete type, the abstraction is free: it specialises the generic, devirtualises the call and elides the retains. The moment the type is erased — any P, a non-final class, a cross-module generic without @inlinable — you have bought an indirect call and possibly a box. That is the whole performance model.

Language modes, which are not the same as versions

-swift-version 5Swift 5 semantics: concurrency checking is warnings only. Still the default for a target that has not opted in-swift-version 6Swift 6 semantics: data-race safety is enforced, and the errors are real errors-enable-upcoming-feature Xtake one future-mode change early, e.g. ExistentialAny, InferSendableFromCaptures-default-isolation MainActor6.2: every declaration in the module is main-actor isolated unless it says otherwise-strict-memory-safety6.2: warns on every use of an unsafe construct, so @unsafe becomes a visible, auditable thing

A 6.3 compiler still compiles Swift 5 code: the toolchain version and the language mode move independently, and one module in a package can be in Swift 6 mode while its neighbour is not. That is the whole migration story.

Values, References and COW

the copy that usually is not one

Assignment of a value type copies. That would be ruinous for an Array, so the standard library’s value types are one word pointing at a class-instance buffer, and the copy is deferred until somebody writes.

var a = [1, 2, 3] // buffer refcount 1 var b = a // refcount 2, no elements copied b.append(4) // isKnownUniquelyReferenced == false -> copy the buffer now // a is [1,2,3]; b is [1,2,3,4] in fresh storage

Verified with withUnsafeBufferPointer: after var b = a both share a base address; after b.append(4) they do not.

Writing your own copy-on-write type

final class Storage { var xs: [Int]; init(_ xs: [Int]) { self.xs = xs } } struct Buf { private var s: Storage init(_ xs: [Int]) { s = Storage(xs) } var count: Int { s.xs.count } // read: no check mutating func append(_ x: Int) { if !isKnownUniquelyReferenced(&s) { s = Storage(s.xs) } // the whole trick s.xs.append(x) } }
isKnownUniquelyReferenced only sees strong references, and only works on a final class. It returns false for an Objective-C class, so a COW type wrapping a bridged NSObject silently copies on every mutation.

struct, class, actor, enum — choosing

KindSemanticsReach for it when
structvalue; copied; no inheritance; deinit only via ~Copyablethe default. Data with no identity.
enumvalue; a closed set of cases with payloadsstate that is one of n things — the compiler checks you handled all n
classreference; identity; inheritance; deinitidentity matters, or you need deinit, or Objective-C needs it
actorreference + an isolation domain; access is awaitedmutable state shared across concurrency domains
final classreference, no subclassingevery class you do not explicitly intend to be subclassed — it unlocks direct dispatch

The mutation rules that trip people up

let s = SomeStruct()the whole value is immutable, including every property — unlike a let class referencelet c = SomeClass()only the reference is immutable; c.x = 1 is finemutating funcrequired to write self in a struct/enum method; self is inoutnonmutating seta setter that does not write storage — how property wrappers write through to a classd["k"]?.append(1)in-place through a subscript: no temporary copy, because the accessor is _modify
A struct in a dictionary or array is a value, and so is the thing you got out of it. for var item in items { item.flag = true } mutates a copy and throws it away. Mutate through the index — items[i].flag = true — or map to a new array.

Value semantics are not automatic

A struct holding a class reference has reference semantics for that part. Copy it and both copies point at the same object. This is the single most common way a "value type" turns out to be shared mutable state.

Memory Layout

size, stride, and the existential box

MemoryLayout<T> is the truth, and it is available at compile time. size is the bytes the value occupies, stride is the distance between consecutive elements in an array (size rounded up to alignment), and the two are not the same number for anything with tail padding.

TypesizeWhy
Int, Double, AnyObject8one word on arm64/x86-64
Bool1stride 1 too
Int?9 (stride 16)no spare bits in an Int, so the tag needs its own byte
Bool?1Bool has 254 spare bit patterns; the tag hides in one
SomeClass?8nil is the null pointer — an optional reference is free
SomeClass??8still free: more spare pointer bits carry the second tag
String16two words; up to 15 UTF-8 bytes live inline (small-string form)
Substring32a String plus a range — it keeps the whole parent alive
Array, Set, Dictionary8one pointer to a heap buffer
() -> Void16function pointer + context pointer
Any323-word buffer + type metadata
any P403-word buffer + metadata + one witness table per protocol
any P & Q48each extra protocol adds another witness-table word

The existential container, drawn

any P (40 bytes on a 64-bit target) +0 value buffer word 0 | payload <= 3 words: stored inline +8 value buffer word 1 | payload > 3 words: word 0 is a pointer to a +16 value buffer word 2 | heap box, and copying the existential +24 type metadata pointer | retains/copies that box +32 protocol witness table
The three-word rule is the whole performance story of any. A struct of four Ints in an any P is heap-allocated every time the existential is copied. The same struct behind some P or a specialised generic costs nothing. Prefer some; reach for any only when you genuinely need a heterogeneous collection.

Swift does not reorder your fields

struct Pad { var a: Bool; var b: Int; var c: Bool } MemoryLayout<Pad>.size // 17 MemoryLayout<Pad>.stride // 24 <- what an array element costs MemoryLayout<Pad>.alignment // 8 struct Tidy { var b: Int; var a: Bool; var c: Bool } MemoryLayout<Tidy>.size // 10, stride 16

Declaration order is layout order, exactly as in C. Sorting members largest-first still pays. There is no -Wpadded; MemoryLayout in a test is how you find it.

Reading and writing raw memory

MemoryLayout<T>.size / .stride / .alignmentstatic; also .size(ofValue:) for an existential's dynamic typewithUnsafeBytes(of: x) { … }the bytes of a value, without a heap round tripUnsafeMutableRawPointer.allocate(byteCount:alignment:)you own the deallocatep.bindMemory(to:capacity:) / assumingMemoryBound(to:)type punning: the second asserts, the first changes the bindingclass instance header16 bytes on Apple platforms: isa pointer + refcount word, before your first stored property

Optionals

an enum with a compiler pass behind it

Optional<T> is an ordinary two-case enum — nothing more. Everything else is sugar and a layout optimisation.

enum Optional<Wrapped> { case none; case some(Wrapped) } Int? == Optional<Int> nil == Optional.none x! == the .some payload, or a fatalError x?.foo == optional chaining, itself producing an optional x ?? d == the payload, else d -- d is @autoclosure, so it is lazy

The unwrapping ladder, worst to best

FormWhen it is right
x!only when nil is a programmer error you want to trap on. It is a deliberate crash, and it is sometimes correct.
if let x { … }the shorthand since 5.7 — no x = x needed. Scoped to the branch.
guard let x else { return }the one to default to: unwraps for the rest of the scope, and forces the failure path to be explicit.
x ?? defaulta value is always available. Chains: a ?? b ?? c.
x.map / .flatMaptransforming without unwrapping. map on an optional-returning body gives T?? — that is what flatMap is for.
if case .some(let x)when you are already in a pattern match.
x?.foo()the call happens only if non-nil; the result is optional even if foo returns non-optional.
Optional chaining on a subscript assignment silently does nothing. dict["missing"]?.count = 5 is a no-op, not a crash and not an insertion. Likewise try? collapses a thrown error into nil and discards it — convenient, and the most common way a real error disappears from a codebase.

Traps worth naming

as? Ta conditional cast; on an existential or a class hierarchy it is a runtime metadata walk, not freeAny? nestingOptional<Any> holding an Optional prints "Optional(Optional(1))" and compares oddly — unwrap before boxingString(describing: opt)gives "Optional(3)"; use opt.map(String.init) ?? ""var x: T!an implicitly-unwrapped optional: still T?, just with an implicit ! at each use. Only for the two-phase init dance and imported Objective-C?? has lower precedence than ==a ?? b == c parses as a ?? (b == c) — parenthesiseflatMap on a Sequence of optionalsrenamed compactMap; the old spelling is deprecated
Nil-coalescing is lazy. The right-hand side is @autoclosure, so cache[k] ?? expensiveRecompute() only recomputes on a miss. Your own APIs can do the same.

ARC

where the retains come from

Every class instance carries a reference count in its header. The compiler inserts swift_retain and swift_release around every place a strong reference is copied or destroyed, then the optimiser removes as many as it can prove are redundant. The count is atomic, so the ones it cannot remove cost real cycles on every core.

The three ownership qualifiers

QualifierKeeps alive?On deallocationCost
strong (default)yesn/aatomic inc/dec per copy
weak var x: T?nobecomes nil, observablya side-table entry; reads are not free
unowned let x: Tnoreading it trapscheaper than weak; still checked
unowned(unsafe)noreading it is undefinedfree, and a dangling pointer if you are wrong

Rule of thumb: weak when the reference legitimately outlives the target (delegates, caches); unowned when the target is guaranteed to outlive the reference (a child pointing at its parent). Since Swift 6.2 weak let is allowed, so an immutable weak reference no longer has to be a var.

Cycles, and the two places they actually happen

// 1. two objects referring to each other final class Node { var next: Node?; weak var prev: Node? } // one side weak // 2. a closure capturing self, stored on self final class VM { var onDone: (() -> Void)? func wire() { onDone = { [weak self] in self?.finish() } // without [weak self]: a cycle } func finish() {} }
[weak self] in guard let self else { return }the standard opening; since 5.8 guard let self needs no = self[unowned self]when the closure cannot outlive self — a synchronous callback you own[weak self, capturedValue = value]a capture list can also bind an arbitrary expression at closure-creation timeTask { [weak self] in … }a Task escapes by definition; an unqualified self here is a real retain that lasts until the task ends
A non-escaping closure cannot cause a cycle — it is destroyed before the call returns — so [weak self] inside array.map { … } is noise. Reserve capture lists for @escaping closures, stored properties and Tasks.

Lifetime is not scope

The optimiser may release an object immediately after its last use, which can be before the end of the enclosing scope. Code that relies on a deinit firing at a particular moment — a lock guard, an os_signpost pair, a file handle — needs withExtendedLifetime(x) { … } or, since Swift 5.7, an explicit defer.

deinitclasses and actors only; runs when the count hits zero, on whichever thread released lastwithExtendedLifetime(obj) { … }pin an object across a region the optimiser would otherwise shortenCFGetRetainCount / _getRetainCountdo not; the numbers include the optimiser's own temporaries and misleadInstruments > Allocations, "Swift Retain/Release"where the traffic actually is — measure before restructuring

Method Dispatch

four mechanisms, and the one that surprises you

Swift picks one of four dispatch mechanisms per call, statically. Knowing which is the difference between a call that inlines and one that cannot.

MechanismUsed forCost
Direct (static)structs, enums, final, private, globals, anything in an extensiona call, usually inlined away
Vtablenon-final class methods declared in the class bodyone load + indirect call
Witness tableprotocol requirements through any P or an unspecialised genericone load + indirect call
objc_msgSend@objc dynamic, and anything inherited from NSObject that stays dynamica message send; enables swizzling and KVO

The trap: an extension method is not a requirement

protocol Greeter { func hello() } // a requirement extension Greeter { func hello() { print("protocol ext") } // a default implementation func bye() { print("protocol ext") } // NOT a requirement -- static dispatch } struct G: Greeter { func hello() { print("G") } func bye() { print("G") } } let g = G(); g.hello(); g.bye() // G, G let p: any Greeter = G(); p.hello(); p.bye() // G, protocol ext <--
Through the existential, bye() dispatches to the protocol extension even though G defines its own. It is not in the witness table, because it was never a requirement, so the call is resolved from the static type. Verified above on 6.3.3. If you want an override to win, declare it in the protocol body. The same rule applies to class extensions: a method added in an extension of a class is not in the vtable and cannot be overridden — a subclass redeclaring it merely shadows it.

Getting dispatch back under control

final class / final funcremoves the vtable entry; the single highest-value annotation in a class-heavy codebaseprivate / fileprivatethe compiler proves there are no overrides in the file and makes it direct anywayinternal + whole-module optimisationWMO devirtualises across the module; this is why -O and -Onone can differ so muchpublic / openopen is the only one subclassable outside the module; public classes are not@inlinableships the body in the module interface so callers in other modules can specialise and inline it@objc dynamicforces objc_msgSend — required for KVO, swizzling and most Objective-C runtime tricks@_dynamicReplacementunderscored, unstable; the mechanism behind SwiftUI previews' hot reload
Static by default is why Swift structs are fast. A protocol-oriented design that ends up everywhere behind any P has quietly converted every call into an indirect one and every value into a possibly-boxed 40-byte container. Use some P in parameter and return position and the same design compiles to direct calls.

Generics

specialised, or table-driven

A Swift generic is compiled once, against a protocol witness table — unlike a C++ template, which is compiled per instantiation. The optimiser then specialises hot instantiations back into concrete code. Where it can do that, generics are free; where it cannot, every operation is an indirect call through a table and every value is manipulated through its value witnesses (copy, destroy, project).

func maxOf<T: Comparable>(_ a: T, _ b: T) -> T { a > b ? a : b } // Inside the module, -O specialises maxOf<Int> into a compare and a select. // Called from another module without @inlinable, it stays generic: the caller // passes the metadata for Int plus the Comparable witness table, and > is an // indirect call through that table.

Where specialisation stops, and how to restart it

@inlinablepublishes the body in the .swiftinterface; other modules can then specialise and inline. The body becomes ABI, so you cannot change it freely@usableFromInlinemakes an internal declaration referenceable from an @inlinable body without making it public-cross-module-optimizationwhole-package specialisation without annotating each function; costs build time@_specialize(where T == Int)underscored: emit a named specialisation eagerly. Occasionally the right answer in a hot library-enable-library-evolutionthe opposite: resilient layout, everything indirect, so the module's ABI can change without recompiling clients

some versus any — the decision that matters

some P (opaque)any P (existential)
Meansone specific type, which the caller does not get to knowany type, decided at run time, boxed
Layoutthe concrete type — nothing added40 bytes; heap box if the payload exceeds 3 words
Dispatchstatic, after specialisationwitness table, always
Heterogeneous arrayno — all elements must be the same typeyes; this is the reason it exists
Associated typesfine; use a primary associated type, some Collection<Int>fine since 5.7, but the associated type is erased
Write it inparameters and return types — the defaultstored properties and collections that must be mixed

Under the ExistentialAny upcoming feature (on by default in future language modes), a bare P in type position is an error and you must write any P — precisely so that the cost is visible in the source. Turning it on early is a cheap way to audit a codebase.

The rest of the generics surface

where Element: Equatablea constraint clause; also where T.Element == U for same-type constraintsextension Array where Element: Numericconditional extension — methods that exist only for some instantiationsprotocol Collection<Element>a primary associated type; enables some Collection<Int> and any Collection<Int>func f<each T>(_ v: repeat each T)parameter packs (5.9): variadic generics, type-safeT: ~Copyableopt out of the implicit Copyable constraint so the generic also accepts move-only types (6.0)associatedtype Element: Equatablea requirement on the conforming type's own choice

Protocols

conformance is a record, not a bit

A conformance is a standalone protocol witness table: a list of function pointers, one per requirement, emitted next to the type. Nothing is added to the type itself, which is why a struct, an enum and even a tuple-shaped generic can conform as cheaply as a class, and why conformances can be added retroactively from another module.

The shapes of a requirement

func f()a method requirement; the conformer supplies the witnessvar x: Int { get set }a property requirement — { get } may be satisfied by a let, { get set } may notinit(from: Decoder)an initialiser requirement; a non-final class must mark its implementation requiredstatic func == operators are static requirements — this is how Equatable worksassociatedtype Elementthe conformer chooses a type; the protocol can constrain itsubscript(i: Int) -> Elementsubscripts are requirements too

Synthesised conformances — what you get free

ProtocolSynthesised when
Equatable, Hashableevery stored property conforms; for an enum, every payload does. Declare the conformance and write nothing.
Codableevery stored property is Codable; customise with a CodingKeys enum
CaseIterablean enum with no associated values — gives allCases
Comparableonly for an enum with no payloads (source order is the ordering); otherwise write <
Sendableinferred for a frozen value type all of whose members are Sendable; never inferred for a public type in a resilient module
RawRepresentablean enum with a raw-value type
Synthesis is all-or-nothing and silent. Add one non-Hashable stored property and the conformance stops being synthesised — the error appears at the declaration, not at the property that caused it. Add : Hashable to the property's type, or write hash(into:) by hand.

Conditional and retroactive conformance

extension Array: Drawable where Element: Drawable { … } // only for some Element // retroactive: conforming someone else's type to someone else's protocol extension URL: @retroactive Identifiable { public var id: String { absoluteString } }

The @retroactive spelling (6.0) exists because two modules doing this to the same pair produce a duplicate conformance the runtime picks arbitrarily. Writing it acknowledges the hazard; the alternative is a wrapper type, which is usually the better answer in application code.

Existential and class constraints

protocol P: AnyObjectclass-only: conformers must be classes, so weak and unowned work and the existential is one wordprotocol P: Sendableevery conformer must be Sendable — a common and useful refinementtypealias Both = P & Qa composition; any P & Q carries two witness tables@objc protocolObjective-C visible; may have optional requirements, which Swift protocols cannotprotocol P { static func make() -> Self }Self requirements are why some protocols cannot be used as any P at all

Enums and Pattern Matching

a closed set, checked at compile time

A Swift enum is a tagged union with a real layout optimiser behind it. The tag is packed into whatever spare bit patterns the payloads leave: enum E { case a, b, c } is one byte, and Optional<SomeClass> is eight, because the null pointer is a spare pattern.

enum Shape { case point // no payload case circle(r: Double) // labelled payload case rect(w: Double, h: Double) indirect case group([Shape]) // boxed, so the type can recurse } enum Suit: String, CaseIterable { case hearts = "H" } // raw values + allCases

The pattern grammar, in one table

PatternMatches
case .circle(let r)an enum case, binding its payload
case .rect(let w, _)_ discards; case .rect alone matches ignoring all payloads
case let .circle(r) where r > 1a guarded case — the where is part of the pattern
case 1...9a range, via ~=
case (let x, 0)a tuple pattern — switch on tuples is a real technique
case is Cat, case let c as Cata type pattern; the second binds the downcast value
case .some(let x), case let x?an optional; the ? suffix is the shorthand
if case .circle(let r) = sone-case match outside a switch; guard case is the early-exit form
for case let .circle(r) in shapesfilter-and-bind in a loop, no compactMap needed
case .a, .b:alternatives in one arm; every alternative must bind the same names and types
func ~= (p: Pattern, v: Value) -> Booloverload it and your own type works in a caseswitch must be exhaustivethe compiler proves it; adding a case then breaks every switch, which is the point@unknown defaultfor a non-frozen enum from another module: handles future cases but still warns when a known one is addedfallthroughexplicit; there is no implicit fall-through and no break required@frozen enumpromises the case list will never change, letting clients switch exhaustively across a resilient boundary
@unknown default is not the same as default. A plain default silences the exhaustiveness check for ever; @unknown default keeps it, so you still get a warning when the library you depend on adds a case you have not handled. Use it for every enum that crosses a module boundary you do not control.
Enums with payloads replace most of the state machines you would write with flags. If two booleans in a struct have an impossible combination, the type is wrong: an enum with three cases makes the impossible state unrepresentable, and the exhaustiveness check then does the work for you at every use site.

Initialisation

two phases, and why classes are fussy

Swift guarantees that no property is ever read before it is initialised, without zeroing memory. For structs that is a simple flow analysis. For classes it produces the two-phase rule, which is the source of most of the initialiser diagnostics you will meet.

Phase 1: every stored property in this class and every superclass gets a value. self is not usable -- no methods, no property reads, no passing self. Ends when super.init() returns. Phase 2: self is fully formed. Now you may call methods, read properties, and override the values set in phase 1.
final class Widget { let id: Int var name: String init(id: Int) { self.id = id // phase 1 self.name = "widget-\(id)" // still phase 1 super.init() // (implicit for a root class) configure() // phase 2 -- calling this earlier is an error } func configure() {} }

The kinds of initialiser

KindRule
designatedinitialises every property of its own class, then delegates up with super.init
conveniencedelegates across to another initialiser of the same class; can never touch super
requiredevery subclass must provide it — how protocol init requirements survive inheritance
init? failablemay return nil; the caller gets an optional. init! is the implicitly-unwrapped form
init memberwisesynthesised for a struct with no explicit init — writing one in an extension keeps the synthesised one
deinitclasses and actors only; runs bottom-up through the hierarchy, no explicit super call
Inheritance of initialisers is conditional, and the rule surprises everyone. A subclass inherits its superclass’s designated initialisers only if it declares no designated initialisers of its own; it inherits the convenience ones only if it has inherited or implemented all the designated ones. Add one initialiser to a subclass and the superclass’s disappear from its API.

Lazy, computed and observed properties

lazy var x = expensive()initialised on first read. Not thread-safe, and it makes the containing struct's getter mutatingvar y: Int { compute() }computed — no storage; the shorthand for a get-only propertyvar z: Int { get { … } set { … } }full form; newValue is the implicit setter parameterdidSet / willSetobservers; they do not fire from inside the type's own initialiserstatic let shared = Thing()a global or static let is lazy and atomic — the correct singleton, no dispatch_once needed@Observable / @Publishedmacro and property wrapper respectively; both turn a stored property into a change notification

Global and static stored properties are initialised on first use via swift_once. That makes them the one genuinely safe lazy in the language — and, in Swift 6, the one that needs Sendable or an actor if it is a var.

Errors

a return value in disguise

A Swift throw is not a C++ exception. There is no unwinder and no table walk: the callee sets an error register and returns, and the caller branches on it. That is why throws is nearly free on the success path and why it composes with async without a second mechanism.

func f() throws -> Intuntyped: the caller sees any Errorfunc f() throws(ParseError) -> Inttyped throws (6.0): the error type is in the signature, so catch is exhaustive and no existential is boxedfunc f() rethrowsthrows only if a closure argument does — how map stays non-throwing for non-throwing bodiesfunc f() throws(Never)the spelling of "does not throw"; what rethrows collapses to for a non-throwing argumenttry f()mandatory marker at every call that can throw — the error path is always visible in the sourcetry? f()Int?, error discardedtry! f()trap on error; a deliberate crashdefer { … }runs on every exit from the scope, in reverse order of declaration

Typed throws, and when to bother

enum ParseError: Error { case eof, badDigit(at: Int) } func parse(_ s: String) throws(ParseError) -> Int { guard !s.isEmpty else { throw .eof } // .eof infers the type … } do { _ = try parse(s) } catch .eof { … } // exhaustive: no `catch { }` needed catch .badDigit(let i) { … }

Use typed throws where the error set is genuinely closed and the caller must handle each case — a parser, a state machine, embedded code that cannot allocate. For everything that crosses a library boundary, any Error is still the right default: adding a case to a typed-throws signature is a source break for every caller.

Errors versus traps versus cancellation

FailureMechanismRecoverable
expected failurethrowyes — that is what it is for
programmer errorprecondition, assert, fatalError, !no — the process dies
arithmetic overflow, array boundsa trapno. Use &+ or addingReportingOverflow to opt out
task cancellationCancellationError from try Task.checkCancellation()yes, and it is cooperative — nothing is killed for you
unrecoverable system statefatalError("…")no; the message survives into the crash log
assert / assertionFailureremoved in -O. For conditions you are checking during developmentprecondition / preconditionFailurekept in -O, removed only by -Ounchecked. For invariants that must hold in shipping codefatalErrornever removed, even by -Ounchecked; returns Never, so the compiler knows the path endsResult<Success, Failure>an error as a value; try result.get() converts back. Mostly a callback-era tool nowLocalizedError / CustomNSErrorFoundation: what makes an error present sensibly to a user or bridge to NSError
try? in a catch-less codebase is where errors go to die. It is the right tool when nil genuinely means "no value", and the wrong one when it means "something failed and nobody looked". -Wunused-result has no equivalent here; only review does.

Closures and Functions

two words, and where the context lives

A closure value is a function pointer plus a context pointer: 16 bytes. If it does not escape, the context can live in the caller’s frame and the whole thing usually inlines away. If it escapes, the context is a heap-allocated box, and every captured variable moves into it.

{ (x: Int) -> Int in x * 2 }the full form{ x in x * 2 }types inferred from context{ $0 * 2 }positional shorthand; $0, $1, and _ to ignore{ [weak self, n = count] in … }capture list — evaluated at closure creation, not at callf { … }trailing closure; multiple trailing closures are labelled after the first (5.3)func f(_ body: () throws -> T) rethrowsthe signature that makes a helper transparent to errors

The attributes on a function type

AttributeMeans
@escapingmay outlive the call — required to store it. Closure parameters are non-escaping by default
@autoclosurewraps the argument expression in a closure at the call site, so it is not evaluated unless used. ??, assert and the && family are built on it
@Sendablesafe to hand to another concurrency domain; captures must be Sendable
@MainActorthe closure runs on the main actor; calling it from elsewhere requires await
@convention(c)a bare C function pointer — therefore it may capture nothing
@convention(block)an Objective-C block; capturing is fine, ARC-managed
consuming/borrowingownership of the parameter — see the ownership card
inoutpass-by-value-result: copied in, copied back on return. Not a reference, and exclusive for the duration
inout is not a pointer, and it enforces exclusivity. Passing the same variable to two inout parameters, or mutating a captured variable while it is inout elsewhere, is a compile error inside a function and a runtime trap ("Simultaneous accesses") across one. Nor may an inout argument be captured by an escaping closure — its lifetime ends when the call returns.

Functions are values, with a few sharp edges

let g = obj.methoda bound method reference — it strongly retains obj. A quiet source of cycleslet h = Type.methodan unbound curried reference: (Type) -> (Args) -> Rxs.map(String.init)initialisers are functions toocallAsFunctiondefine it and an instance becomes callable: instance(arg)@discardableResultsuppresses the unused-result warning at the declaration, not the calloperator +++ / precedencegroupyou can declare new operators, and their precedence group, at file scope

Structured Concurrency

a tree of tasks, and cooperative cancellation

Every await is a potential suspension point: the function may return to the executor and resume later, on a different thread. The frame lives on a heap-allocated async stack, not the thread stack, which is why an async function can suspend cheaply and why thread-local state is not reliable across an await.

async let a = fetchA() // starts immediately, in a child task async let b = fetchB() // both run concurrently let pair = try await (a, b) // suspend here until both are done try await withThrowingTaskGroup(of: Item.self) { group in for id in ids { group.addTask { try await fetch(id) } } // dynamic fan-out var out: [Item] = [] for try await item in group { out.append(item) } // in completion order return out }

The structure rule, and its two escapes

FormParentInherits
async let, TaskGroupthe enclosing task — cannot outlive its scopepriority, task-locals, isolation, cancellation
Task { … }unstructured, but still inheritspriority, task-locals, actor isolation of the context
Task.detached { … }nothingnothing — not the priority, not the isolation. Rarely what you want
Cancellation is cooperative and nothing is ever killed. Cancelling sets a flag on the task and its children. Code that never calls Task.checkCancellation(), never reads Task.isCancelled and never awaits a cancellation-aware primitive (Task.sleep, URLSession) runs to completion regardless. A long compute loop must check the flag itself.
try Task.checkCancellation()throws CancellationError if cancelled — the one-liner for a loopTask.isCancelledthe flag, for when you want to return a partial result rather than throwwithTaskCancellationHandler(operation:onCancel:)bridge cancellation to something that needs an explicit stop — a socket, a C APItry await Task.sleep(for: .seconds(1))cancellation-aware; Thread.sleep is not and blocks the cooperative poolawait Task.yield()give the executor a chance — the fix for a tight async loop that starves everything else@TaskLocal static var requestIDtask-local storage; inherited by child tasks, scoped by withValue

Bridging callbacks in

func load() async throws -> Data { try await withCheckedThrowingContinuation { k in legacyLoad { data, err in if let err { k.resume(throwing: err) } else { k.resume(returning: data!) } } } }

A continuation must be resumed exactly once. The checked forms trap on a double resume and log on a leak; the unsafe variants skip the bookkeeping and simply corrupt the task. Develop with checked, and only consider switching if a profile actually shows it.

AsyncStream / AsyncThrowingStreamturn a callback-based feed into a for await loop; the continuation carries a termination handlerfor await x in seqthe AsyncSequence loop — suspends between elementsMainActor.run { … }hop to the main actor for a block, from a nonisolated contextawait withDiscardingTaskGroupa group whose child results are discarded, so it does not accumulate memory in a long-running server loop

Actors and Isolation

the domain is part of the type

An actor is a reference type whose mutable state may only be touched from inside its own isolation domain. From outside, every access is awaited and hops to the actor’s executor. The compiler enforces this statically — that is the entire mechanism, and there is no lock you can forget to take.

actor Counter { private var n = 0 // isolated: only reachable from inside func bump() { n += 1 } // implicitly isolated, sync from inside nonisolated let id = UUID() // immutable + Sendable, so no isolation needed nonisolated func describe() -> String { "counter \(id)" } } let c = Counter() await c.bump() // hop to the actor print(c.id) // no await: nonisolated
Actor reentrancy is the trap that replaces the deadlock. An actor method that awaits gives up its isolation for the duration: another call can enter and mutate state before the first resumes. Re-check your invariants after every await inside an actor — caches especially. Actors prevent data races, not logic races.

The isolation vocabulary

SpellingMeans
@MainActorisolated to the main actor — a global actor whose executor is the main thread. UI code lives here
@globalActordeclare your own process-wide actor, e.g. one serialising all database access
nonisolatedopt a member out of its type’s isolation: callable from anywhere, may not touch isolated state
nonisolated(unsafe)"I have checked this by hand" — the escape hatch for a global that predates the model
isolated a: Counteran isolated parameter: the whole function body runs in a’s domain
#isolationthe caller’s isolation, as a value — how a generic API stays on the caller’s actor
assumeIsolated { }assert at run time that we are already on this actor, and get synchronous access. Traps if wrong
@concurrent (6.2)the opposite of the new default: run this async function on the shared pool, off the caller’s actor
nonisolated(nonsending) (6.2)an async function that runs in its caller’s isolation rather than hopping off it

What Swift 6.2 changed, and why it matters

Under approachable concurrency (-default-isolation MainActor, plus NonisolatedNonsendingByDefault), a module’s declarations are main-actor isolated unless marked otherwise, and a plain async function stays on its caller’s actor instead of hopping to the global pool. For an app that is mostly UI with a little background work, that inverts the annotation burden: you mark the few things that must leave the main actor with @concurrent, instead of marking everything that must stay.

await MainActor.run { … }from a nonisolated context, do this on the main actor@MainActor final class ViewModelthe ordinary shape of an app's model typeTask { @MainActor in … }an unstructured task pinned to the main actoractor's executorcustomisable via unownedExecutor — how an actor can be pinned to a specific dispatch queue@preconcurrency import Foodowngrade Sendable errors from a module that has not been audited yet

Sendable and Data-Race Safety

what Swift 6 mode actually checks

Sendable marks a type as safe to cross an isolation boundary. In Swift 6 language mode, passing a non-Sendable value from one domain to another is a compile error, and that single rule is the whole of data-race safety.

TypeSendable?
Int, String, Array of Sendable, enums with Sendable payloadsyes, inferred
a struct whose members are all Sendableinferred — but not for a public type in a library-evolution module, where you must write it
actoralways — that is the point of an actor
final class with only immutable Sendable storageyes, if you declare it
a non-final class, or any class with mutable stateno — and @unchecked is the only way to claim otherwise
a closureonly if @Sendable and all captures are Sendable
@unchecked Sendable"I serialise this myself, with a lock." Legitimate for a type wrapping a Mutex; a lie everywhere else@preconcurrencyon an import, a protocol conformance or a declaration: treat the other side as un-audited and downgrade the errors to warningsnonisolated(unsafe) var cachea global that you have reasoned about by hand. Prefer a Mutex or an actorsending x(6.0) the value is transferred, not shared: the caller must not use it afterwards, so it need not be SendableMutex<T> / Atomic<T>from the Synchronization module (6.0): a real lock and real atomics, both Sendable, both non-copyable

Region-based isolation, which is what makes this usable

SE-0414 taught the compiler to track regions of values that can reach each other. A freshly built, non-Sendable value that nothing else references may be sent into another domain, because the compiler can prove the sender no longer uses it. This is why the following compiles even though Report is not Sendable:

func build() -> Report { Report() } // not Sendable func run() async { let r = build() // a fresh region, unreachable elsewhere await consumer.take(r) // OK: the region is transferred // print(r) <- error: 'r' used after being transferred }

The migration path that actually works

1. stay in Swift 5 modeand turn on -strict-concurrency=complete to see the warnings without breaking the build2. fix by modulethe language mode is per-module; move one at a time and leave the rest on 53. @preconcurrency importfor dependencies you do not control, so their un-audited types stop generating noise4. push isolation upmost warnings dissolve when a type becomes @MainActor rather than when it becomes Sendable5. 6.2 defaults last-default-isolation MainActor once the module is clean; then mark the genuinely concurrent parts @concurrent
The point is not to make everything Sendable. It is to make each piece of mutable state have one owner: an actor, the main actor, or a lock. Most of the diagnostics are telling you that a type currently has none.

Ownership and Move-Only Types

borrowing, consuming, ~Copyable

By default Swift parameters are borrowed for the duration of the call: no retain, no copy, and the caller keeps ownership. The explicit spellings exist for the cases where the default costs a retain/release pair, and for types that cannot be copied at all.

ConventionCallee getsUse for
borrowing (default)read-only access; caller keeps the valuealmost everything
consumingownership; the caller may not use the value afterwardsan initialiser or a builder that stores its argument — saves a retain
inoutexclusive read-write access, written back on returnin-place mutation
sendingownership and the right to cross an isolation boundaryhanding a fresh object to an actor
struct FileHandle: ~Copyable { // move-only: no implicit copies exist private let fd: Int32 init(_ path: String) throws { … } consuming func close() { _close(fd) } // consuming: you cannot use it after deinit { _close(fd) } // structs get a deinit once ~Copyable } func use(_ h: borrowing FileHandle) { … } // read without taking ownership let h = try FileHandle("/tmp/x") use(h) h.close() // consumes h; a later use is a compile error, not a runtime bug
~Copyable is a suppressed constraint, not a new kind of type. Every generic parameter carries an implicit : Copyable; writing <T: ~Copyable> removes it, so the generic accepts both. This is the same trick as ~Escapable and it is why the feature could be added without splitting the language.

The operators and what they do

consume xend x's lifetime here and hand the value on; using x afterwards is an errorcopy xforce a copy where the compiler would otherwise move — occasionally needed to satisfy exclusivitydiscard selfinside a consuming method of a ~Copyable type: end the value without running deinit_read / _modify accessorsunderscored coroutine accessors: yield a borrow instead of returning a copy. What makes d[k]?.append() in-place@lifetime / dependsOnthe lifetime-dependency annotations that let Span be safe; still evolving

Exclusivity, enforced

Swift guarantees that a variable is not read while it is being modified. Within a function that is checked at compile time; across function boundaries and for class properties and globals it is checked at run time, and a violation traps with "Simultaneous accesses to 0x…, but modification requires exclusive access".

swap(&a[i], &a[j])traps if i == j; a.swapAt(i, j) is the safe form-enforce-exclusivity=uncheckedremoves the dynamic checks. Measure first; the checks are usually cheapInlineArray<N, T>(6.2) a fixed-size array stored inline — no heap buffer, no COW check, no retainSpan<T> / RawSpan(6.2) a safe, non-escapable view over contiguous memory: the bounds-checked replacement for passing around an UnsafeBufferPointer

Strings

a collection of grapheme clusters

String is a collection of Characters, and a Character is an extended grapheme cluster — what a reader calls a character, which may be many Unicode scalars and many bytes. Everything awkward about Swift strings follows from taking that seriously.

let s = "café🇬🇧x" s.count // 6 -- grapheme clusters s.unicodeScalars.count// 7 -- the flag is two regional-indicator scalars s.utf16.count // 9 -- what NSString and Cocoa APIs count s.utf8.count // 14 -- what the storage actually holds "é" == "e\u{301}" // true: canonical equivalence, compared by value not by bytes "é".utf8.count // 2 but the byte counts differ: 2 vs 3
String has no integer subscript, and that is deliberate. Finding the nth character means walking the UTF-8 from a known index, so an s[i] that looked O(1) would be O(n) and every loop over it O(n²). Index by String.Index, or work in a view whose element really is fixed-width.
s.index(s.startIndex, offsetBy: 3)O(n) — hoist it out of a loops.firstIndex(of: "f")String.Index?s[i..<j]→ a Substring, sharing the parent's storageString(s[range])copy out — do this before storing, or the whole parent stays alives.utf8[…]the view for byte work; also withUTF8 { buf in … } for a contiguous bufferfor ch in sgrapheme-breaking as it goes; the fastest correct way to walk a string

Storage, and the one number worth knowing

A String is 16 bytes. A string of 15 UTF-8 bytes or fewer is stored inline in those two words — no allocation, no refcount, no COW check. Beyond that it points at a heap buffer, or at a bridged NSString, or at a literal in the binary. This is why short-string work in Swift is much faster than the allocation-per-string mental model suggests.

Substring is 32 bytesa String plus a range. It keeps the entire parent buffer alive — slice a 10 MB file, keep one line, and you have kept 10 MBs.reserveCapacity(n)worth it when appending in a loops += / s.appendamortised O(1); building with + in a reduce is the classic accidental O(n²)"\(x)"string interpolation is a result builder: ExpressibleByStringInterpolation lets you type-check your own#"raw \n string"#raw strings: no escapes. Extend with more #s"""multi-line"""indentation of the closing delimiter is stripped from every line
Comparing strings compares Unicode canonical equivalence, not bytes. Two strings can be == and have different utf8 counts, and a Set<String> will deduplicate them. If you need byte identity — a hash, a protocol frame, a filename on a case-sensitive volume — compare the utf8 views, not the strings.

Collections

complexity, and where the copies happen

The protocol hierarchy is the API contract, and each step adds a guarantee: Sequence (iterate once, possibly destructively) → Collection (multi-pass, indexed, count) → BidirectionalCollection (walk backwards) → RandomAccessCollection (O(1) index offset). A generic algorithm should ask for the weakest one that works.

OperationArraySet / DictionaryString
subscript by index/keyO(1)O(1) averageO(1) by Index; there is no integer index
append / insert at endamortised O(1)O(1) averageamortised O(1)
insert / remove at frontO(n)O(n)
containsO(n)O(1) averageO(n)
countO(1)O(1)O(n) — it counts grapheme clusters
sortO(n log n), introsort, not stable
copy on assignmentO(1) until written — then O(n)samesame
A slice keeps the parent’s indices, and the parent’s storage. let s = arr[2...] has startIndex == 2, so s[0] traps with Index out of bounds — verified. Use s.first, s.startIndex, or Array(s) when you want a fresh 0-based value. The same aliasing keeps a big array alive behind a small slice.

Dictionary and Set, in practice

d[k, default: 0] += 1the idiomatic counter: one lookup, in-place, no double hashingd.updateValue(v, forKey: k)returns the old value; d[k] = v discards itDictionary(grouping:by:)the histogram/bucketing one-linerDictionary(uniqueKeysWithValues:)traps on a duplicate; the uniquingKeysWith: form takes a merge closured.keys / d.valueslazy views, not arrays; d.values is mutable in placeordering is unspecifiedand differs between runs — the hash seed is per-process. Never depend on iteration order; sort explicitlyHashable via hash(into:)feed the same fields you compare in ==, or the invariant breaks and lookups silently miss

Avoiding the copies

a.reserveCapacity(n)one allocation instead of log n reallocationsreduce(into:)the accumulator is inout, so building an array or dictionary does not copy each step. reduce with + doeslazyxs.lazy.filter{}.map{} fuses the passes and allocates nothing intermediate; drop the lazy and each stage builds an arraya.withUnsafeBufferPointer { }a raw view for the hot loopContiguousArray<T>an array that can never be bridged to NSArray; for classes and objc types, faster element accessa.removeAll(keepingCapacity: true)reuse the buffer across iterationsInlineArray<4, Int>(6.2) fixed size, stored inline — no heap, no uniqueness check at all

Wrappers, Builders and Macros

three ways to write code that writes code

Swift has three distinct metaprogramming mechanisms and they are often confused. All three are compile-time, all three are type-checked after expansion, and none of them is a text substitution.

Property wrappers — storage with behaviour

@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) } } var projectedValue: Bool { value == range.upperBound } // reached via $x init(wrappedValue: T, _ r: ClosedRange<T>) { range = r; value = … } } struct Volume { @Clamped(0...11) var level = 5 } // the compiler rewrites `level` into a stored `_level: Clamped<Int>` plus a // computed `level` forwarding to wrappedValue, and `$level` -> projectedValue

Result builders — a DSL from a series of statements

A @resultBuilder type turns the statements of a closure into calls on itself: buildBlock, buildOptional, buildEither, buildArray, buildExpression, buildFinalResult. That is the whole of how SwiftUI’s body works — an if in a view body becomes buildEither, which is also why the two branches must produce compatible types and why a for loop needs ForEach rather than the language’s own loop.

Macros — the 5.9 mechanism, and the only one that adds declarations

RoleExpands toExample
@freestanding(expression)an expression#URL("https://…"), checked at compile time
@freestanding(declaration)one or more declarations#warning-style code generation
@attached(member)new members inside a type@Observable adding its storage
@attached(memberAttribute)attributes on existing membersmarking every property observable
@attached(peer)declarations beside the targetgenerating a completion-handler twin of an async func
@attached(accessor)get/set on a stored propertyturning storage into a computed proxy
@attached(extension)a conformance and its membersadding : Codable and its implementation
macro impl is a separate moduleit links SwiftSyntax and runs in the compiler's process space as a plugin executable, so a macro-using package builds SwiftSyntax first — the reason macro adoption costs build timeswiftc -dump-macro-expansionssee exactly what was generated; in Xcode, right-click > Expand Macro#externalMacro(module:type:)the declaration side that points at the implementation typehygienemacro-introduced names cannot capture names at the use site unless explicitly declared in names:@Observablethe Observation module's macro: replaces ObservableObject + @Published with per-property change tracking, so a SwiftUI view redraws only for the properties it read
A macro cannot see anything but the syntax it is attached to. No type information, no other files, no name lookup. That is what keeps expansion parallel and cacheable, and it is why macros that "just need the type of this property" cannot be written the obvious way.

Objective-C and C Interop

what bridges, and what it costs

Swift, Objective-C and C share a process, an ARC runtime and a calling convention, but not a type system. The importer does the work, and knowing where it inserts a conversion is most of the debugging.

SwiftObjective-C / CBridging cost
StringNSString *lazy: often a wrapper, but UTF-16 conversion when the encodings differ
Array, Dictionary, SetNSArray etc.O(1) wrap in, O(n) verify out; ContiguousArray opts out entirely
IntNSIntegerfree
Int?, String?nullable pointerfree — if the header is audited
an un-audited headeranythingeverything imports as T!, so nil-safety is on trust
throwsNSError ** + BOOL/nil returnthe importer rewrites the last parameter away
a closurea blockfree; blocks are already ARC objects
structC structfree when layout-compatible; imported as-is
NS_ASSUME_NONNULL_BEGIN / _ENDthe audit macro in the header; without it, every pointer imports implicitly-unwrappedNS_SWIFT_NAME(foo(bar:))rename on importNS_REFINED_FOR_SWIFTimport as __foo so you can wrap it in a nicer Swift APINS_SWIFT_SENDABLE / NS_SWIFT_UI_ACTORannotate concurrency across the bridge — how a framework declares its Sendability@objc / @objcMembersexpose to the runtime; needed for selectors, KVO and IB outlets@objc dynamicroute through objc_msgSend so it can be swizzled or observed@nonobjchide an overload from the runtime when the mapping is ambiguousUnmanaged<T>manual retain/release for a Core Foundation object across an unannotated boundary

C, and now C++

module.modulemapwhat makes a C library importable as a Swift module; SwiftPM writes one for a system-library targetUnsafePointer<T>a C const T *; UnsafeMutablePointer for T *UnsafeRawPointerconst void *; OpaquePointer for a handle whose type you do not modelwithUnsafePointer(to:)a scoped C pointer to a Swift value — valid only inside the closurestrdup / freeif C allocated it, C's free must release it; Swift's deallocator is not the same allocator-cxx-interoperability-mode=defaultC++ interop (5.9): C++ classes import as Swift structs, std::string and std::vector bridge, and Swift types can be exposed back to C++
A C pointer obtained inside withUnsafePointer is dangling the moment the closure returns. The same applies to array.withUnsafeBufferPointer and to passing a Swift String to a C function that stores the pointer. If the callee keeps it, you must allocate memory that you own and free it yourself.

Unsafe Swift

the pointer family, and the new audit

Every unsafe construct in Swift is spelled with Unsafe in its name. That is a deliberate design decision: the risky surface is grep-able, and since 6.2 it is compiler-checkable too.

TypeC equivalentNotes
UnsafePointer<T>const T *typed, bound memory
UnsafeMutablePointer<T>T *you own initialize/deinitialize
UnsafeRawPointerconst void *untyped bytes; load(as:) requires correct alignment
UnsafeBufferPointer<T>pointer + counta RandomAccessCollection, so algorithms work on it
OpaquePointera handleno element type; convert with init(_:)
Unmanaged<T>a +0/+1 referencetakeRetainedValue vs takeUnretainedValue is the whole API
Span<T> (6.2)the safe replacement: bounds-checked, non-escapable, no lifetime hazard
// the three rules of allocate let p = UnsafeMutablePointer<Int>.allocate(capacity: 8) p.initialize(repeating: 0, count: 8) // 1. initialise before reading defer { p.deinitialize(count: 8) // 2. deinitialise before deallocating p.deallocate() } // 3. deallocate exactly once
withUnsafeBytes(of: v) { buf in … }read a value's bytes; scopeda.withUnsafeMutableBufferPointer { }the in-place hot loop over an array; do not capture the pointer or touch a insidep.assumingMemoryBound(to:)assert the memory is already bound to that type — no rebinding, UB if wrongp.bindMemory(to:capacity:)actually change the binding; strict aliasing rules applyunsafeBitCast(x, to:)a reinterpret between same-sized types. Almost always the wrong tool — look for a real conversion firstunsafeDowncast(o, to:)an unchecked as!; only in a profile-driven hot path

Strict memory safety (6.2)

-strict-memory-safety makes every use of an unsafe construct produce a warning tagged [#StrictMemorySafety], which you silence per-use with unsafe at the expression or @safe/@unsafe on a declaration. It is not a new restriction — it is an audit trail, so a reviewer can see every place the language’s guarantees were opted out of. Verified: a struct with an UnsafeMutableRawPointer member warns "has storage involving unsafe types".

Exclusivity is checked at run time for globals and class properties, and a violation traps with "Simultaneous accesses to 0x…". -enforce-exclusivity=unchecked removes those checks — and with them the guarantee that inout aliasing is caught rather than silently corrupting. Measure before you reach for it.

The Compiler and the Build

SwiftPM, modules and ABI

The unit of compilation is the module, and the unit of visibility is the module too. There are no headers: the interface is derived from the source, emitted into a .swiftmodule (binary, compiler-version-specific) or a .swiftinterface (textual, stable, what library evolution ships).

Access control, which is about modules, not files

privatethe enclosing declaration and its extensions in the same filefileprivatethe fileinternalthe module — the default, and the right answer most of the timepackage(5.9) every module in the same SwiftPM package: the level that was missing for multi-module packagespublicother modules may use it; they may not subclass or override itopenpublic, plus subclassable and overridable outside the module

Optimisation modes, and what actually changes

FlagEffect
-Ononedebug. No specialisation, no devirtualisation, no ARC elision — expect 5–100× on numeric code
-Orelease. Whole-module by default in SwiftPM release builds: specialises generics, devirtualises internal classes, elides retains
-Osizeas -O but preferring size — less inlining and specialisation
-Ouncheckedremoves precondition, bounds and overflow checks. A trap becomes undefined behaviour. Rarely worth it
-wmo / -enable-batch-modewhole-module vs per-file: the trade is optimisation quality against incremental build time
-cross-module-optimizationspecialise across modules in one package without annotating each function
-enable-library-evolutionresilient ABI: layouts become opaque, everything indirects, clients need not recompile. Required for a binary framework, costly for a local one
// Package.swift -- the parts that matter let package = Package( name: "Tool", platforms: [.macOS(.v26)], products: [.executable(name: "tool", targets: ["Tool"])], targets: [ .executableTarget(name: "Tool", dependencies: ["Core"], swiftSettings: [.swiftLanguageMode(.v6), .enableUpcomingFeature("ExistentialAny"), .defaultIsolation(MainActor.self)]), .target(name: "Core"), .testTarget(name: "CoreTests", dependencies: ["Core"]), ])
swift build -c releasethe release build; -Xswiftc -O to pass a flag straight throughswift test / swift runXCTest, or Swift Testing's @Test and #expect macros (5.10+)swift package resolve / updatePackage.resolved is the lockfile — commit it for an executableswiftc -emit-sil / -emit-ir / -Ssee what the optimiser did; SIL is where specialisation and ARC elision are visibleswiftc -dump-macro-expansionsthe generated source for every macroswift-demanglereal symbols read plainly: $s8dispatch1GV5helloyyF is dispatch.G.hello() -> ()swift-format / sourcekit-lspboth ship inside the toolchain — no separate install
Name mangling encodes the whole signature, including the witness table. The symbol $s8dispatch1GVAA7GreeterA2aDP5helloyyFTW demangles to "protocol witness for Greeter.hello() in conformance G : Greeter" — the TW suffix is literally the witness-table thunk from the dispatch card, visible in nm.

Performance

where the time actually goes

Swift performance work is almost never about algorithms in the loop body. It is about four things: retain/release traffic, boxing, failed specialisation and accidental copies. In that order.

The catalogue, worst first

SymptomCauseFix
swift_retain/release at the top of a profileclass references copied in a hot loop; an array of classesfinal; move to structs; ContiguousArray; hoist the reference out of the loop
swift_allocObject in a loopan existential larger than 3 words being copied, or an escaping closure per iterationsome P instead of any P; hoist the closure
generic code slow only when called from another modulespecialisation stopped at the boundary@inlinable, or -cross-module-optimization
swift_dynamicCastas? in a loop, or is over a class hierarchyan enum, or a protocol requirement, instead of a type test
array operations dominatingCOW copying because a second reference existsfind the stray copy; isKnownUniquelyReferenced in a debug build to confirm
string work dominatingcount, index(offsetBy:) or + inside a loopwork in utf8; hoist indices; reserveCapacity; append not +
everything slow, uniformlyyou profiled a -Onone buildalways measure -c release. This is the single commonest mistake
xcrun xctrace / InstrumentsTime Profiler for wall time; Allocations with "Swift Retain/Release" for ARC trafficswift test -c release --filter Perfmeasure release, alwaysXCTMeasureOptions / @Test with a clocka repeatable benchmark rather than one stopwatch runswiftc -O -emit-sil | grep specializeddid the generic actually specialise?ContinuousClock / SuspendingClocklet t = ContinuousClock().measure { … } — monotonic, and the right clock for a benchmarkos_signpostmark regions so Instruments can attribute them
The cheapest three wins, in order. Mark classes final. Replace any P with some P in parameter and return position. Build with -c release before believing any measurement. In most Swift codebases those three account for more than every micro-optimisation put together.

What is genuinely fast in Swift

Structs of trivial fields in a ContiguousArray, iterated with for and processed by non-escaping closures, compile to essentially the same code a C loop would: the closure inlines, the bounds check hoists, and there is no ARC at all because nothing is a reference. Numeric Swift beaten into that shape is competitive with C, and the Span/InlineArray types added in 6.2 exist to make that shape expressible without dropping to Unsafe pointers.

Traps, by Symptom

the ones that cost an afternoon

Compile-time

MessageWhat it really means
"The compiler is unable to type-check this expression in reasonable time"a long literal array or a chained arithmetic expression with mixed numeric types. Split it and annotate one type
"Escaping closure captures mutating self"a struct method storing a closure over self. Make the type a class, or capture a copy
"Cannot convert Int to CGFloat"Swift has no implicit numeric conversions, deliberately. Write CGFloat(n)
"Missing argument label"argument labels are part of the name; _ in the declaration removes one
"Type does not conform to Sendable" at a Task { }a captured non-Sendable value. Usually the fix is isolation, not @unchecked
"Main actor-isolated property cannot be referenced from a nonisolated context"you crossed an actor boundary synchronously. await, or mark the caller @MainActor
"Initializer requirement can only be satisfied by a required initializer"a non-final class conforming to a protocol with an init. Add required, or make the class final
"Generic parameter T could not be inferred"a return-position-only generic. Annotate the result, or pass a metatype

Run-time

CrashCause
"Unexpectedly found nil while unwrapping an Optional"a ! or an implicitly-unwrapped optional — often an unconnected IB outlet
"Index out of range"an integer index into a slice, which keeps the parent’s indices. Use startIndex or copy with Array(s)
"Fatal error: Not enough bits to represent the passed value"an IntInt32 conversion. Use Int32(exactly:) or truncatingIfNeeded:
arithmetic overflow trapSwift traps rather than wrapping. &+ &- &* wrap explicitly; addingReportingOverflow reports
"Simultaneous accesses to 0x…"exclusivity: the same variable inout twice, or a global mutated during its own access
a deadlock at launcha synchronous wait on the main thread for something that needs the main actor. There is no reentrant lock to save you
"Task was cancelled" that nobody handledcancellation is cooperative — the parent scope ended while the child was still running
a leak, in Instruments > Leaksa retain cycle: a closure stored on self capturing self, or two objects both strong

Quietly wrong

dict["k"]?.count = 5optional chaining on assignment: does nothing at all when the key is absenttry? f()the error is gone, not handledSet<String> deduplicating "é"canonical equivalence: two different byte sequences are the same Stringiteration order of a Dictionaryunspecified, and different on every run — a test that passes locally and fails in CIsort() is not stableequal elements may be reordered; sort by a tiebreaker if it mattersa method defined in a protocol extensionstatically dispatched: your override is ignored through any Pa struct captured by a closurethe closure gets a copy at capture time unless the capture is inout-adjacentstate after an await inside an actorreentrancy: another call may have run. Re-check invariants, do not cache across the suspensionSubstring held long-termkeeps the entire parent string alive; String(sub) to copy outlazy var from two threadsnot atomic — unlike a global or static let, which are

Language & Library Index

Every keyword, attribute, type, method and flag worth remembering, grouped by what it does, dotted by the Swift it needs — type in the filter box, or press /

Keywords & Modifiers

58

Declarations

funca function or method
var / letmutable / immutable binding
structa value type
classa reference type
actora reference type with an isolation domain
enuma closed set of cases
protocola set of requirements
extensionadd members to an existing type
typealiasa name for a type
associatedtypea type the conformer chooses
init / deinitinitialiser / deinitialiser
subscriptan indexed accessor
operator / precedencegroupdeclare a custom operator
macrodeclare a macro
importbring in a module

Access control

privatethe declaration and its extensions in this file
fileprivatethis file
internalthis module — the default
packageevery module in this SwiftPM package
publicother modules may use it
openpublic, and subclassable outside the module
finalno subclassing or overriding
overridereplaces a superclass member
requiredevery subclass must implement this init
dynamicdispatch through the Objective-C runtime
static / classtype-level member

Mutation & ownership

mutatingthis method may write self
nonmutatingthis setter does not write storage
inoutcopy in, copy back out
borrowingread-only access, caller keeps ownership
consumingtakes ownership; caller may not use it after
consume xend this value’s lifetime here
copy xforce a copy the compiler would elide
discard selfend a ~Copyable value without running deinit
~Copyable / ~Escapablesuppress an implicit constraint
weak / unownednon-owning references
weak letan immutable weak reference
lazy varinitialised on first read

Concurrency & isolation

async / awaita suspendable function, and its call
async leta child task started immediately
nonisolatedopt a member out of its type’s isolation
nonisolated(unsafe)“I checked this by hand”
nonisolated(nonsending)run in the caller’s isolation
isolated a: SomeActoran isolated parameter
sendingtransfer, do not share
#isolationthe caller’s isolation, as a value

Control flow & patterns

guard … elseearly exit, binding for the rest of the scope
if let xshorthand unwrap
if case / guard casea one-case pattern match
for case let .x(v) in xsfilter and bind in a loop
switch / case / defaultexhaustive multiway branch
@unknown defaulthandle future cases, keep the warning
wherea constraint or a pattern guard
repeat … whiledo-while
deferrun on every exit from this scope
break / continue / fallthroughloop and switch control
Self vs selfthe type vs the instance
some / anyopaque vs existential type

Operators

34

Arithmetic & overflow

+ - * / %arithmetic; % is remainder, not modulo
&+ &- &*wrapping arithmetic
addingReportingOverflowvalue plus an overflow flag
<< >>shift; smart for signed types
& | ^ ~bitwise and, or, xor, not
isMultiple(of:)divisibility without %
&/ &%no longer exist

Comparison & identity

== !=value equality
< <= > >=ordering
=== !==reference identity
~=pattern match
is / as / as? / as!type test and casts

Optionals & ranges

?? nil-coalescing
?.optional chaining
!force unwrap
a...b / a..<bclosed and half-open ranges
x?.y = zassignment through a chain
a ? b : cternary conditional

Assignment & special

= += -= *= /= %=assignment and compound forms
&= |= ^= <<= >>=compound bitwise
&xinout argument marker
$xthe projected value
\Type.patha key path
#selector / #keyPathObjective-C selector and key path literals
#available / #unavailableruntime OS version check
#file #line #functionsource location literals

Declaring your own

infix operator <~> : Groupdeclare an operator
precedencegroupwhere it binds
prefix / postfix funcunary operators
AdditionPrecedence …the built-in groups
ExpressibleBy…Literalmake a literal build your type
callAsFunctionmake an instance callable
@dynamicMemberLookuparbitrary .member
@dynamicCallablearbitrary instance(…)

Attributes

41

Optimisation & ABI

@inlinablepublish the body for cross-module inlining
@usableFromInlineinternal, but referenceable from @inlinable
@frozenthe layout or case list will never change
@discardableResultno warning when the result is ignored
@available(macOS 26, *)platform availability
@mainthe program entry point
@warn_unqualified_accessrequire Type.member spelling
@_spi(Name)system-programming interface

Closures & parameters

@escapingthe closure may outlive the call
@autoclosurewrap the argument expression in a closure
@Sendablesafe to hand to another isolation domain
@convention(c)a bare C function pointer
@convention(block)an Objective-C block

Concurrency

@MainActorisolated to the main actor
@globalActordeclare a process-wide actor
@preconcurrencytreat the other side as un-audited
@unchecked SendableI serialise this myself
@retroactiveacknowledge a conformance you do not own
@concurrentrun on the shared pool, off the caller’s actor
@TaskLocaltask-local storage

Metaprogramming

@propertyWrapperstorage with behaviour
@resultBuildera statement-level DSL
@attached(member)a macro adding members
@freestanding(expression)a macro used as #name(…)
@Observableper-property change tracking
@Test / @SuiteSwift Testing

Objective-C bridge

@objcexpose to the Objective-C runtime
@objc(name)expose under a different name
@objcMembersexpose every member
@nonobjchide from the runtime
@NSManagedCore Data supplies the storage
@NSCopyingcopy on assignment
@IBOutlet / @IBActionInterface Builder connections
@UIApplicationMain / @NSApplicationMainsuperseded by @main

Safety & diagnostics

@unsafe / @safemark a declaration for the safety audit
@testable importsee internal declarations from tests
#warning / #errorcompile-time diagnostics
#if / #elseif / #endifconditional compilation
#if hasFeature(X)is an upcoming feature enabled
@_exported importreexport a module to your clients
@_implementationOnly importreplaced by internal import

Types & Numerics

39

Fundamental

Int / UIntword-sized integer
Int8 … Int64, UInt8 …sized integers
Double / Float / Float16IEEE 754 binary64 / 32 / 16
Booltrue or false
Characterone extended grapheme cluster
String / Substringtext
StaticStringa literal, guaranteed
Void / ()the empty tuple
Neverthe uninhabited type
Any / AnyObject / AnyClassthe erased types
Optional<T>a two-case enum
Result<S, F: Error>a success or a failure, as a value

Numeric protocols

Numeric / SignedNumericthe arithmetic protocols
BinaryIntegerany integer type
FixedWidthIntegerhas bitWidth, max, min
BinaryFloatingPointany float type
Strideablecan form a range with a stride
AdditiveArithmetic+ - and .zero

Integer operations

.max / .minrange bounds
bitWidth / nonzeroBitCountwidth and popcount
quotientAndRemainder(dividingBy:)both at once
dividingFullWidth128-bit by 64-bit division
Int(exactly:)nil rather than a trap
Int(truncatingIfNeeded:)take the low bits
Int.random(in: 1...6)uniform random
x.magnitudeabsolute value, unsigned

Floating point

.pi / .infinity / .nannamed constants
isNaN / isFinite / isNormalclassification
rounded(.up)explicit rounding rule
truncatingRemainder(dividingBy:)fmod
x.ulp / nextUp / nextDownthe neighbouring representable values
Decimalbase-10 fixed point (Foundation)

Ranges, time & misc

Range / ClosedRangea..<b / a...b
PartialRangeFrom / UpTo / Throughthe one-sided forms
stride(from:to:by:)an arithmetic sequence
Durationa time span, attosecond precision
ContinuousClock / SuspendingClockmonotonic clocks
ObjectIdentifiera hashable identity for a class
KeyPath / WritableKeyPatha reference to a property

Optionals, Errors & Control Flow

26

Optional API

x ?? defaultnil-coalescing
x.map { }transform the payload
x.flatMap { }transform, flattening one level
if let / guard let / while letunwrap into a binding
x?.method()call only if non-nil
Optional(x)!force unwrap
compactMapmap and drop the nils
T!implicitly unwrapped optional

Throwing

throws / throwdeclare and raise
throws(E)typed throws
rethrowsthrows only if a closure argument does
try / try? / try!call, or discard, or trap
do / catchhandle by pattern
Error / LocalizedErrorthe protocols
CancellationErrora cancelled task
defer { }cleanup on every exit

Trapping

assert / assertionFailuredebug-only check
precondition / preconditionFailurerelease check
fatalError("…")unconditional stop
Never as a return typethis call does not return
exit(0) / abort()the C exits

Compile-time control

#if DEBUGconditional compilation
#if canImport(UIKit)is a module available
#if swift(>=6.0) / compiler(>=6)language mode vs compiler version
#if os(macOS) / arch(arm64)platform tests
#sourceLocationoverride the reported file and line

Collections

34

Array

[T] / Array<T>contiguous, growable, COW
ContiguousArray<T>never bridges to NSArray
ArraySlice<T>a view sharing the parent’s storage
InlineArray<N, T>fixed size, stored inline
append / append(contentsOf:)amortised O(1)
insert(_:at:) / remove(at:)O(n)
removeAll(keepingCapacity:)empty but keep the buffer
reserveCapacity(n)one allocation instead of log n
withUnsafeBufferPointera raw view for a hot loop
swapAt(i, j)exchange two elements
replaceSubrange(_:with:)splice
a[i...] / a[..<j]slice with a one-sided range

Dictionary & Set

[K: V] / Dictionaryhashed, O(1) average
d[k, default: 0] += 1in-place default
updateValue(_:forKey:)set and return the old value
Dictionary(grouping:by:)bucket a sequence
Dictionary(uniqueKeysWithValues:)build from pairs
merge(_:uniquingKeysWith:)combine two dictionaries
mapValues / compactMapValuestransform values, keep keys
d.keys / d.valueslazy views
Set<T>unordered, hashed, unique
union / intersection / subtractingset algebra
isSubset(of:) / isDisjoint(with:)set relations
insert(_:)returns (inserted:memberAfterInsert:)

The protocol hierarchy

Sequenceiterate once
Collectionmulti-pass, indexed
BidirectionalCollectionwalk backwards
RandomAccessCollectionO(1) index offset
MutableCollectionassign through a subscript
RangeReplaceableCollectionchange the length
IteratorProtocolmutating func next() -> Element?
AnyIterator / AnySequencetype-erased wrappers
sequence(first:next:)unfold a sequence from a closure
Slice<C>the default slice type

Algorithms

33

Transforming

map / compactMap / flatMaptransform, drop nils, flatten
filterkeep the matching elements
reduce(_:_:)fold to a single value
reduce(into:_:)fold with an inout accumulator
lazyfuse the pipeline
enumerated()pairs of (offset, element)
zip(a, b)pairwise, stopping at the shorter
joined(separator:)concatenate sequences
split(separator:maxSplits:omittingEmptySubsequences:)break into slices
chunked / windowsnot in the stdlib

Searching & testing

first(where:) / last(where:)the first match
firstIndex(of:) / firstIndex(where:)an index, or nil
contains(_:) / contains(where:)membership
allSatisfy(_:)every element matches
count(where:)count the matches
min() / max() / min(by:)extremes
elementsEqual / starts(with:)compare two sequences
firstRange(of:) / ranges(of:)subsequence search

Ordering & rearranging

sorted() / sorted(by:)a sorted copy
sort() / sort(by:)sort in place
reversed()O(1) lazy view on a BidirectionalCollection
shuffled() / shuffle()random permutation
partition(by:)in-place split, returns the pivot index
swapAt(_:_:)exchange two elements
randomElement()one element, uniformly
indicesthe valid index range

Slicing & iteration

prefix(n) / suffix(n)up to n elements
prefix(while:) / drop(while:)take or skip a run
dropFirst(n) / dropLast(n)skip from an end
forEachiterate with a closure
stride(from:to:by:)an arithmetic sequence
makeIterator() / next()manual iteration
withContiguousStorageIfAvailablefast path for a contiguous buffer

Strings & Text

26

The views

s.countgrapheme clusters — O(n)
s.unicodeScalarsUnicode code points
s.utf16UTF-16 code units
s.utf8the storage itself
s.withUTF8 { buf in }a contiguous UTF-8 buffer
String.Indexan opaque position
s.index(_:offsetBy:)move an index
s.indicesevery character position

Building & slicing

"\(x)"interpolation
#"raw \n"#raw string
"""…"""multi-line literal
s += / s.appendamortised O(1)
s.reserveCapacity(n)preallocate
s[i..<j]a Substring
s.split(separator:)array of Substrings
s.trimmingPrefix / trimmingCharacters(in:)trim
s.replacing(_:with:)substring replacement

Comparing & formatting

s1 == s2Unicode canonical equivalence
localizedStandardCompareFinder-style ordering
caseInsensitiveCompareASCII-cheap comparison
uppercased() / lowercased()full Unicode case mapping
hasPrefix / hasSuffixgrapheme-aware tests
x.formatted() / .formatted(.number)modern formatting
Date.now.formatted(date:time:)date formatting
String(format:)printf-style (Foundation)
String(describing:) / String(reflecting:)CustomStringConvertible / Debug

Standard-Library Protocols

22

Equality & ordering

Equatable==
Hashablehash(into:)
Comparable<
Identifiablevar id: ID

Conversion & description

CustomStringConvertiblevar description
CustomDebugStringConvertiblevar debugDescription
LosslessStringConvertibleinit?(String)
RawRepresentablerawValue and init?(rawValue:)
Codable / Encodable / Decodableserialisation
CodingKeythe key names for Codable
ExpressibleByStringLiteral …literal conformances

Concurrency & ownership

Sendablesafe to cross an isolation domain
@unchecked Sendableasserted, not checked
Copyablethe implicit constraint on every generic parameter
Escapablemay outlive its source
AnyObjecta class constraint

Sequences & numerics

Sequence / Collectionthe iteration hierarchy
AsyncSequence / AsyncIteratorProtocolfor await
Numeric / BinaryInteger / FixedWidthIntegerthe arithmetic protocols
Strideableranges with a step
Hasherthe hashing primitive
RandomNumberGeneratorpluggable randomness

Concurrency

34

Tasks

Task { }unstructured task
Task.detached { }inherits nothing
async let x = f()a child task, started now
await t.value / t.resultwait for a Task
t.cancel()request cancellation
Task.isCancelledthe flag
try Task.checkCancellation()throw if cancelled
withTaskCancellationHandler(operation:onCancel:)bridge cancellation out
try await Task.sleep(for: .seconds(1))cancellation-aware sleep
await Task.yield()give the executor a turn
Task.currentPrioritythe priority in effect
@TaskLocal static var xtask-local value

Groups

withTaskGroup(of:returning:)dynamic fan-out
withThrowingTaskGroupthe throwing form
withDiscardingTaskGroupresults discarded as they finish
group.addTask { }add a child
for try await x in groupresults in completion order
group.cancelAll() / waitForAll()group control

Actors & isolation

actor / distributed actoran isolation domain
@MainActorthe main-thread global actor
MainActor.run { }hop for a block
MainActor.assumeIsolated { }assert we are already here
nonisolated / nonisolated(unsafe)opt out of isolation
@concurrentrun off the caller’s actor
unownedExecutorpin an actor to an executor
GlobalActor protocoldeclare your own

Streams, continuations & locks

AsyncStream / AsyncThrowingStreamcallbacks to for-await
AsyncStream.makeStream()stream plus continuation
withCheckedContinuationbridge a callback API in
withCheckedThrowingContinuationthe throwing form
withUnsafeContinuationno bookkeeping
Mutex<T>a real lock (Synchronization)
Atomic<T>lock-free atomics (Synchronization)
AsyncSequence.map / filter / prefixlazy async pipelines

Generics & Globals

30

Generic syntax

func f<T>(_ x: T) -> Ta generic function
where T: Equatable, T.Element == Uconstraint clause
some Pan opaque type
any Pan existential
protocol P<Element>a primary associated type
extension Array where Element: Numericconditional extension
func f<each T>(_ v: repeat each T)parameter packs
<T: ~Copyable>accept move-only types too
associatedtype E: Equatablea type the conformer picks
Selfthe conforming or current type
AnyHashabletype-erased Hashable
@inlinable / @usableFromInlinelet other modules specialise
ExistentialAnyan upcoming feature
typealias Pair<T> = (T, T)generic typealias

Global functions

print(_:separator:terminator:)write to stdout
debugPrint / dumpdebug representations
type(of: x)the dynamic type
swap(&a, &b)exchange two values
min / max / absthe numeric globals
zip(a, b)pairwise sequence
stride(from:to:by:)an arithmetic sequence
sequence(first:next:)unfold from a closure
repeatElement(_:count:)a repeated sequence
numericCast(_:)convert between integer types generically
withoutActuallyEscaping(_:do:)lend a non-escaping closure
readLine(strippingNewline:)read stdin
exit(_:) / abort()end the process
isKnownUniquelyReferenced(&x)COW uniqueness test
withUnsafeCurrentTask { }inspect the running task
fatalError / precondition / assertthe trapping family

Memory, Ownership & Unsafe

34

Layout & identity

MemoryLayout<T>.sizebytes the value occupies
MemoryLayout<T>.stridedistance between array elements
MemoryLayout<T>.alignmentrequired alignment
MemoryLayout.size(ofValue:)the dynamic type’s size
ObjectIdentifier(obj)a hashable identity
isKnownUniquelyReferenced(&x)am I the only owner
withExtendedLifetime(x) { }pin an object across a region
CFGetRetainCountdo not

References

weak var x: T?zeroing non-owning reference
weak let x: T?immutable weak reference
unowned let x: Tnon-owning, non-zeroing
unowned(unsafe)no check at all
[weak self] in guard let selfthe standard closure opening
[unowned self]when the closure cannot outlive self
let f = obj.methoda bound method reference

Ownership

borrowing / consumingparameter conventions
struct T: ~Copyablea move-only type
consume x / copy xthe ownership operators
discard selfend a value without deinit
Span<T> / RawSpana safe view over contiguous memory
InlineArray<N, T>fixed-size inline storage
inoutcopy in, copy back
-enforce-exclusivity=uncheckeddrop the dynamic checks

Unsafe pointers

UnsafePointer<T> / UnsafeMutablePointer<T>const T * / T *
UnsafeRawPointer / UnsafeMutableRawPointervoid *
UnsafeBufferPointer<T>pointer + count
OpaquePointeran untyped handle
Unmanaged<T>manual retain/release
.allocate(capacity:) / .deallocate()manual lifetime
withUnsafeBytes(of:) / withUnsafePointer(to:)scoped access to a value
bindMemory(to:capacity:)change the memory binding
unsafeBitCast(x, to:)reinterpret between same-sized types
unsafeDowncast(o, to:)unchecked as!
-strict-memory-safetyaudit every unsafe use

Foundation

34

Files & URLs

URL(filePath:) / URL(string:)file and network URLs
u.appending(path:)extend a URL
URLComponentsparse and build query strings
FileManager.defaultthe file system API
Data(contentsOf:) / write(to:)read and write a file
String(contentsOf:encoding:)read text
FileHandle / AsyncBytesstreaming
Bundle.mainresources beside the binary
ProcessInfo.processInfoenvironment and arguments
Process / Piperun a subprocess

Serialisation

JSONEncoder / JSONDecoderCodable to and from JSON
PropertyListEncoder / Decoderplists
dateDecodingStrategy = .iso8601date handling
CodingKeys / init(from:)customise the mapping
JSONSerializationuntyped JSON
Data.base64EncodedString()base64

Dates, text & measurement

Date.nowthe current instant
Calendar.currentcalendrical arithmetic
DateComponentsa broken-down date
ISO8601DateFormattermachine-readable dates
x.formatted(…)FormatStyle
Locale / TimeZonelocalisation context
Measurement<UnitLength>a value with a unit
Regex / /pattern/the regex literal
Regex<Output> and RegexBuildertyped captures
NSRegularExpressionthe older engine

Platform services

UserDefaults.standardsmall persistent settings
NotificationCenter.defaultbroadcast notifications
URLSession.sharednetworking
NSCachea purgeable cache
DispatchQueue / OperationQueuethe pre-async concurrency APIs
NSLock / NSRecursiveLockthe older locks
os.Logger / os_signpostunified logging
UUID / Data / IndexSetsmall value types

Wrappers, Macros & the App Surface

24

Property wrappers

@propertyWrapperdeclare one
@State / @BindingSwiftUI view-local state
@Environment(\.key)read from the environment
@Observableper-property change tracking
@ObservedObject / @Publishedthe pre-Observation pair
@AppStorage / @SceneStorageUserDefaults and scene state
@TaskLocaltask-local storage
@MainActornot a wrapper — a global actor

Result builders

@resultBuilderdeclare a DSL
@ViewBuilderSwiftUI’s builder
ForEachthe loop inside a builder
RegexBuildera regex as a builder

Macros

@attached(member) / (peer) / (accessor)attached macro roles
@freestanding(expression) / (declaration)freestanding roles
#externalMacro(module:type:)point at the implementation
swiftc -dump-macro-expansionssee the generated code
#expect / #requireSwift Testing assertions
#Predicatea typed predicate (Foundation)

App entry points

@mainthe entry point
App / Scene / WindowGroupthe SwiftUI app shape
View protocol / var bodythe SwiftUI unit
NSApplicationDelegateAdaptorreach AppKit from SwiftUI
main.swift vs @maintop-level code
ArgumentParsera CLI (swift-argument-parser)

Compiler, SwiftPM & Tools

37

swiftc

-O / -Onone / -Osizeoptimisation level
-Ouncheckeddrop preconditions and bounds checks
-swift-version 5 | 6language mode
-wmo / -enable-batch-modewhole-module vs per-file
-cross-module-optimizationspecialise across a package
-enable-library-evolutionresilient ABI
-enable-upcoming-feature Xtake one future change early
-default-isolation MainActormain-actor by default
-strict-memory-safetyaudit unsafe constructs
-strict-concurrency=completefull checking in Swift 5 mode
-emit-sil / -emit-ir / -Ssee what the compiler did
-dump-macro-expansionsexpanded macro source
-D FLAG / -Xcc / -Xlinkerpass through
-g / -gline-tables-onlydebug info
-warnings-as-errors / -suppress-warningsdiagnostic policy

SwiftPM

swift build -c releasebuild
swift run / swift testrun and test
swift package init --type executablenew package
swift package resolve / updatedependency graph
swift package clean / purge-cachereset
swift package dump-packagethe manifest as JSON
.swiftLanguageMode(.v6)per-target language mode
.enableUpcomingFeature("X")per-target feature
.defaultIsolation(MainActor.self)per-target isolation default
platforms: [.macOS(.v26)]minimum deployment target
products / targets / dependenciesthe manifest shape
Bundle.moduleresources in a SwiftPM target

Testing & debugging

@Test / @Suite / #expectSwift Testing
XCTAssertEqual / XCTUnwrapXCTest
measure { }a performance test
swift-demangleread a mangled symbol
lldb: po / p / vprint in the debugger
dump(x)a structural dump
Mirror(reflecting:)runtime reflection
Thread sanitizer / Address sanitizerruntime checks
Instruments: Time Profiler, Allocationswhere the time and the retains go
swift-format / sourcekit-lspformatting and editor support

Errors, by Symptom

28

It will not compile

unable to type-check in reasonable timesplit the expression
escaping closure captures mutating selfa struct storing a closure over self
cannot convert Int to CGFloatno implicit numeric conversions
missing argument labellabels are part of the name
does not conform to Sendablea value crossing an isolation boundary
main actor-isolated … from a nonisolated contexta synchronous actor hop
can only be satisfied by a required initializera non-final class conforming to a protocol with init
generic parameter could not be inferredreturn-position-only generic
referencing instance method requires Element == …a conditional conformance did not apply
ambiguous use of …overload resolution gave up

It crashed

unexpectedly found nil unwrapping an Optionala ! or an IUO
Index out of rangean integer index into a slice
Not enough bits to represent the passed valuea narrowing integer conversion
arithmetic overflowSwift traps rather than wrapping
Simultaneous accesses to 0x…exclusivity violation
Fatal error: Duplicate keysDictionary(uniqueKeysWithValues:)
EXC_BAD_ACCESS after an unowned readuse after free
a deadlock at launcha synchronous wait for the main actor

It is quietly wrong

dict["k"]?.count = 5a no-op when the key is absent
try? f()the error is gone, not handled
Set<String> deduplicating look-alikescanonical equivalence
Dictionary iteration order changedthe hash seed is per-process
sorted() reordered equal elementsintrosort is not stable
my protocol-extension override is ignoredit was never a requirement
state changed across an await inside an actoractor reentrancy
a Substring is holding megabytesslices retain the parent
lazy var initialised twicelazy is not atomic
benchmarks 50× slower than expectedyou measured -Onone