Sobes.tech
Middle

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

sobes.tech AI

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

В Swift capture list используется в замыканиях для управления захватом переменных из окружающего контекста, особенно чтобы избежать циклов сильных ссылок (retain cycles).

Capture list записывается в квадратных скобках перед телом замыкания и позволяет указать, как именно переменные будут захвачены — по сильной ссылке (strong), слабой (weak) или без владельца (unowned).

Пример:

class ViewController {
    var name = "Swift"
    lazy var closure: () -> Void = { [weak self] in
        guard let self = self else { return }
        print(self.name)
    }
}

Здесь [weak self] предотвращает удержание сильной ссылки на self внутри замыкания, что помогает избежать утечки памяти.

Таким образом, capture list — это инструмент для контроля жизненного цикла объектов, захватываемых замыканием.

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 используется, когда захваченный объект гарантированно будет существовать пока замыкание используется (т.е. имеет такой же или более длительный жизненный цикл).

sobes.tech AI

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

Capture list в Swift используется для явного управления захватом переменных замыканиями (closures). Это позволяет избежать сильных циклов ссылок, особенно при работе с self в замыканиях, которые используются внутри классов. Основные типы захвата: weak и unowned.

Тип захвата Описание Когда использовать
weak Захватывает переменную как опционал, не увеличивая счетчик ссылок. Её значение может стать nil. Когда объект замыкания может пережить захватываемый объект.
unowned Захватывает переменную без увеличения счетчика ссылок, но считается, что захватываемый объект будет жив, пока существует замыкание. Не опционал. Когда объект замыкания и захватываемый объект имеют одинаковое время жизни, и захватываемый объект не будет nil до завершения замыкания.

Синтаксис capture list:

{ [capture list] (parameters) -> returnType in
    // code
}

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

class MyClass {
    var value = 0

    func doSomethingLater() {
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in
            guard let self = self else { return } // Проверка на nil
            self.value += 1
            print("Value is now \(self.value)")
        }
    }
}

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

class ViewController: UIViewController {
    var button: UIButton!

    override func viewDidLoad() {
        super.viewDidLoad()
        button = UIButton(type: .system)
        button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
    }

    @objc func buttonTapped() {
        // unowned self не требуется в этом случае, так как button существует, пока существует ViewController.
        // Но если бы замыкание могло пережить ViewController, unowned мог бы быть уместен при
        // уверенности, что self не станет nil.
        let action = { [unowned self] in
             self.view.backgroundColor = .blue
        }
        action()
    }
}

Захватывать можно не только self, но и любые другие переменные, доступные в замыкании. Например, чтобы сохранить значение переменной на момент создания замыкания:

func createIncrementer(initialValue: Int) -> () -> Int {
    var currentValue = initialValue
    return { [initialValue] in // Захватываем initialValue по значению
        currentValue += 1
        print("Initial value was \(initialValue)") // initialValue сохраняет свое значение
        return currentValue
    }
}

let incrementer = createIncrementer(initialValue: 5)
print(incrementer()) // Output: Initial value was 5, then 6
print(incrementer()) // Output: Initial value was 5, then 7