Memory and Value Semantics (with version cues)

ARC basics, retain cycles, copy-on-write, and value vs reference semantics.

Version Quick Reference

  • Swift 5.x+: ARC for reference types; standard library uses copy-on-write (CoW) for Array, Dictionary, Set.
  • Swift 5.7+: stricter Sendable checking (behind flags) helps surface thread-safety and aliasing issues.
  • Swift 6+: strict concurrency checking by default; @MainActor/Sendable diagnostics catch unsafe sharing early.

ARC Essentials

  • Classes and closures are reference types managed by ARC. Increments on strong references; decrements on release.
  • Avoid retain cycles by breaking strong references in one direction (weak or unowned depending on lifetime guarantees).
1
2
3
4
5
6
final class Controller {
    var handler: (() -> Void)?
    func start() {
        handler = { [weak self] in self?.doWork() }
    }
}

weak vs unowned

  • weak: optional, set to nil automatically when the instance deallocates. Use when the reference may outlive the owner.
  • unowned: non-optional, crashes if accessed after deallocation. Use only when lifetimes are strictly tied.

Copy-on-Write (CoW)

  • Standard collections are value types with CoW: copying is cheap until mutation.
1
2
3
var a = [1, 2, 3]
var b = a            // shares storage
b.append(4)          // triggers copy; `a` remains [1, 2, 3]
  • When building your own CoW types, store reference-backed storage and implement mutating setters that call isKnownUniquelyReferenced.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
struct Buffer {
    final class Storage {
        var values: [Int]
        init(_ values: [Int]) { self.values = values }
    }
    private var storage = Storage([])

    private mutating func ensureUnique() {
        if !isKnownUniquelyReferenced(&storage) {
            storage = Storage(storage.values)
        }
    }

    mutating func append(_ value: Int) {
        ensureUnique()
        storage.values.append(value)
    }
}

Value vs Reference Semantics

  • Prefer value types (struct/enum) for domain models; they compose better and avoid aliasing bugs.
  • Use classes for shared mutable state, UI objects, or when identity matters.
  • Document ownership: who creates, who retains, and who mutates.

Concurrency Considerations

  • CoW reduces accidental sharing across threads, but mutable references inside structs can still race. Mark shared references as Sendable or wrap in actors/locks.
  • @MainActor on UI-bound classes clarifies threading; for data models, prefer pure value types or isolated actors.

Testing

  • Add tests to ensure CoW types copy on mutation (check identity before/after mutation).
  • For retain cycles, use weak expectations in tests: create, nil-out strong refs, and assert deallocation occurs.