Swift Cheat Sheet
Optionals, closures, protocols, generics, actors and SwiftUI in one Swift cheat sheet. Every snippet is copy ready, filterable and cross referenced.
Basics
Constants, variables, type inference and string interpolation. Swift is statically typed but you rarely write the type out.
Types & Operators
Swift never converts numeric types for you. That verbosity is deliberate: it removes a whole family of silent precision bugs.
| Type | Holds | Notes |
|---|---|---|
| Int | whole numbers | 64 bit on modern devices, the default |
| UInt8 ... UInt64 | unsigned | use only for bit work and byte buffers |
| Double | 64 bit float | the default for decimals |
| Float | 32 bit float | graphics and memory constrained data |
| Bool | true or false | no integer coercion at all |
| String | Unicode text | value type, grapheme cluster based |
| Character | one grapheme | can be several Unicode scalars |
| Array<T> | ordered list | written [T] |
| Dictionary<K,V> | key to value | written [K: V] |
| Set<T> | unique, unordered | T must be Hashable |
| Optional<T> | value or nil | written T? |
| Void | no value | the empty tuple () |
| Any / AnyObject | any / any class | escape hatch, avoid in new code |
| Operator | Meaning |
|---|---|
| + - * / % | arithmetic, % is remainder and works on Int only |
| == != < > <= >= | comparison, needs Equatable or Comparable |
| === !== | identity, do two references point at the same object |
| && || ! | logical, short circuits |
| ?? | nil coalescing, supply a default |
| ?. | optional chaining |
| ! | force unwrap, crashes on nil |
| ... ..< | closed range and half open range |
| &+ &- &* | wrapping arithmetic, no overflow trap |
| ~= | pattern match, what switch case uses |
| & | ^ << >> ~ | bitwise |
| is as? as! as | type check and cast |
Optionals
Swift has no implicit null. A value that may be absent has type T?, and the compiler will not let you use it until you deal with the nil case.
The unwrapping decision, in order
Strings
Swift strings are Unicode correct by design. That is why you index them with String.Index instead of Int, and why count is not a constant time lookup.
Collections
Array, Dictionary and Set are all value types with copy on write, so passing one around is cheap until somebody mutates it.
Control Flow
Switch is exhaustive and pattern matching runs deep. Most of the branching you would write with if elsewhere becomes a switch here.
Functions & Closures
Argument labels make call sites read like sentences. Closures are first class, and trailing closure syntax is why SwiftUI looks the way it does.
Structs & Classes
The choice between value and reference semantics is the most consequential design decision in a Swift codebase. Start with a struct.
| Aspect | struct | class |
|---|---|---|
| semantics | value, copied on assignment | reference, shared |
| memberwise init | generated for free | you write it |
| inheritance | no, compose with protocols | yes, single superclass |
| mutation | methods need mutating | free, even on a let |
| deinit | not available | available |
| identity | none, compare by value | === compares identity |
| thread safety | easy, copies are independent | needs care, shared state |
| storage | usually inline or in the parent | always heap, ARC counted |
| reach for it when | models, values, most types | identity, shared state, ObjC interop |
Enums
Swift enums carry data, conform to protocols and have methods. Used well they make illegal states impossible to represent.
Protocols
Protocols describe capability rather than ancestry. With protocol extensions they also carry default behaviour, which is why Swift leans on composition instead of inheritance.
| Protocol | Gives you |
|---|---|
| Equatable | == and !=, synthesised for simple types |
| Hashable | use as a Dictionary key or in a Set |
| Comparable | < > and sorted() |
| Codable | encode to and decode from JSON and more |
| Identifiable | a stable id, required by SwiftUI lists |
| CustomStringConvertible | your own description in print |
| Sequence / Collection | for in, map, filter and the rest |
| IteratorProtocol | drive a custom Sequence |
| ExpressibleByStringLiteral | init your type from a literal |
| RawRepresentable | raw value conversion, usually via enums |
| Error | throwable, usually an enum |
| Sendable | safe to cross a concurrency boundary |
| OptionSet | bit flag style sets |
| AsyncSequence | for await iteration |
Generics
Write once, work with every type that satisfies the constraint, with no boxing and no loss of type information.
Error Handling
Swift errors are values, not exceptions. A function that can fail says so in its signature, and the compiler makes callers acknowledge it.
Properties & Access Control
Computed properties, observers and wrappers cover most of what you would write boilerplate for elsewhere.
| Level | Visible to |
|---|---|
| private | the enclosing declaration and its extensions in the same file |
| fileprivate | everything in the same file |
| internal | the whole module, this is the default |
| package | every module in the same package, Swift 5.9 |
| public | other modules, but not subclassable outside |
| open | other modules, and subclassable or overridable there |
Memory & ARC
Automatic Reference Counting frees objects the moment the last strong reference disappears. The only thing it cannot solve for you is a cycle.
The three reference kinds
Concurrency
Structured concurrency replaced callback pyramids. Under Swift 6 strict checking, data races become compile errors instead of intermittent crashes.
SwiftUI
Views are values that describe the interface for the current state. You never mutate a view, you change state and let the framework rebuild.
UIKit
Most shipping iOS code is still UIKit, and mixed UIKit and SwiftUI apps are the norm. Here is the imperative half of the platform.
Persistence
SwiftData is the modern declarative store. Core Data still runs underneath it and is what most existing apps use.
Foundation & Codable
JSON, dates, files and networking. Codable in particular removes most of the parsing code you would otherwise hand write.
Testing
Swift Testing is the modern framework built around macros and plain expressions. XCTest is still everywhere, and the two run side by side.
Debugging & LLDB
The debugger console is faster than another print and rebuild cycle. These are the commands worth knowing by heart.
| Command | What it does |
|---|---|
| po value | print the object description |
| p value | print with type information |
| v value | print a variable without running code |
| e expression | evaluate, and it can mutate state |
| e -l swift -- code | force the Swift language context |
| bt | backtrace for the current thread |
| bt all | backtrace for every thread |
| frame variable | list everything in the current frame |
| frame select 2 | move up the stack |
| thread step-over (n) | step over one line |
| thread step-in (s) | step into the call |
| continue (c) | resume execution |
| b MyType.method | set a symbolic breakpoint |
| br list / br delete | list or remove breakpoints |
| watchpoint set variable x | break when x changes |
| image lookup -a address | resolve a crash address to a symbol |
| expr -O -- object | description of a complex object |
Package Manager & Tooling
Swift Package Manager ships with the toolchain and is the default way to build, test and depend on code, inside Xcode and on the command line alike.
| Command | What it does |
|---|---|
| swift package init | scaffold a new package |
| swift build | compile in debug |
| swift build -c release | compile optimised |
| swift run | build and run the executable |
| swift test | run the test targets |
| swift test --filter Parser | run a subset |
| swift package resolve | fetch and pin dependencies |
| swift package update | move within the allowed ranges |
| swift package show-dependencies | print the dependency tree |
| swift package clean | delete the build folder |
| swift-format -i -r Sources | format in place |
| xcrun swift-format lint | lint without rewriting |
| xcodebuild -scheme App test | run tests in CI |
| Keys | Action |
|---|---|
| Cmd R | run |
| Cmd U | run tests |
| Cmd B | build |
| Cmd Shift K | clean build folder |
| Cmd Shift O | open quickly, jump to any symbol |
| Ctrl Cmd J | jump to definition |
| Ctrl Cmd Left | go back |
| Cmd Shift F | find in project |
| Ctrl I | re-indent selection |
| Cmd / | toggle comment |
| Cmd Ctrl E | rename in scope |
| Cmd Shift A | quick actions menu |
| Cmd Opt P | resume the preview canvas |
| Cmd Shift Y | toggle the debug area |
Macros & Attributes
Attributes steer the compiler. Macros, added in Swift 5.9, generate real source code at build time and are fully type checked.
| Attribute | Meaning |
|---|---|
| @main | marks the program entry point |
| @escaping | the closure outlives the call |
| @autoclosure | wrap the argument in a closure automatically |
| @discardableResult | no warning if the return value is ignored |
| @available | gate on OS version or mark deprecated |
| @inlinable | expose the body for cross module inlining |
| @MainActor | isolate to the main actor |
| @Sendable | the closure is safe to send across tasks |
| @objc / @objcMembers | expose to the Objective-C runtime |
| @dynamicMemberLookup | resolve arbitrary members at compile time |
| @resultBuilder | build values from a statement list, as SwiftUI does |
| @frozen | promise the layout will not change, ABI stability |
| @testable | import a module with internal access |
Compiler Error Index
The messages that actually stop people, each with the smallest reproduction and the fix that resolves it.
Coming From Another Language
Direct translations for what you already know how to do. Find the habit on the left, learn its Swift spelling on the right.
| Objective-C | Swift |
|---|---|
| NSString * | String |
| NSArray / NSMutableArray | let [T] / var [T] |
| NSDictionary | [K: V] |
| NSNumber | Int, Double, Bool |
| nil check on every call | Optional plus if let |
| id | Any or AnyObject |
| @property (nonatomic, strong) | var |
| @property (weak) | weak var |
| @interface / @implementation | one struct or class, no header |
| @protocol | protocol |
| categories | extension |
| blocks | closures |
| NSError ** | throws |
| #import | import, no header files |
| Kotlin | Swift |
|---|---|
| val / var | let / var |
| String? | String? |
| ?: elvis | ?? nil coalescing |
| ?.let { } | if let |
| data class | struct with Equatable and Hashable |
| sealed class | enum with associated values |
| when | switch |
| interface | protocol |
| companion object | static members |
| extension functions | extension |
| suspend fun | async func |
| coroutineScope | withTaskGroup |
| Flow | AsyncSequence or AsyncStream |
| listOf / mutableListOf | let [T] / var [T] |
| TypeScript | Swift |
|---|---|
| const / let | let / var |
| string | undefined | String? |
| ?? and ?. | ?? and ?. |
| interface / type | protocol / struct |
| union type A | B | enum with associated values |
| Array<T> / T[] | [T] |
| Record<K, V> | [K: V] |
| map / filter / reduce | map / filter / reduce |
| async / await | async / await, plus try |
| Promise.all | async let, or a task group |
| try / catch | do / catch with throws |
| JSON.parse | JSONDecoder().decode |
| npm install | add to Package.swift |
| generics <T extends X> | <T: X> |
| Python | Swift |
|---|---|
| x = 5 | let x = 5 |
| list / dict / set | [T] / [K: V] / Set<T> |
| None | nil, inside an Optional |
| len(x) | x.count |
| f"{name}" | "\(name)" |
| [f(x) for x in xs] | xs.map(f) |
| [x for x in xs if p(x)] | xs.filter(p) |
| for i, v in enumerate(xs) | for (i, v) in xs.enumerated() |
| class / @dataclass | class / struct |
| try / except | do / catch |
| raise ValueError | throw MyError.invalid |
| with open(p) as f | defer for cleanup |
| json.loads | JSONDecoder().decode |
| pytest | swift test |
What Swift Is
Swift is a compiled, statically typed language built by Apple to replace Objective-C, and designed so that the safe way to write something is also the short way. Optionals remove null crashes, value types remove aliasing bugs, and since Swift 6 the compiler catches data races too.
Chris Lattner started it in 2010, Apple announced it at WWDC in 2014, and it went open source in December 2015. Governance now sits with the Swift project, and language changes go through a public proposal process on GitHub rather than being handed down.
It is the default for every Apple platform, but it is not limited to them. Swift compiles on Linux and Windows, powers server frameworks such as Vapor, and runs on microcontrollers through Embedded Swift.
The trade compared with a dynamic language is straightforward. You spend more time being precise about types up front, and in exchange a large class of runtime failures becomes a build error you fix in seconds.
Swift Syntax Basics
Swift reads like a modern curly-brace language. Types come after the name, semicolons are optional and almost never used, and type inference means you rarely write the type at all.
Constants and variables
Use let for anything that does not change, which in practice is most things. The compiler warns when a var is never mutated.
let maximum = 100
var counter = 0
counter += 1
let name: String = "Ada" // explicit type, usually unnecessary
Functions and argument labels
Swift separates the name the caller uses from the name the body uses. Well chosen labels are why idiomatic Swift reads almost like prose at the call site.
func move(from start: Point, to end: Point) { }
move(from: a, to: b)
func log(_ message: String) { } // underscore removes the label
log("no label needed")
Control flow
Switch is exhaustive, so leaving out a case is a compile error rather than a silent fallthrough. Since Swift 5.9 both if and switch also work as expressions.
let grade = switch score {
case 90...: "A"
case 80..<90: "B"
default: "C"
}
Optionals, the Idea That Defines Swift
There is no implicit null. A value that might be absent has the type String?, which is a genuinely different type from String, and the compiler will not let you use one where the other is expected.
var middleName: String? // nil until set
if let middleName {
print(middleName) // non optional inside this branch
}
let display = middleName ?? "none"
let length = user?.profile?.bio?.count // nil if any link is nil
The everyday choice is between if let and guard let. Use guard let when the value is required for the rest of the function, because it exits early and keeps the unwrapped binding in scope below, which flattens nesting considerably.
func greet(_ name: String?) {
guard let name else { return }
print("Hello, \(name)") // name is a plain String here
}
Force unwrapping with ! exists, and it crashes when the value is nil. It is occasionally the honest choice, for a resource you ship inside the app bundle, but every one is a promise about data you may not control.
Value Types and Reference Types
This is the design decision that shapes a Swift codebase most. A struct is a value type: assigning it makes an independent copy. A class is a reference type: assigning it shares one instance.
| Question | struct | class |
|---|---|---|
| copied on assignment | yes | no, shared |
| free memberwise init | yes | no |
| inheritance | no | yes |
| can have deinit | no | yes |
| identity with === | no | yes |
| safe across threads | usually | needs care |
The guidance from Apple, and from most production codebases, is to start with a struct and move to a class only when you need identity, inheritance, a deinitialiser, or genuinely shared mutable state. Passing large structs around is cheap because Array, String, Dictionary and Set all use copy on write: the underlying buffer is shared until something actually mutates it.
Protocols Instead of Inheritance
Swift is often described as protocol oriented. A protocol declares capability, any type can adopt as many as it likes, and protocol extensions supply default implementations so adoption costs almost nothing.
protocol Greeter {
var name: String { get }
func greet() -> String
}
extension Greeter {
func greet() -> String { "Hello, \(name)" }
}
struct English: Greeter { let name: String } // greet comes for free
One rule catches everyone once: only methods declared in the protocol body are dynamically dispatched. A method that exists solely in the extension is selected by the static type of the variable, so overriding it in a conforming type will not take effect through a protocol-typed reference.
Since Swift 5.7 the keywords some and any make the cost explicit. Prefer some for parameters and return types, because it stays generic and free. Reach for any only when you need to store different concrete types in one collection.
Concurrency Without Callback Pyramids
Structured concurrency landed in Swift 5.5 and changed how asynchronous code is written. Instead of nesting completion handlers, you write straight-line code with ordinary try and catch.
func loadDashboard() async throws -> Dashboard {
async let profile = loadProfile()
async let posts = loadPosts()
return try await Dashboard(profile: profile, posts: posts)
}
Because the two loads start immediately and the await collects them, this costs the slower of the two rather than the sum. Actors then protect shared mutable state by serialising access, and @MainActor guarantees that anything touching the interface runs on the main thread.
Swift 6 turned strict concurrency checking on by default. Passing a non-Sendable value across an actor boundary is now a compile error, which is uncomfortable during migration and genuinely prevents the intermittent crashes that used to survive code review.
SwiftUI in One Paragraph
A SwiftUI view is a struct describing what the interface should look like for the current state. You never mutate a view. You change state, and the framework works out the smallest redraw.
struct CounterView: View {
@State private var count = 0
var body: some View {
VStack(spacing: 12) {
Text("Count: \(count)")
.font(.title)
Button("Increment") { count += 1 }
}
.padding()
}
}
The @Observable macro introduced in Swift 5.9 replaced most uses of ObservableObject and @Published. It tracks which properties a view actually reads, so changing an unrelated property no longer triggers a redraw.
When Swift Is the Right Choice
- Any Apple platform app: iOS, macOS, watchOS, tvOS and visionOS. There is no serious alternative if you want full platform capability and first day support for new OS features.
- Cross platform mobile with shared logic: Swift for the iOS layer alongside Kotlin on Android is still the highest quality path, and the two languages are close enough that porting logic is quick.
- Server side Swift: worth it when your team already writes Swift and you want one language across client and backend, with real performance and low memory use.
- Command line tools: a single compiled binary with no runtime to install, and Swift Argument Parser makes the interface pleasant.
- Performance sensitive libraries: value semantics and no tracing garbage collector give predictable latency, which matters for audio, graphics and real time work. If that is the draw rather than the Apple platforms, Rust solves the same problem with a stricter compiler and no runtime at all.
It is a weaker fit for quick data exploration, where Python remains far quicker to reach an answer, for teams with no Apple hardware, and for web frontends, where JavaScript and TypeScript remain the sensible defaults.
Learning Path That Actually Works
The order matters. Skipping ahead to SwiftUI before optionals and value semantics are comfortable is the most common way people get stuck.
- Week one: syntax, optionals, collections and control flow. Write small command line programs, not apps.
- Week two: structs, classes, enums with associated values, and error handling. Model something real and notice how enums remove impossible states.
- Week three: protocols, extensions, generics and closures. This is where the language starts feeling expressive rather than strict.
- Week four: SwiftUI and data flow, then async await once you need to fetch something.
- Ongoing: ARC and capture lists, then strict concurrency. Both make far more sense once you have hit the bugs they prevent.
The canonical free resources are The Swift Programming Language, Apple's SwiftUI tutorials, and the API Design Guidelines, which explain why the standard library is named the way it is. Version control matters from day one, so keep a Git reference nearby, and if your app talks to a database the SQL you write against it is unchanged by the client language. Server side Swift deploys as a single binary, so a scratch Docker image is usually all the runtime it needs.
Common Mistakes
- Force unwrapping to silence the compiler: Xcode offers
!as a fix-it, and taking it converts a build error into a crash on a user's device. - Retain cycles in stored closures: any closure you keep that mentions
selfneeds[weak self]. A closure passed tomapdoes not. - Reaching for a class out of habit: developers arriving from Java or Objective-C often make everything a class and lose the safety that value semantics provide for free.
- Modelling state with several booleans: three flags describe eight states when only four exist. An enum with associated values makes the other four unrepresentable.
- Ignoring the HTTP status code:
URLSessiondoes not treat a 500 as an error, so an unchecked response surfaces later as a confusing decoding failure. - Creating a DateFormatter inside a loop: it is genuinely expensive to construct. Reuse one, or use the newer
formattedAPI.
Frequently Asked Questions
What is the difference between a struct and a class in Swift?
A struct is a value type, so it is copied on assignment and each copy is independent. A class is a reference type, so assignment shares one instance and changes are visible everywhere. Swift favours structs for models and data, and classes when you need identity, inheritance, deinit, or reference semantics such as a shared controller.
When should I use guard let instead of if let in Swift?
Use guard let when the unwrapped value is required for the rest of the function, because guard exits early and keeps the bound value in scope afterwards, which flattens nesting. Use if let when the value is optional to the logic and you only need it inside a short branch.
What does weak self do in a Swift closure?
Closures capture the values they reference strongly by default, so a closure stored on an object that also references that object creates a retain cycle and neither is deallocated. Writing [weak self] in the capture list makes the reference weak and optional, breaking the cycle. Use unowned only when self is guaranteed to outlive the closure.
Is Swift only for iOS development?
No. Swift builds apps for iOS, macOS, watchOS, tvOS and visionOS, but it is also open source and runs on Linux and Windows. Server frameworks such as Vapor and Hummingbird use it for backends, and Swift is used for command line tools, systems programming, and embedded targets through Embedded Swift.
What is the difference between async await and completion handlers?
Completion handlers pass a closure that runs later, which nests badly and makes error paths easy to miss. Async await lets you write the same asynchronous work as straight-line code with ordinary try and catch, and the compiler checks that every path returns or throws. Structured concurrency also cancels child tasks automatically when the parent is cancelled.
What does the Sendable protocol mean in Swift concurrency?
Sendable marks a type as safe to pass across concurrency boundaries such as between actors or into a Task. Value types made of Sendable parts conform automatically, while classes generally do not unless they are immutable or protect their own state. Under Swift 6 strict concurrency the compiler enforces this, turning a whole class of data races into build errors.