Что такое capture list в closure и как с ним работать
Что проверяют
Понимаете ли вы, как замыкание удерживает захваченные объекты при подсчёте ссылок, и умеете ли сознательно выбирать режим захвата. Вопрос почти всегда ведёт к циклам сильных ссылок и утечкам, но хороший ответ шире: захват по значению не менее важен.
Как отвечать
По умолчанию замыкание захватывает переменные по ссылке и держит объекты сильно. Список захвата это перечисление перед словом in, которое меняет эти правила. Режимов три: сильный по умолчанию, слабый [weak self], где значение становится опциональным и обнуляется после освобождения объекта, и [unowned self] без опциональности, но с падением при обращении к уже освобождённому объекту.
Отдельно стоит захват по значению: [value = counter] копирует значение в момент создания замыкания, и дальнейшие изменения переменной внутрь не попадут.
final class Loader {
var onDone: (() -> Void)?
func start() {
onDone = { [weak self] in
guard let self else { return }
self.finish()
}
}
}Правило выбора простое: слабая ссылка нужна там, где объект сам владеет замыканием и цикл замкнётся. Для неубегающих замыканий, которые вызываются и умирают на месте, ослаблять захват незачем.
Где спотыкаются
Ставят слабый захват везде подряд, включая анимации и обработчики, живущие один вызов. Цикла там не бывает, а вот тихий отказ появляется: если объект успел освободиться, блок молча не выполнится, и найти такое по логам трудно.
Берут unowned по привычке, потому что не надо разворачивать опционал. Работает, пока владелец гарантированно переживает замыкание; стоит порядку освобождения измениться, и получается падение, которое воспроизводится через раз.
Не учитывают, что захват по значению делает копию на момент объявления, и удивляются старым данным внутри блока. Обратная ошибка тоже частая: ждут копии для ссылочного типа, хотя копируется лишь сама ссылка, а объект остаётся общим.