Bird
Raised Fist0
Swiftprogramming~15 mins

Strong reference cycles between classes in Swift - Deep Dive

Choose your learning style10 modes available
Overview - Strong reference cycles between classes
What is it?
Strong reference cycles happen when two or more class instances hold strong references to each other, preventing them from being removed from memory. This means the memory they use is never freed, causing a memory leak. In Swift, this often occurs when classes reference each other directly or indirectly without breaking the cycle.
Why it matters
Without understanding and managing strong reference cycles, your app can use more and more memory over time, slowing down or crashing. This wastes device resources and leads to poor user experience. Managing these cycles ensures your app runs smoothly and efficiently by freeing memory when objects are no longer needed.
Where it fits
Before learning this, you should understand Swift classes, how memory management works with Automatic Reference Counting (ARC), and the difference between strong, weak, and unowned references. After this, you can learn about memory optimization, closures capturing self, and advanced ARC debugging techniques.
Mental Model
Core Idea
A strong reference cycle happens when two objects keep each other alive by holding strong references, so neither can be freed from memory.
Think of it like...
It's like two friends holding hands tightly in a circle, so neither can leave the group because they are both holding on to each other.
┌───────────────┐       ┌───────────────┐
│   Object A    │──────▶│   Object B    │
│ (strong ref)  │       │ (strong ref)  │
└───────────────┘◀──────┘───────────────┘

Both objects hold strong references to each other, creating a loop.
Build-Up - 7 Steps
1
FoundationUnderstanding ARC and Strong References
🤔
Concept: Introduce Automatic Reference Counting and how strong references keep objects alive.
Swift uses ARC to manage memory automatically. Every time you create a class instance, ARC keeps track of how many strong references point to it. As long as there is at least one strong reference, the object stays in memory. When the count drops to zero, ARC frees the memory.
Result
Objects stay alive while referenced strongly and are removed when no strong references remain.
Understanding ARC is key because strong references are the default way Swift keeps objects alive, which can lead to cycles if not managed.
2
FoundationClasses Holding Strong References to Each Other
🤔
Concept: Show how two class instances can hold strong references to each other, causing a cycle.
Imagine two classes, Person and Apartment. If Person has a strong reference to Apartment, and Apartment has a strong reference back to Person, neither can be freed because each keeps the other alive.
Result
Neither object is deallocated, causing a memory leak.
Recognizing that mutual strong references create cycles helps spot where memory leaks can happen.
3
IntermediateBreaking Cycles with Weak References
🤔Before reading on: do you think weak references increase or do not increase the reference count? Commit to your answer.
Concept: Introduce weak references that do not increase the reference count and can break cycles.
A weak reference points to an object without increasing its reference count. If the object is deallocated, the weak reference automatically becomes nil. By making one side of the cycle weak, you allow ARC to free the objects properly.
Result
Memory leaks are prevented because the cycle is broken.
Knowing that weak references do not keep objects alive is crucial to breaking strong reference cycles.
4
IntermediateUsing Unowned References to Break Cycles
🤔Before reading on: do you think unowned references can become nil automatically like weak references? Commit to your answer.
Concept: Explain unowned references which do not increase reference count and are non-optional, assuming the referenced object always exists.
Unowned references are like weak references but are non-optional and expected to always have a value during their lifetime. They do not increase the reference count and help break cycles when one object outlives the other.
Result
Cycles are broken without optional unwrapping, but unsafe if the referenced object is deallocated first.
Understanding unowned references helps manage cycles when you know the lifetime relationship between objects.
5
IntermediateCommon Cycle Patterns in Delegation
🤔Before reading on: do you think delegate properties should be strong or weak references? Commit to your answer.
Concept: Show how delegate patterns often cause cycles and how weak references solve them.
In delegation, an object holds a reference to its delegate. If both are classes and the delegate property is strong, a cycle forms. Making the delegate reference weak breaks the cycle because the delegate does not keep the owner alive.
Result
Delegation works without memory leaks.
Recognizing delegation as a common source of cycles guides proper use of weak references.
6
AdvancedDetecting Cycles with Memory Debugging Tools
🤔Before reading on: do you think Xcode's memory graph debugger can show strong reference cycles? Commit to your answer.
Concept: Teach how to use Xcode's memory graph debugger to find and fix cycles.
Xcode provides a memory graph tool that visualizes objects and their references. It highlights cycles so you can identify which references cause leaks. Using this tool helps find hidden cycles in complex code.
Result
You can detect and fix memory leaks caused by cycles.
Knowing how to use debugging tools is essential for maintaining healthy memory in real projects.
7
ExpertUnexpected Cycles from Closures Capturing Self
🤔Before reading on: do you think closures capture self strongly by default? Commit to your answer.
Concept: Explain how closures can capture self strongly, creating hidden cycles with classes.
Closures capture variables they use, including self, strongly by default. If a class instance holds a closure that captures self, a cycle forms because the closure keeps the instance alive and vice versa. Using capture lists like [weak self] breaks this cycle.
Result
Avoids subtle memory leaks caused by closures.
Understanding closure capture behavior prevents hard-to-find cycles in asynchronous or callback code.
Under the Hood
Swift uses Automatic Reference Counting (ARC) to track how many strong references point to each class instance. Each strong reference increments the count, and each removal decrements it. When the count reaches zero, ARC deallocates the instance. Weak and unowned references do not increment the count, so they don't keep the instance alive. A strong reference cycle happens when objects reference each other strongly, so their counts never reach zero, causing memory leaks.
Why designed this way?
ARC was designed to automate memory management without a garbage collector, providing predictable performance and safety. Strong references as default simplify ownership understanding. Weak and unowned references were introduced to handle cycles and lifetime relationships explicitly, balancing safety and flexibility. Alternatives like garbage collection were rejected for performance and control reasons.
┌───────────────┐       ┌───────────────┐
│   Object A    │──────▶│   Object B    │
│ (strong ref)  │       │ (strong ref)  │
└───────────────┘◀──────┘───────────────┘

Reference counts never reach zero due to mutual strong references.

With weak reference:

┌───────────────┐       ┌───────────────┐
│   Object A    │──────▶│   Object B    │
│ (strong ref)  │       │ (weak ref)   │
└───────────────┘       └───────────────┘

Object B can be deallocated when no other strong refs exist.
Myth Busters - 4 Common Misconceptions
Quick: Do weak references keep objects alive? Commit to yes or no.
Common Belief:Weak references keep objects alive just like strong references.
Tap to reveal reality
Reality:Weak references do NOT increase the reference count and do NOT keep objects alive. They become nil automatically when the object is deallocated.
Why it matters:Believing weak references keep objects alive leads to misunderstanding how to break cycles, causing persistent memory leaks.
Quick: Can unowned references become nil automatically? Commit to yes or no.
Common Belief:Unowned references behave like weak references and become nil when the object is deallocated.
Tap to reveal reality
Reality:Unowned references do NOT become nil automatically and are non-optional. Accessing an unowned reference after deallocation causes a runtime crash.
Why it matters:Misusing unowned references can cause app crashes, so understanding their lifetime assumptions is critical.
Quick: Do closures capture self strongly by default? Commit to yes or no.
Common Belief:Closures do not capture self strongly unless explicitly told to.
Tap to reveal reality
Reality:Closures capture self strongly by default, which can create hidden strong reference cycles if not handled with capture lists.
Why it matters:Ignoring closure capture behavior leads to subtle memory leaks that are hard to detect and fix.
Quick: Does making all references weak solve all memory leaks? Commit to yes or no.
Common Belief:Making all references weak is a good way to avoid memory leaks.
Tap to reveal reality
Reality:Making all references weak can cause objects to be deallocated too early, leading to crashes or unexpected behavior.
Why it matters:Overusing weak references breaks object ownership and program logic, causing instability.
Expert Zone
1
Strong reference cycles can form not only between two objects but also in complex graphs involving multiple objects and closures.
2
Unowned references require careful lifetime guarantees; they are safe only when the referenced object outlives the owner, which is often subtle in asynchronous code.
3
Swift's memory graph debugger can show cycles but may not reveal cycles caused by closures capturing self unless you understand how to interpret the graph.
When NOT to use
Avoid using weak or unowned references when the lifetime of the referenced object is uncertain or when the reference must always be valid. Instead, use strong references and redesign ownership or use value types like structs to avoid cycles.
Production Patterns
In production, developers use weak delegates to avoid cycles, capture lists in closures to prevent leaks, and tools like Instruments and Xcode memory graph debugger to detect cycles. They also design ownership hierarchies carefully to minimize cycles and use protocols and value types to reduce reference complexity.
Connections
Garbage Collection
Alternative memory management approach
Understanding ARC's strong reference cycles highlights why garbage collection uses different strategies to detect and clean cycles automatically.
Observer Pattern
Common source of reference cycles
Knowing how observers hold references helps understand why weak references are essential to avoid cycles in event-driven designs.
Social Network Friendships
Mutual references analogy
Just like mutual friendships can create tight social circles, mutual strong references create cycles that trap objects in memory.
Common Pitfalls
#1Creating a delegate property as a strong reference causing a cycle.
Wrong approach:class ViewController { var delegate: SomeDelegate? // strong by default }
Correct approach:class ViewController { weak var delegate: SomeDelegate? // breaks cycle }
Root cause:Not realizing that delegate properties should be weak to avoid cycles.
#2Using unowned reference when the referenced object might be deallocated first.
Wrong approach:class Child { unowned let parent: Parent init(parent: Parent) { self.parent = parent } } // Parent might be deallocated before Child
Correct approach:class Child { weak var parent: Parent? init(parent: Parent) { self.parent = parent } }
Root cause:Assuming unowned is always safe without verifying object lifetimes.
#3Not using capture lists in closures, causing self to be captured strongly.
Wrong approach:class MyClass { var closure: (() -> Void)? func setup() { closure = { print(self) } // strong capture } }
Correct approach:class MyClass { var closure: (() -> Void)? func setup() { closure = { [weak self] in print(self) } // weak capture } }
Root cause:Forgetting closures capture self strongly by default.
Key Takeaways
Strong reference cycles occur when objects hold strong references to each other, preventing ARC from freeing memory.
Weak and unowned references break cycles by not increasing reference counts, but they have different safety guarantees.
Closures capture self strongly by default, which can create hidden cycles if not handled with capture lists.
Using tools like Xcode's memory graph debugger helps detect and fix strong reference cycles in real projects.
Proper understanding and management of reference types and ownership is essential for efficient and crash-free Swift apps.

Practice

(1/5)
1.

What causes a strong reference cycle between two Swift classes?

Consider two classes holding references to each other.

easy
A. Neither class holds any reference to the other.
B. One class holds a weak reference and the other a strong reference.
C. Both classes hold strong references to each other, preventing deallocation.
D. Both classes hold weak references to each other.

Solution

  1. Step 1: Understand strong references

    Strong references keep objects alive by increasing their reference count.
  2. Step 2: Identify cycle condition

    If two objects hold strong references to each other, neither can be deallocated, causing a cycle.
  3. Final Answer:

    Both classes hold strong references to each other, preventing deallocation. -> Option C
  4. Quick Check:

    Strong references + mutual hold = cycle [OK]
Hint: Strong references both ways cause cycles; use weak/unowned [OK]
Common Mistakes:
  • Thinking weak references cause cycles
  • Assuming one strong and one weak causes cycle
  • Confusing unowned with strong references
2.

Which of the following is the correct way to declare a weak reference in Swift to avoid a strong reference cycle?

class Person {
    var apartment: Apartment?
}

class Apartment {
    // What should go here?
    var tenant: ???
}
easy
A. var tenant: Person
B. weak var tenant: Person?
C. unowned var tenant: Person
D. var tenant: Person?

Solution

  1. Step 1: Identify weak reference syntax

    In Swift, weak references are declared with the keyword weak and must be optional.
  2. Step 2: Apply to property

    So, weak var tenant: Person? is correct to avoid strong cycles.
  3. Final Answer:

    weak var tenant: Person? -> Option B
  4. Quick Check:

    Weak + optional = correct syntax [OK]
Hint: Weak references must be optional and use 'weak' keyword [OK]
Common Mistakes:
  • Declaring weak without optional type
  • Using unowned when weak is needed
  • Forgetting 'weak' keyword
3.

What will be the output of the following Swift code?

class Person {
    let name: String
    var apartment: Apartment?
    init(name: String) { self.name = name }
    deinit { print("Person \(name) is being deinitialized") }
}

class Apartment {
    let unit: String
    weak var tenant: Person?
    init(unit: String) { self.unit = unit }
    deinit { print("Apartment \(unit) is being deinitialized") }
}

var john: Person? = Person(name: "John")
var apt: Apartment? = Apartment(unit: "4A")
john!.apartment = apt
apt!.tenant = john
john = nil
apt = nil
medium
A. Person John is being deinitialized Apartment 4A is being deinitialized
B. No output, memory leak occurs
C. Only Person John is being deinitialized
D. Only Apartment 4A is being deinitialized

Solution

  1. Step 1: Analyze references

    Person holds strong reference to Apartment; Apartment holds weak reference to Person.
  2. Step 2: Check deallocation

    Setting both variables to nil breaks strong references, so both objects deinit and print messages.
  3. Final Answer:

    Person John is being deinitialized Apartment 4A is being deinitialized -> Option A
  4. Quick Check:

    Weak reference avoids cycle, both deinit [OK]
Hint: Weak reference breaks cycle; both objects deinit when nil [OK]
Common Mistakes:
  • Expecting memory leak output
  • Thinking only one object deinitializes
  • Ignoring weak reference effect
4.

Identify the error in the following Swift code that causes a strong reference cycle:

class Customer {
    let name: String
    var card: CreditCard?
    init(name: String) { self.name = name }
    deinit { print("Customer \(name) is being deinitialized") }
}

class CreditCard {
    let number: UInt64
    var customer: Customer?
    init(number: UInt64) { self.number = number }
    deinit { print("CreditCard \(number) is being deinitialized") }
}

var alice: Customer? = Customer(name: "Alice")
var card: CreditCard? = CreditCard(number: 1234567890123456)
alice!.card = card
card!.customer = alice
alice = nil
card = nil
medium
A. CreditCard's customer property should be declared weak to avoid cycle.
B. Customer's card property should be declared weak to avoid cycle.
C. Both properties should be unowned to avoid cycle.
D. No error; code works fine without changes.

Solution

  1. Step 1: Identify reference types

    Both Customer.card and CreditCard.customer are strong references by default.
  2. Step 2: Find cycle cause

    Since both hold strong references, they create a cycle preventing deallocation.
  3. Step 3: Fix cycle

    Declaring CreditCard.customer as weak var customer: Customer? breaks the cycle.
  4. Final Answer:

    CreditCard's customer property should be declared weak to avoid cycle. -> Option A
  5. Quick Check:

    Weak reference breaks cycle [OK]
Hint: Make one side weak to break cycle, usually the back reference [OK]
Common Mistakes:
  • Making both references weak
  • Making the wrong property weak
  • Ignoring the cycle causing deallocation failure
5.

You have two classes, Owner and Car. The Owner has a strong reference to Car, and Car has a strong reference to Owner. You want to avoid a strong reference cycle but also ensure that Car always has an Owner during its lifetime. How should you declare the reference from Car to Owner?

hard
A. Declare the reference as weak var owner: Owner (non-optional)
B. Declare the reference as unowned var owner: Owner
C. Declare the reference as var owner: Owner (strong reference)
D. Declare the reference as weak var owner: Owner?

Solution

  1. Step 1: Understand ownership requirements

    Since Car must always have an Owner during its lifetime, the reference cannot be optional.
  2. Step 2: Choose reference type

    unowned references are non-optional and do not increase reference count, avoiding cycles.
  3. Step 3: Apply declaration

    Declare unowned var owner: Owner in Car to avoid cycle and ensure non-optional reference.
  4. Final Answer:

    Declare the reference as unowned var owner: Owner -> Option D
  5. Quick Check:

    Unowned + non-optional = correct for guaranteed lifetime [OK]
Hint: Use unowned for non-optional back references to avoid cycles [OK]
Common Mistakes:
  • Using weak with non-optional type (invalid)
  • Using strong reference causing cycle
  • Using weak optional when non-optional needed
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard