Что такое capture list (список захвата) в замыканиях Swift?

Ответ

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

Базовый синтаксис: { [capture_specification] in ... }

Пример проблемы и решения:

class ViewController {
    var counter = 0
    var closure: (() -> Void)?

    func setupProblem() {
        // ПРОБЛЕМА: Сильный цикл! Замыкание сильно захватывает `self`,
        // а `self` сильно хранит замыкание в свойстве `closure`.
        closure = {
            self.counter += 1 // Сильный захват self
        }
    }

    func setupSolution() {
        // РЕШЕНИЕ: Используем capture list для слабого захвата.
        closure = { [weak self] in
            self?.counter += 1 // self теперь опциональный
        }
    }
}

Основные типы захвата в списке:

  • [weak object]: Захват слабой (weak) ссылки. Объект становится опциональным (T?) внутри замыкания. Используется для разрыва retain cycles.
  • [unowned object]: Захват безусловной (unowned) ссылки. Предполагает, что объект не будет освобожден. Опасен, если предположение неверно.
  • [variable = value]: Захват по значению (value capture). Фиксирует значение переменной на момент создания замыкания.
    var value = 10
    let closure = { [capturedValue = value] in
        print(capturedValue) // Всегда напечатает 10, даже если внешняя `value` изменится
    }
    value = 20
    closure() // Вывод: 10

Правило: Всегда используйте capture list ([weak self] или [unowned self]), когда замыкание, хранимое в свойстве класса, захватывает self.

Ответ 18+ 🔞

А, capture list, ёпта! Это ж та самая штука, которая от циклических ссылок спасает, а то приложение твоё накрывается медным тазом, и ты потом полгода ищешь, где память течёт, блядь.

Смотри, представь себе классическую пиздецовую ситуацию. Есть у тебя ViewController, здоровый такой мужик. И есть у него замыкание, этакая хитрая жопа, которая внутри себя self использует.

class ViewController {
    var counter = 0
    var closure: (() -> Void)? // Вот эта падла, хранитель замыкания

    func setupProblem() {
        // А вот и ПИЗДЕЦ, Карл! Смотри, что происходит:
        closure = {
            self.counter += 1 // Замыкание схватило self мертвой хваткой, как бульдог за яйца
        }
    }
}

Вот смотри на эту хуйню: self (то есть вьюконтроллер) крепко держит свойство closure. А closure, в свою очередь, внутри себя намертво вцепился в self. Они друг друга держат, обнялись, как два пидараса шерстяных, и никуда не отпускают. Это и есть retain cycle, сильный цикл ссылок. Память никогда не освободится, утечка, блядь! Приложение жрёт оперативку, как не в себя, и потом — бац! — креш. Удивление пиздец.

И что делать? А вот тут-то и выходит на сцену наш спаситель — capture list, список захвата, блядь! Чистая магия, чтобы разорвать эти ебучіе объятия.

    func setupSolution() {
        // РЕШЕНИЕ, наконец-то! Используем capture list.
        closure = { [weak self] in // Сказали замыканию: "Хватай self СЛАБО, нежно!"
            self?.counter += 1 // И теперь self — опциональный. Может и nil быть, если вьюконтроллера уже нет.
        }
    }

Вот видишь разницу? Мы в квадратных скобочках [weak self] дали команду: «Э, дружок-замыкание, хватай self, но только слабой ссылкой, не за душицу!». И теперь, если вьюконтроллер умрёт, замыкание не будет держать его на этом свете силой. self внутри станет nil, и всё, пипец, цикл разорван. Красота, бля!

А есть ещё, блядь, [unowned self]. Это как weak, только без страховки. Ты говоришь: «Да похуй, self точно будет жить, пока это замыкание живо!». И внутри замыкания self — не опциональный. Но если ты ошибся, и self таки умер раньше — приложение крашнется, когда замыкание попробует к нему обратиться. Овердохуища риска. Используй, только если на 146% уверен, как в том, что земля плоская.

Ну и фишка третья — захват по значению. Вообще огонь, блядь.

var value = 10
let closure = { [capturedValue = value] in // Захватили значение ВНЕШНЕЙ переменной на момент создания
    print(capturedValue) // И теперь capturedValue — это константа 10, зафиксированная, блядь!
}
value = 20 // Внешней переменной похуй, она теперь 20
closure() // А тут выведет: 10. Потому что capturedValue — это снимок прошлого, как фотка.

Короче, правило жизни простое, запомни его, как «Отче наш»: всегда пиши [weak self] (или [unowned self], если совсем еблан), когда замыкание, которое живёт в свойстве класса (как наша closure), обращается к self. Иначе сам себя за ногу привяжешь, блядь, и будешь в памяти плавать, как Муму в том озере.