Keentune
Swift, oriented
12 chapters
·
about 17 min read
·
free
Two decisions shape nearly everything else in Swift. Most of what you define is a *value*: assigning it makes an independent copy, so nothing can change your data behind your back. And a thing that might be missing has a different type from a thing that is there, so the compiler makes you handle absence exactly where you wrote it. Anyone arriving from Java, Python or JavaScript mispredicts both. Learn them first; reference types, memory and concurrency are built on top.
Each chapter opens with the short version. Tap one to read the detail.
Structs copy, classes share
~2 min
A struct or enum is a value type: assigning one makes an independent copy. A class is a reference type: two names refer to one instance. That single split decides how mutation, let, equality and memory behave. Prefer structs; reach for a class only when you need one shared, identifiable object.
Optionals, and the absence you cannot forget
~2 min
Int? is a different type from Int, so missing values cannot slip past unhandled. nil is not a null pointer — it is the empty case of a two-case value. Bind with if let or guard let, fall back with ??, and read ! as an assertion that stops the program rather than a conversion.
Memory: deterministic, until two objects own each other
~2 min
Swift uses automatic reference counting (ARC), not a tracing garbage collector, so an object dies the instant its last strong reference goes — deterministically. Nothing sweeps up afterwards, which means two objects that hold each other strongly are never freed. weak and unowned are how you break that.
Closures capture the variable, not its value
~2 min
A closure captures a reference to the surrounding variable, so it sees changes made after it was created. A capture list [x] snapshots instead. Closures are reference types, so a stored one that captures self is the most common way to build a retain cycle by accident.
Protocols and extensions, and the dispatch trap
~2 min
Protocols describe capability, and extensions supply default implementations, which is how value types share behaviour without inheritance. The trap: a member declared *only* in a protocol extension is dispatched statically, so calling it through the protocol runs the default even when the concrete type defines its own.
Generics, some and any
~2 min
A type parameter carries a relationship the caller picks. some P reverses that — the callee picks one fixed type and hides it. any P is a box holding different conforming types over time, at the cost of the identity that made the other two useful.
Enums carry data, and switches must be exhaustive
~2 min
A Swift enum case can carry a payload, and each case can carry a different one — so an enum models "one of these shapes" rather than a numbered constant. switch must cover every case, which turns adding a case into a list of compile errors instead of a silent bug.
Errors are values, not exceptions
~2 min
A throwing function announces it in its signature, and every call to one is marked try. There is no stack unwinding, so propagating one costs about what returning costs. try? turns the error into nil and try! traps, and both discard information you usually wanted.
Initialization, and the memberwise one you can lose
~2 min
Every stored property must have a value before initialization ends — there are no implicit defaults. A struct gets a free memberwise initializer *only* while it declares no initializer of its own; writing one in an extension keeps both. Classes initialize in two phases and cannot touch self until the first finishes.
Collections and strings are values too
~2 min
Arrays, dictionaries, sets and strings are all value types — copies are independent, with the physical copy deferred until a write. Their failure modes differ on purpose: an out-of-range array index traps, a missing dictionary key returns nil, and a string cannot be indexed by an integer at all.
async, await, and actors that take turns
~2 min
async lets a function suspend without blocking a thread; await marks every point where it might. Concurrency comes from creating tasks, not from the keyword. Actors serialize access to their own state — but they can interleave other work at each await, so an invariant must hold across every suspension.
Property wrappers, result builders and macros
~2 min
Three ways to stop writing the same code by hand. A property wrapper moves storage and access rules into a reusable type. A result builder rewrites a block of expressions into constructor calls, which is what makes declarative blocks legal. A macro generates source at compile time and can only add, never delete.
See the full Swift curriculum
Written by Keentune. We are not affiliated with or endorsed by the organizations whose documentation informs this guide, and any linked sources belong to their respective owners.
All exam, test, and product names and trademarks are the property of their respective owners and are used here for identification and reference only. Keentune is independent study practice — not affiliated with, authorized, or endorsed by any of these organizations.
© 2026 SportaApp LLC