Keentune

Swift curriculum

25 chapters
·
215 concepts
·
free
Everything the adaptive question bank can teach and test in Swift, from foundations through advanced practice. Work through it in order, or start practising and let the questions find your level.
New here? Read the Swift guide
A free 17-minute primer — the mental model, the mistakes beginners make, and what to practise first.
A. The basics: bindings, types and optionals
let binds once, and a let struct cannot have any property mutated
an unannotated 42 is Int and 3.0 is Double
Int and Double never mix; you write Double(i)
grouping values ad hoc, naming elements, discarding with _
Optional<Wrapped> is .some/.none; nil is absence, not a null pointer
if let and guard let, and the shorthand if let x that rebinds the name
! traps on nil instead of producing a fallback
a ?? b short-circuits and evaluates b only when a is nil
T! is still an Optional, checked on every access
B. Operators and expressions
x = y yields no value, so if x = y cannot compile
% takes the sign of the dividend, so -9 % 4 is -1
Int.max + 1 traps at runtime rather than wrapping
a...b includes b, a..<b excludes it, and one-sided ranges omit an end
&& and || skip the right operand once the left decides
compared element by element, left to right, up to six elements
?? binds looser than arithmetic, so mixed expressions need parentheses
C. Strings and characters
one Character can be several Unicode scalars, like a flag emoji
count is O(n); isEmpty is the cheap emptiness test
you subscript with a String.Index from index(_:offsetBy:)
a precomposed é and a decomposed e plus accent compare equal
unicodeScalars, utf8 and utf16 give three different counts of one string
""" strips the closing delimiter's indentation from every line
#"\n"# is two characters, and \#(x) restores interpolation
a Substring keeps the whole original alive until you convert it
D. Collections
a copy is independent, though the physical copy waits for a write
there is no nil fallback; check indices or use first
a missing key gives nil, and assigning nil removes the key
counts[k, default: 0] += 1 inserts and updates in one step
and Hashable is synthesized only when every stored property conforms
iteration order may differ between runs of the same program
one drops nils, the other flattens one level of nesting
lazy defers element work so a chained map/filter builds no intermediate array
an ArraySlice does not start at index 0
E. Control flow and pattern matching
every possible value needs a case or a default
a case ends by itself; fallthrough is the explicit opt-in
case let x where x > 0 binds and filters in one pattern
one switch can match several values at once, and ranges as cases
case "a", "b":, and every alternative must bind the same names
iterating a collection or range, and stepping by an increment
they can produce a value in a let or a return
deferred blocks run on every exit, including a throw, last in first out
if #available(iOS 17, *) gates newer OS API at runtime
F. Functions
the caller writes the label, the body uses the name
a defaulted parameter may be skipped entirely by the caller
the values arrive inside the body as an array
the write-back happens on return, so inout is not a pointer
passing one variable to two inout parameters is rejected
an inner function closes over enclosing locals and can be returned
Never is uninhabited and marks a call that does not come back
@discardableResult deliberately silences the unused-return warning
G. Closures and capture
$0 and $1 work only when the types come from context
the last closure moves outside the parentheses; later ones keep labels
copying a closure shares the same captured storage
a captured var sees mutations made after the closure was created
[x] copies the value at closure-creation time instead
@escaping marks a closure stored or called after the function returns
required, because the capture keeps the instance alive
[weak self] yields an optional; [unowned self] traps after dealloc
@autoclosure wraps the argument expression so it evaluates only if used
H. Enumerations
an enum case is useful with no raw value at all
Int cases count up from zero, String cases default to their names
init?(rawValue:) returns nil for an unrecognized raw value
each case may carry a different payload, extracted by pattern matching
recursion needs indirect so a case can box its own payload
an enum may have methods and computed properties, never stored ones
synthesizes allCases, and cannot apply when a case has an associated value
a mutating method may replace the whole value with self = .other
@unknown default handles future library cases without losing the warning
I. Value and reference semantics
assigning a struct produces a value nothing else can change
two variables holding one class instance observe each other's mutations
a value-type method that writes self must be marked mutating
a let class reference still allows changing the object's vars
=== asks "the same object", == asks "the same value"
free for a struct when every stored property already conforms
isKnownUniquelyReferenced is how a struct defers the copy until a write
a class property makes the struct's copies share that object
J. Properties
one occupies memory, the other runs a getter on every access
the setter's implicit parameter, and the read-only shorthand
initialized on first access, must be var, and is not thread-safe
willSet receives newValue, didSet receives oldValue
assignments inside init do not fire them
writing the property inside its own didSet does not re-trigger it
static for a fixed one, class for a computed one a subclass can override
a stored type property initializes on first use, exactly once
K. Methods, subscripts and inheritance
a static func belongs to the type, not to any value
parameterized access declared with subscript(i: Int) -> T, read-only or read-write
structs and enums compose or conform instead
omitting override is an error, which prevents accidental shadowing
and lets the compiler skip dynamic dispatch
you may add a setter to an inherited read-only property, never remove one
class members can be overridden, static members cannot
class method calls are indirect unless devirtualized
L. Initialization and deinitialization
no implicit defaults; the initializer must cover them all
unless you declare your initializer in an extension
one initializes fully and calls up, the other delegates across
set your own properties, call super.init, only then use self
a subclass inherits superclass initializers only under specific conditions
required forces every subclass to supply the same initializer
init? returns nil on failure; init! gives an implicitly unwrapped result that traps on use
a property default preserves the memberwise initializer
it runs when the last strong reference goes and cannot be called by hand
M. Errors, throwing and optional chaining
errors are ordinary values, most often an enum
a throwing function cannot go where a non-throwing one is required
every throwing call is marked try, and the error is caught or propagated
try? discards the error into nil; try! traps
the function throws only when the closure it was handed throws
throws(E) fixes the error type; plain throws means any Error
catch let e as MyError, and the implicit error binding in a bare catch
propagation is an ordinary return path with no stack unwinding
which is how cleanup survives a throw
Result<Success, Failure> stores a throw as a value, unwrapped with get()
the first nil ends the chain and the whole expression is optional
N. Protocols
a { get } requirement may be satisfied by a settable property
needed so a struct can satisfy a method that changes it
a non-final class must mark that initializer required
refining one protocol, and A & B as a single type
class-only conformance, which is what a weak reference needs
including retroactive conformance on a type you do not own
a default implementation the conformer may override
a member that is not a requirement is not dynamically dispatched
where Element: Equatable adds members only for some conformers
Array is Equatable exactly when its Element is
Equatable, Hashable, Codable and CaseIterable write themselves, conditionally
O. Generics
func f<T>(_ x: T) is one declaration the caller instantiates per call site
the body is type-checked once, before any instantiation
an unconstrained T supports almost nothing until you constrain it
a protocol placeholder that each conformer fills in
usually deduced from the implementing member's signature
Collection<Int> constrains the associated type inline
constraining associated types and relationships the angle brackets cannot express
where C1.Element == C2.Element ties two parameters together
a Box<Dog> is not a Box<any Animal>, and why that must be so; the standard-library collections are the compiler-blessed exception, so [Dog] DOES convert to [any Animal]
an AnySequence-style wrapper, and the dispatch it costs
each T makes one function generic over an arbitrary number of types
P. Opaque and existential types
the caller cannot name it, but it is a single fixed type
one variable may hold different conforming types over time
type identity preserved versus erased, and the boxing the erasure costs
with some P the callee chooses the type; with <T> the caller does
every return in an opaque-returning body must produce the same type
sugar for an unnamed generic parameter, not an existential
why such protocols need any and resist being used as a type
Q. Automatic reference counting
the object is deallocated the instant the count reaches zero
deallocation is deterministic, and reference cycles are never collected
structs and enums are not reference counted
two objects that own each other are never freed
it is zeroed automatically when the referent goes away
it is not zeroed, so touching it after deallocation traps
pick by whether the reference can legally outlive the referent
a stored closure capturing self keeps its own owner alive
R. Memory safety and ownership
a write access to a variable may not overlap any other access to it
the conflict the compiler rejects outright
locals are checked at compile time, class properties at runtime
~Copyable forbids implicit duplication of a value
whether the callee takes ownership or only reads
an explicit transfer that ends the source binding's lifetime
value types with deterministic cleanup
a withUnsafe… pointer is valid only inside its closure
S. Concurrency: async/await, tasks and actors
async lets a function suspend without blocking its thread
other work runs and shared state can change across it
concurrency comes from creating tasks, not from the keyword
starts a child immediately and awaits it at the point of use
a dynamic number of children, all awaited before the group returns
a child task cannot outlive the scope that created it
Task {} inherits context; Task.detached inherits nothing
cancellation sets a flag; the task decides where to stop
at most one task runs an actor's isolated code at a time
reaching another actor's isolated member requires await
an actor may interleave other work at every await, so invariants must hold there
opting a member out of isolation so it can be called synchronously
@MainActor pins work to the main thread, and you can declare your own
a type whose values are safe to hand across an isolation boundary
for await, and AsyncStream for adapting a callback API
bridging a completion handler, with the resume-exactly-once rule
the language mode that turns concurrency warnings into errors
T. Macros
the expansion becomes source; nothing of the macro runs at runtime
#stringify produces code in place, @Attached decorates a declaration
expression, declaration, member, peer, accessor and extension roles do different jobs
declared in your module, implemented in a separate compiler plugin
an expansion may add code but never delete or rewrite what you wrote
names introduced by an expansion cannot collide with the caller's names
U. Attributes: property wrappers, result builders and the rest
@propertyWrapper moves storage and access logic into a reusable type
init(wrappedValue:), and passing extra arguments in the attribute
$name exposes a second interface beside the wrapped value
@resultBuilder rewrites a block of expressions into buildBlock calls
buildOptional, buildEither and buildArray are what make if and for legal inside
what it exposes to Objective-C, and the message dispatch it implies
cross-module inlining and layout promises that become ABI commitments
V. Extensions and access control
methods, computed properties and initializers only
to change inherited class behavior you subclass instead
open, public, package, internal, fileprivate and private, most to least visible
an unmarked declaration is visible throughout its own module only
a private member is still visible to extensions in the same file
only open permits subclassing or overriding from another module
a public method on an internal type is still internal
private(set) var publishes the getter and hides the setter
W. Type casting, key paths and reflection
as? yields an optional, as! traps when the cast fails
any value at all versus any class instance
case let shape as Circle combines the test and the binding
type(of:) gives the dynamic type; T.self is a value of type T.Type
\Person.name is a stored reference to a member, not a call to it
WritableKeyPath lets you assign through the path
map(\.name) relies on the implicit key-path-to-closure conversion
read-only introspection, and why it is not a serialization system
X. Advanced operators
~, &, | and ^ on fixed-width integers
>> on a signed integer preserves the sign bit
&+, &- and &* wrap around instead of trapping
implementing + or == for your own type as a static function
declare the token, then how tightly it binds
init(exactly:), init(truncatingIfNeeded:) and init(clamping:)
Y. Language evolution and version gates
a numbered SE proposal reviewed in public is how the language changes
one compiler builds Swift 5 mode and Swift 6 mode under different rules
opting into a future mode's behavior one feature at a time
what stays compilable versus what stays binary-compatible
#if swift(>=6.0) tests the language mode, #if compiler(>=6.0) the toolchain
@available gates platform API, independently of the Swift version
Keentune is not affiliated with or endorsed by the organizations whose documentation informs these maps.
All about Swift practice
Also on your phone
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