Skip to content
AgileLogics
6 min read

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 final classes can be resolved at compile time (static dispatch), and often inlined.
  • Calls through any protocol 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

  1. Get work off the main thread — decoding, image processing, and file I/O in background tasks keep the interface responsive.
  2. Load less — paginate lists, downsample large images to the size they're displayed at, cache network results.
  3. Keep SwiftUI bodies cheap — no sorting, filtering, or formatting of large data inside body; compute it in the model.
  4. Use the right collection — a Set or Dictionary lookup 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.