При выполнении callback, захватившего self слабо, как guard let влияет на время жизни объекта?
final class Screen {
var onTap: (() -> Void)?
func bind() {
onTap = { [weak self] in
guard let self else { return }
self.render()
}
}
func render() { print("render") }
}
guard let self временно превращает значение из weak-ссылки в обычную сильную локальную ссылку. Если объект существует в момент проверки, он не может быть уничтожен до тех пор, пока эта сильная ссылка нужна внутри callback.
Однако это не означает обязательное сохранение объекта до конца лексического блока: ARC может освободить его после последнего использования сильной локальной ссылки.
ARC автоматизировал управление временем жизни объектов через вставку операций удержания и освобождения ссылок компилятором. Это избавило разработчика от ручного управления retain и release, но не устранило циклы сильных ссылок.
Для разрыва циклов появились слабые формы ссылок. weak не владеет объектом и автоматически становится nil, поэтому безопасен при исчезновении объекта, но требует optional-доступа.
Замыкание часто должно обращаться к владельцу, но не продлевать его жизнь. Поэтому используют [weak self]. Простое обращение к self после этого невозможно без проверки: weak-ссылка может обнулиться в любой момент, когда исчезнет последняя сильная ссылка.
Если объект существует в начале callback, необходимо не допустить его уничтожения между проверкой и последующим использованием. Иначе код может получить нестабильное поведение при конкурирующем освобождении объекта или при сложной цепочке вызовов.
Конструкция guard let self else выполняет чтение weak-ссылки и создаёт из результата сильную локальную ссылку. Если weak-ссылка уже равна nil, выполняется return; если объект существует, локальная ссылка временно владеет им.
Сильная ссылка действует не обязательно до закрывающей фигурной скобки, а как минимум до последнего семантически необходимого использования. Например, после self.render() ARC может освободить объект до фактического выхода из callback, если других сильных ссылок нет.
Это отличается от [self]: захват self без weak обычно создаёт сильную ссылку в замыкании и может образовать цикл, если замыкание хранится самим объектом. В приведённом примере onTap хранится в Screen, поэтому сильный захват self внутри onTap создал бы цикл.
Если требуется гарантировать жизнь объекта до конкретной границы, применяют явную сильную ссылку или withExtendedLifetime. Но продлевать жизнь объекта дольше необходимого следует осторожно: это увеличивает удержание памяти и может маскировать ошибку в архитектуре владения.
Минимальный вариант безопасного callback выглядит так:
При вызове callback weak-ссылка сначала проверяется. Успешная проверка даёт сильную локальную ссылку на время необходимых операций; неуспешная не вызывает обращение к уже уничтоженному объекту.
Экран хранит callback от сетевого слоя, а callback хранится в самом экране. Вариант с сильным захватом self прост, но создаёт цикл Screen → callback → Screen; экран не освобождается после закрытия.
Вариант с [unowned self] не создаёт цикл и не требует optional-проверки, но аварийно завершает программу, если callback сработает после уничтожения экрана. Он допустим только при доказанно более коротком времени жизни callback.
Выбранный вариант — [weak self] и guard let self. После закрытия экрана callback безопасно завершится, если объект уже уничтожен. Если объект ещё существует, локальная сильная ссылка защищает его от уничтожения во время необходимой работы callback. Результат — отсутствие цикла и отсутствие обращения к освобождённой памяти.
guard let self объект до конца callback?Нет, это не безусловная гарантия. Он создаёт сильную ссылку, но ARC может завершить её жизнь после последнего использования self. Если требуется удержание до конкретного конца области, границу нужно выразить явно, например через withExtendedLifetime(self) { ... }.
guard let self отличается от проверки if self != nil?Проверка if self != nil лишь узнаёт состояние weak-ссылки и сама по себе не даёт сильного владения объектом. Между проверкой и последующим чтением объект может исчезнуть. guard let self одновременно читает ссылку и сохраняет результат в сильной локальной ссылке.
[weak self] не всегда устраняет проблему жизненного цикла?Он устраняет цикл именно через этот захват, но callback может храниться другим объектом, который продолжает выполнять работу после уничтожения владельца. Тогда callback будет регулярно просыпаться и завершаться по nil, создавая лишние операции. В такой ситуации следует отменять подписку или задачу при уничтожении владельца, а weak использовать как дополнительную защиту, а не как замену управлению жизненным циклом.