ARC, weak and unowned

Swift · Advanced · 12 min

Retain cycles are the most common source of memory leaks in Swift apps, especially with closures and delegates.

Swift manages class instances with Automatic Reference Counting. Every strong reference increments a count; when it drops to zero the instance's deinit runs and its memory is freed immediately and deterministically — there's no garbage collector.

The catch is reference cycles: if A holds B strongly and B holds A strongly, neither count reaches zero and both leak. Break cycles with non-owning references:

  • `weak var` — doesn't keep the object alive and becomes nil when it's freed, so it is always an optional var. Use it when the other object may go away first, e.g. a delegate or a child's reference to its parent.
  • `unowned` — doesn't keep the object alive and is non-optional, but accessing it after deallocation traps. Use only when the referenced object always outlives this one.

Closures are reference types and capture self strongly. A class that stores a closure that uses self forms a cycle. Break it with a capture list:

onTap = { [weak self] in
    guard let self else { return }
    self.count += 1
}

Not every closure needs [weak self]: non-escaping closures, and escaping ones that are released after running (like a one-shot Task or animation), don't create lasting cycles.

Value types (structs, enums) aren't reference counted themselves, but any class references they contain are. Use Xcode's Memory Graph Debugger or Instruments' Leaks to find cycles.

Example

final class Owner {
    let name: String
    var pet: Pet?
    var onRename: (() -> Void)?
    init(name: String) { self.name = name }
    deinit { print("Owner \(name) freed") }
}

final class Pet {
    weak var owner: Owner?          // weak breaks the Owner <-> Pet cycle
    deinit { print("Pet freed") }
}

do {
    let o = Owner(name: "Kim")
    let p = Pet()
    o.pet = p
    p.owner = o
    o.onRename = { [weak o] in print(o?.name ?? "gone") }  // no self-cycle
    o.onRename?()                   // Kim
}
// Owner Kim freed
// Pet freed

Remember

  • ARC frees an object when its last strong reference disappears
  • weak becomes nil; unowned traps if used after free
  • Stored closures capturing self need [weak self]

Watch out for

  • Using unowned for an object that can actually be deallocated first, which crashes at run time.

Check yourself

  1. Which choice fits a delegate property that should not keep the delegate alive?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: docs.swift.org/swift-book/documentation/the-swift-programming-language/automaticreferencecounting/