Sobes.tech
Middle

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. variableName inside 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. variableName inside 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:

  1. 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
    
  2. Using unowned when 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
    
  3. 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).

What do you know about Capture List in Swift? — iOS - sobes.tech