Performance
Prerequisites: Memory management with ARC, Opaque and existential types
Swift is fast by default, but a few language details make a real difference in hot code. As always: measure first, then optimize what the measurements point to.
Measure with Instruments
Xcode's Instruments (Product → Profile, ⌘I) is the tool for finding where time and memory actually go:
- Time Profiler — which functions use the most CPU time.
- Allocations and Leaks — memory growth and reference cycles.
- SwiftUI instrument — which views update, how often, and why.
- Hangs — moments when the main thread is blocked and the app freezes.
Always profile a Release build on a real device; debug builds are much slower and not representative.
Copy-on-write
Array, Dictionary, Set, and String are value types, but copying
one doesn't immediately duplicate its storage. Copies share storage
until one of them is modified — only then is the data copied. This is
copy-on-write:
let a = Array(repeating: 0, count: 1_000_000)
var b = a // cheap: shares storage
b[0] = 1 // now b gets its own copy
print(a[0], b[0])
0 1
The practical rule: passing collections around is cheap; modifying a copy of a large collection triggers a full copy. Watch for code that modifies copies inside loops.
Reserve capacity
When you know roughly how many elements you'll add, reserve space once instead of letting the array regrow repeatedly:
var squares: [Int] = []
squares.reserveCapacity(10_000)
for n in 0..<10_000 {
squares.append(n * n)
}
print(squares.count)
10000
Static vs dynamic dispatch
- Calls on structs, enums, and
finalclasses can be resolved at compile time (static dispatch), and often inlined. - Calls through
anyprotocol types and non-final class methods are looked up at runtime (dynamic dispatch).
Prefer some or generics over any in performance-sensitive code, and
mark classes final when they aren't meant to be subclassed.
Common wins in apps
- Get work off the main thread — decoding, image processing, and file I/O in background tasks keep the interface responsive.
- Load less — paginate lists, downsample large images to the size they're displayed at, cache network results.
- Keep SwiftUI bodies cheap — no sorting, filtering, or formatting
of large data inside
body; compute it in the model. - Use the right collection — a
SetorDictionarylookup instead of searching an array inside a loop turns O(n²) work into O(n).
Common mistakes
- Optimizing before profiling, then speeding up code that was never the bottleneck.
- Measuring in the simulator or a debug build.