Closures can keep objects alive by holding strong references to them, causing memory to not be freed. This is called a retain cycle.
Closures causing retain cycles in Swift
class MyClass { var closure: (() -> Void)? func setup() { closure = { print(self) } } }
Closures capture variables from their surrounding context, including self.
If self holds a strong reference to the closure, and the closure captures self strongly, a retain cycle happens.
closure captures self strongly, and self owns closure.class MyClass { var closure: (() -> Void)? func setup() { closure = { print(self) } } }
[weak self] breaks the retain cycle by capturing self weakly inside the closure.class MyClass { var closure: (() -> Void)? func setup() { closure = { [weak self] in guard let self = self else { return } print(self) } } }
[unowned self] also breaks the retain cycle but assumes self will not be nil when closure runs.class MyClass { var closure: (() -> Void)? func setup() { closure = { [unowned self] in print(self) } } }
This example shows how to use a weak reference in a closure to avoid a retain cycle. When person is set to nil, the object is freed properly.
class Person { let name: String var greeting: (() -> Void)? init(name: String) { self.name = name } func sayHello() { greeting = { [weak self] in guard let self = self else { return } print("Hello, my name is \(self.name)") } } } var person: Person? = Person(name: "Alex") person?.sayHello() person?.greeting?() person = nil print("Person is now nil")
Always use [weak self] or [unowned self] in closures that capture self to avoid retain cycles.
Use weak when self can become nil during closure lifetime.
Use unowned when you are sure self will not be nil when closure runs.
Closures capturing self strongly can cause retain cycles and memory leaks.
Use capture lists like [weak self] or [unowned self] to break retain cycles.
Understanding retain cycles helps keep your app memory efficient and bug-free.
Practice
Solution
Step 1: Understand closure capturing behavior
Closures in Swift capture variables, including self, strongly by default.Step 2: Recognize impact on memory
If a closure captures self strongly and self holds the closure, neither can be released, causing a retain cycle.Final Answer:
Closures capture self strongly, preventing deallocation -> Option AQuick Check:
Strong capture causes retain cycle = B [OK]
- Thinking closures do not capture variables
- Assuming closures run on background threads
- Believing closures automatically break retain cycles
Solution
Step 1: Recall capture list syntax
In Swift, capture lists are declared inside square brackets before the closure body.Step 2: Identify correct weak capture syntax
The correct syntax is [weak self] inside the capture list, followed by the closure code.Final Answer:
let closure = { [weak self] in print(self) } -> Option CQuick Check:
Capture list uses [weak self] = C [OK]
- Using parentheses instead of brackets for capture list
- Writing weak self without brackets
- Swapping order to [self weak]
class MyClass {
var closure: (() -> Void)?
func setup() {
closure = {
print(self)
}
}
}
let obj = MyClass()
obj.setup()
What is the main problem with this code?Solution
Step 1: Analyze closure capturing self
The closure assigned to the property captures self strongly by default.Step 2: Understand retain cycle effect
Since self holds closure and closure holds self, neither is released, causing a retain cycle and memory leak.Final Answer:
It causes a retain cycle because closure captures self strongly -> Option DQuick Check:
Strong capture causes retain cycle = A [OK]
- Assuming no retain cycle without capture list
- Thinking it causes syntax error
- Believing closure prints nil
class ViewController {
var completion: (() -> Void)?
func configure() {
completion = {
print(self)
}
}
}Solution
Step 1: Identify cause of retain cycle
The closure captures self strongly, causing retain cycle with the property.Step 2: Apply weak capture to break cycle
Adding [weak self] in the capture list makes self a weak reference, breaking the cycle.Final Answer:
Use [weak self] in the closure capture list -> Option BQuick Check:
Weak capture breaks retain cycle = D [OK]
- Removing closure property instead of fixing capture
- Making completion strong reference (already strong)
- Calling configure multiple times doesn't fix cycle
class Worker {
var task: (() -> Void)?
func start() {
task = { [unowned self] in
print("Working with \(self)")
}
}
}
var w: Worker? = Worker()
w?.start()
w = nil
What happens when w is set to nil and why?Solution
Step 1: Understand unowned capture behavior
Unowned references do not increase retain count but expect self to exist while closure runs.Step 2: Analyze deallocation when w = nil
Setting w to nil releases Worker instance; no retain cycle exists because closure holds unowned self.Final Answer:
Worker instance is deallocated safely; unowned does not retain self -> Option AQuick Check:
Unowned capture allows safe deallocation = A [OK]
- Confusing unowned with weak causing runtime crash
- Assuming strong capture causes leak here
- Thinking closure runs automatically on deallocation
Ready to prove it?
Pass the test and this pattern moves unknown
