Ответ
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, прямо в начале замыкания, в квадратных скобочках. Это как инструкция: «Эй, замыкание, вот эту переменную ты хватай так, а эту — эдак».
Основные приёмы, два богатыря:
-
[weak self]— это наш осторожный богатырь. Хватаетselfза полу, но так, чтоб в любой момент отпустить, если того в тартарары отправят. Ссылка становится слабой и опциональной. Безопасно, но приходится потом разворачивать.closure = { [weak self] in self?.doSomething() // `self` стал Optional. Проверили — живёт? Работаем. Нет? Ну и хуй с ним. } -
[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, а то будет не «Муму», а «Бдыщь!» и креш в логах.