What do you know about Capture List in Swift?
sobes.tech AI
Answer from AI
The Capture List is used in closures to explicitly specify how variables from the surrounding scope should be captured.
Without a Capture List, a closure automatically captures variables either by reference (for classes, functions) or by value (for structures, enumerations, tuples).
The Capture List allows you to control this behavior, helping to prevent strong reference cycles and making closures more predictable.
Syntax of Capture List:
{ [captureListItem] parameters -> returnType in
// closure body
}
captureListItem can be:
weak variableName: Captures the variable weakly. Helps avoid strong reference cycles, especially when working with class instances.variableNameinside the closure becomes optional.unowned variableName: Captures the variable unowned. Used when the closure and the captured object have the same lifecycle or the closure will not outlive the object.variableNameinside the closure is not optional.variableName: Captures the variable by value (though for class instances, this will still be a reference to the object, not a copy).
Examples of usage:
-
Preventing strong reference cycles with
weak:class Person { let name: String var apartment: Apartment? init(name: String) { self.name = name } deinit { print("\(name) is being deinitialized") } } class Apartment { let unit: String var tenant: Person? init(unit: String) { self.unit = unit } deinit { print("Apartment \(unit) is being deinitialized") } } var john: Person? = Person(name: "John") var unit4A: Apartment? = Apartment(unit: "4A") john!.apartment = unit4A unit4A!.tenant = john // Without capture list, this can create a strong reference cycle: // lazy var greeting: () -> String = { // return "Hello, I'm \(self.name)." // self strongly captured // } extension Person { // With capture list, using weak self to break the cycle lazy var greeting: () -> String = { [weak self] in guard let self = self else { return "Hello, I'm no longer here." } return "Hello, I'm \(self.name)." } } print(john!.greeting()) john = nil // Now both Person and Apartment can be deinitialized unit4A = nil -
Using
unownedwhen the lifecycle is known:class Customer { let name: String init(name: String) { self.name = name } deinit { print("\(name) is being deinitialized (Customer)") } var card: CreditCard? } class CreditCard { let number: Int unowned let customer: Customer // Unowned reference back to customer init(number: Int, customer: Customer) { self.number = number self.customer = customer } deinit { print("Card #\(number) is being deinitialized (CreditCard)") } // Closure that should not outlive the customer var processPayment: () -> String = { [unowned self] in return "Processing payment for card #\(self.number) owned by \(self.customer.name)" } } var alice: Customer? = Customer(name: "Alice") alice!.card = CreditCard(number: 1234_5678_9012_3456, customer: alice!) print(alice!.card!.processPayment()) alice = nil // Both Customer and CreditCard are deinitialized -
Capturing by value (for
Int,String,structs, etc.):var count = 0 let closure = { [count] in print("Initial count was \(count)") // Captures the value 0 } count = 10 closure() // Output: Initial count was 0
The Capture List is declared between the opening curly brace { and the closure parameters parameters. Variables in the Capture List are initialized when the closure is created. For reference types captured without weak or unowned, this means the captured variable holds a strong reference to the object at the time of closure creation.
Choosing between weak and unowned depends on the expected lifecycle of the objects. weak is used when the captured object might become nil during the closure's lifetime. unowned is used when the captured object is guaranteed to exist as long as the closure is used (i.e., has the same or a longer lifecycle).