Swift Cheatsheet
Memory & Performance
Use this Swift reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.
Value semantics and copy-on-write
var a = [1, 2, 3] var b = a // no copy yet, storage is shared b.append(4) // the copy happens here (copy-on-write)
Array, Dictionary, Set, and String are value types with COW storage: cheap to pass around, and copies materialize only on mutation of a shared buffer. Plain structs copy eagerly, but small structs are register-cheap.
ARC: strong, weak, unowned
final class Person { let name: String var apartment: Apartment? init(name: String) { self.name = name } deinit { print("\(name) deallocated") } } final class Apartment { weak var tenant: Person? // back-reference stays weak, cycle broken }
| Reference | Optionality | When the target deallocates |
|---|---|---|
| strong (default) | any | Keeps it alive |
weak | must be an optional var | Becomes nil |
unowned | non-optional | Crash on access |
Classes are reference-counted (ARC). Two objects holding strong references to each other never deallocate. Use weak for delegates and parent/back pointers, unowned only when the target provably outlives the reference.
Closure capture cycles
final class Downloader { var onProgress: ((Double) -> Void)? func start() { onProgress = { [weak self] p in // without weak: self → closure → self leak guard let self else { return } self.render(p) } } func render(_ p: Double) {} }
Value types never create retain cycles. Only reference types that store closures referring back to themselves need capture lists.
mutating and controlled mutation
struct Counter { private(set) var value = 0 // public read, internal write mutating func inc() { value += 1 } // struct methods that write need `mutating` } var c = Counter() c.inc() // requires var, not let
Performance checklist
var out: [Int] = [] out.reserveCapacity(n) // skip repeated reallocation var s = "" s.reserveCapacity(1 << 16) for x in answers { s += "\(x)\n" } // amortized O(1) append print(s, terminator: "") // Lazy chains stop early instead of building intermediate arrays let firstBig = nums.lazy.map { $0 * $0 }.first { $0 > 100 }
| Pitfall | Fix |
|---|---|
removeFirst() in a loop, O(n) each | Head index, or Deque from swift-collections |
insert(_, at: 0) in a loop | Append, then reversed() once |
array.contains inside a loop, O(n²) total | Build a Set first |
| Growing arrays without capacity | reserveCapacity(_:) |
| Benchmarking debug builds | swift build -c release / swiftc -O |
Overflow and explicit numeric conversion
let x: Int32 = 2_000_000_000 // let boom = x + x // runtime crash: overflow traps by default let wrapped = x &+ x // overflow operators wrap: &+ &- &* let ratio = Double(done) / Double(total) // conversions are always explicit UInt8(clamping: 300) // 255 Int8(truncatingIfNeeded: 300) // 44, keeps the low bits
Measuring
let start = ContinuousClock.now work() print(ContinuousClock.now - start) // "0.123 seconds"
Always measure optimized builds. Debug builds skip inlining and bounds-check aggressively, so debug timings are meaningless for judging algorithmic code.