Назад к вопросам
Middle
314
questionbank

Что Вы знаете о Capture List в Swift?

Sobes Copilot

Получайте ответы в реальном времени

Ответ от нейросети

sobes.tech AI

Capture List используется в замыканиях (closures) для явного указания, как переменные из окружающей области видимости должны быть захвачены.

Без Capture List замыкание автоматически захватывает переменные либо по ссылке (для классов, функций), либо по значению (для структур, перечислений, кортежей).

Capture List позволяет управлять этим поведением, помогая предотвратить сильные циклы ссылок и сделать замыкание более предсказуемым.

Синтаксис Capture List:

{ [captureListItem] parameters -> returnType in
    // closure body
}

captureListItem может быть:

  • weak variableName: Захватывает переменную по слабой ссылке. Помогает избежать сильных циклов ссылок, особенно при работе с экземплярами классов. variableName внутри замыкания становится опциональным.
  • unowned variableName: Захватывает переменную по безхозной ссылке. Используется, когда замыкание и захватываемый объект имеют одинаковый жизненный цикл или замыкание не переживет захватываемый объект. variableName внутри замыкания не становится опциональным.
  • variableName: Захватывает переменную по значению (хотя для экземпляров классов это все равно будет ссылка на объект, а не копия объекта).

Примеры использования:

  1. Предотвращение сильного цикла ссылок с 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. Использование unowned когда известен жизненный цикл:

    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. Захват по значению (для Int, String, structs и т.д.):

    var count = 0
    let closure = { [count] in
        print("Initial count was \(count)") // Captures the value 0
    }
    
    count = 10
    closure() // Output: Initial count was 0
    

Capture List объявляется между открывающей фигурной скобкой { и параметрами замыкания parameters. Variables в Capture List инициализируются когда замыкание создается. 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.

Правильный выбор между weak и unowned зависит от ожидаемого жизненного цикла объектов. weak используется, когда захваченный объект может стать nil во время жизни замыкания. unowned используется, когда захваченный объект гарантированно будет существовать пока замыкание используется (т.е. имеет такой же или более длительный жизненный цикл).