Background:
Closures as a concept originated from functional programming languages and allow blocks of code to be passed around as values. In Swift, closures (similar to lambdas in other languages) were introduced from the beginning. This enables elegant implementations of asynchronous calls, delegation of actions, sorting collections, filtering, etc.
Problem:
Closures capture variables from their surrounding context. If among those variables there are references to class objects, a retain cycle can occur, especially if the closure is stored as a property of that class. It is also important to understand the difference between escaping and non-escaping closures and to be aware of how value capturing works.
Solution:
Swift implements closures as standalone objects; they can capture context, and the owner of the closure must be cautious about the architecture.
Code example:
class Performer { var onComplete: (() -> Void)? func doWork() { onComplete = { print("Completed work!") } } } // Capturing self inside closure class Test { var value = 0 func start() { DispatchQueue.global().async { [weak self] in self?.value = 1 } } }
Key features:
If a closure does not explicitly capture self, can a retain cycle occur?
Yes, if the closure is stored as a strong property of the class and accesses self inside, a retain cycle arises, even if you do not explicitly write self. Use [weak self]/[unowned self] or make the closure local, if possible.
What is the difference between escaping and non-escaping closures?
An escaping closure can be called after the function has completed, and is typically used for asynchronous operations. A non-escaping closure is executed before the function finishes. In escaping capturing, the order of capturing self is different.
Code example:
func asyncWork(completion: @escaping () -> Void) { DispatchQueue.global().async { completion() } }
Can a closure modify the values of captured variables?
Yes, if a closure captures a variable declared as var, it can change its value (for value types). For classes, it will be a reference, and properties can always be modified.
Code example:
var value = 1 let closure = { value += 1 } closure() print(value) // 2
A ViewController saves a closure as a property (e.g., completionHandler) and directly accesses self inside. As a result, a retain cycle occurs: ViewController => closure => ViewController. Disabling the screen does not free the memory.
Pros: The code looks concise and short.
Cons: Memory leaks, the ViewController "hangs" in memory, potential bugs.
Using [weak self] or [unowned self] inside the closure, or storing the closure for no longer than the lifecycle of the object. Review such places in code review.
Pros: Proper resource deallocation, absence of unpredictable leaks.
Cons: [weak self] requires caution during unwrapping, possible implicit crashes with improper usage.