Что такое capture list в замыканиях Swift и зачем она нужна?

Ответ

Capture list (список захвата) — это синтаксическая конструкция в начале замыкания, которая явно определяет как переменные из окружающего контекста захватываются и хранятся внутри замыкания. Это основной инструмент для предотвращения retain cycles (циклов сильных ссылок).

Проблема (retain cycle):

class MyViewController {
    var closure: (() -> Void)?

    func problematicSetup() {
        closure = { 
            self.doSomething() // Сильный захват `self` -> ЦИКЛ!
        }
    }
    // `self` удерживает `closure`, а `closure` удерживает `self`.
}

Решение с использованием capture list:

func correctSetup() {
    // [weak self] создает слабую (optional) ссылку
    closure = { [weak self] in
        self?.doSomething() // `self` стал Optional
    }

    // [unowned self] создает unowned (non-optional, но небезопасную) ссылку
    closure = { [unowned self] in
        self.doSomething() // Краш, если `self` освобожден
    }
}

Ключевые правила выбора:

  • Используйте [weak capturedInstance], если захваченный объект может стать nil (наиболее безопасный и распространенный способ).
  • Используйте [unowned capturedInstance], только если время жизни объекта гарантированно превышает время жизни замыкания (редкие случаи, риск краша).
  • Можно захватывать значения по значению: [x, y] — создается локальная копия.
  • Можно давать новые имена: [weak weakSelf = self].

Пример с несколькими захватами:

networkService.fetchData { [weak self, unowned database, count = totalAttempts] result in
    guard let self = self else { return }
    self.handle(result, from: database)
    print("Attempt: (count)")
}

Зачем это нужно: Без правильного capture list замыкания, захватывающие self или другие ссылочные типы, могут создавать циклы удержания памяти, приводящие к утечкам.

Ответ 18+ 🔞

Да ты посмотри, какой народ-то безмозглый, блядь! Сидят, пишут замыкания, а про capture list нихуя не знают. Ну как так-то, а? Это ж основа, сука, чтоб память не текла, как из дырявого ведра!

Смотри, вот тебе классика жанра, прям как в том анекдоте про Герасима и Муму, только с классами. Два друга, self и closure, так друг за друга уцепились, что навеки вместе, в аду памяти. Цикл сильных ссылок, ёпта!

class MyViewController {
    var closure: (() -> Void)?

    func problematicSetup() {
        closure = { 
            self.doSomething() // Сильный захват `self` -> ЦИКЛ!
        }
    }
    // `self` удерживает `closure`, а `closure` удерживает `self`.
}

Вот они обнимашки, блядь! Один другого не отпускает, и оба на свалку истории не могут — память не освободится, пока кто-то первым не сдохнет. А никто не сдохнет! Замкнутый круг, пиздец.

Так, а теперь слушай сюда, как умные дяди делают. Есть волшебная штука — capture list, прямо в начале замыкания, в квадратных скобочках. Это как инструкция: «Эй, замыкание, вот эту переменную ты хватай так, а эту — эдак».

Основные приёмы, два богатыря:

  1. [weak self] — это наш осторожный богатырь. Хватает self за полу, но так, чтоб в любой момент отпустить, если того в тартарары отправят. Ссылка становится слабой и опциональной. Безопасно, но приходится потом разворачивать.

    closure = { [weak self] in
        self?.doSomething() // `self` стал Optional. Проверили — живёт? Работаем. Нет? Ну и хуй с ним.
    }
  2. [unowned self] — а это богатырь-самоубийца, блядь. Кричит: «Да я его насквозь знаю, он меня никогда не кинет!». Хватает self мёртвой хваткой, но НЕ создаёт сильную ссылку. А если self всё-таки освободится раньше, и замыкание попробует к нему обратиться — КРАШ, ёперный театр! В рот меня чих-пых! Используй, только если на 146% уверен в порядке смерти.

    closure = { [unowned self] in
        self.doSomething() // Краш, если `self` освобожден. Надежда — дура.
    }

Так какого хуя выбирать? Правило простое, как палка:

  • [weak ...] — твой лучший друг в 99% случаев. Особенно в асинхронщине, с сетевыми запросами, таймерами. Безопасно, предсказуемо.
  • [unowned ...] — только если время жизни объекта ДЕЙСТВИТЕЛЬНО гарантированно дольше, чем у замыкания. Рискованно, но иногда оправданно для микропроизводительности.

А ещё можно не только ссылки ослаблять! Можно, сука, значения захватить, прям копию сделать на момент создания замыкания:

networkService.fetchData { [weak self, unowned database, count = totalAttempts] result in
    guard let self = self else { return } // Развернули weak self
    self.handle(result, from: database) // Используем unowned database
    print("Attempt: (count)") // Используем захваченное по значению `count`
}

Видишь? count захватили как константу, database — как unowned (предполагаем, что база живёт вечно), а self — как weak, с последующей проверкой.

Итог, блядь: Без capture list твои замыкания — это как тот немой Герасим, который может только «Муму» мычать, когда его спрашивают, кто насрал на крыше. Ни объяснить, ни отпустить. А с ним — ты становишься полноправным архитектором памяти. Захотел — отпустил, захотел — привязал. Главное — не перепутай weak с unowned, а то будет не «Муму», а «Бдыщь!» и креш в логах.