Нужно ли всегда использовать [unowned self] в замыканиях в Swift?
Краткий ответ: нет, не всегда. Выбор между weak, unowned и отсутствием модификатора зависит от времени жизни объектов.
Давайте разберёмся по порядку. Retain cycle возникает, когда два объекта держат сильные ссылки друг на друга, и ни один из них не может быть освобождён. В вашем примере:
ViewControllerдержит сильную ссылку наNetworkManagerNetworkManagerдержит сильную ссылку на замыкание (черезcompletion)- Замыкание держит сильную ссылку на
self(ViewController)
Это классический треугольный retain cycle. Если вы не используете [weak self] или [unowned self], ViewController никогда не будет освобождён, даже если вы его закроете.
Когда использовать [unowned self]?
Используйте unowned, когда вы абсолютно уверены, что self будет жить как минимум столько же, сколько и замыкание. Например:
class MyViewController: UIViewController {
let label = UILabel()
func setupLabel() {
// Замыкание выполняется синхронно, self точно жив
let action = { [unowned self] in
self.label.text = "Hello"
}
action()
}
}
Или когда замыкание вызывается внутри метода того же объекта:
class DataProcessor {
var data: [Int] = []
func process() {
someAsyncOperation { [unowned self] in
// self точно жив, пока выполняется process()
self.data.append(1)
}
}
}
Важно: если self будет освобождён до выполнения замыкания, приложение упадёт с EXC_BAD_ACCESS.
Когда использовать [weak self]?
Используйте weak в большинстве случаев, особенно когда:
- Замыкание может пережить self (асинхронные операции, сетевые запросы)
- self — UIViewController, который может быть закрыт
- Вы не уверены в порядке освобождения объектов
class ViewController: UIViewController {
func loadData() {
networkManager.fetchData { [weak self] data in
guard let self = self else { return }
self.updateUI(with: data)
}
}
}
Да, guard let self = self выглядит громоздко, но это безопасно. В Swift 4.2+ можно использовать короткую запись:
networkManager.fetchData { [weak self] data in
self?.updateUI(with: data)
}
Но будьте осторожны: если нужно выполнить несколько действий с self, лучше использовать guard, чтобы не писать self? каждый раз.
Когда можно вообще не использовать weak/unowned?
Только если замыкание не захватывает self или захватывает его как слабую ссылку по умолчанию. Например:
// Нет захвата self — нет проблем
let closure = {
print("Hello")
}
// Захватываем свойство, а не self
class MyClass {
var value = 5
func doSomething() {
let closure = { [value] in
print(value) // захвачена копия value
}
}
}
Best Practices:
- По умолчанию используйте
weak— это безопаснее всего - Используйте
unownedтолько когда уверены в жизненном цикле — например, в синхронных замыканиях или в замыканиях, которые гарантированно выполняются до освобождения self - Избегайте
unownedв UI-контроллерах — они могут быть освобождены в любой момент (пользователь закрыл экран) - Проверяйте retain cycles с помощью инструментов Xcode — Debug Memory Graph
В вашем примере с NetworkManager обязательно используйте [weak self], потому что сетевой запрос может завершиться после того, как ViewController уже закрыт.
Альтернативный подход: используйте [weak self] с опциональной цепочкой
Многие разработчики предпочитают не писать guard let self = self, а использовать опциональную цепочку:
networkManager.fetchData { [weak self] data in
self?.updateUI(with: data)
self?.anotherMethod()
self?.yetAnotherMethod()
}
Это короче и читается легче. Но есть нюанс: если self станет nil после первого вызова, остальные методы не выполнятся. Для простых случаев это нормально, но если логика требует атомарности (все методы должны выполниться или ни один), лучше использовать guard.
Ещё один совет: можно создать локальную strong ссылку внутри замыкания, чтобы гарантировать, что self жив на время выполнения:
networkManager.fetchData { [weak self] data in
guard let strongSelf = self else { return }
strongSelf.updateUI(with: data)
strongSelf.anotherMethod()
}
Некоторые разработчики называют это "weak-strong dance". Это безопасно, потому что strongSelf — локальная переменная, и она будет жить только внутри замыкания.
В современных версиях Swift (5.0+) также можно использовать [weak self] в сочетании с guard let self, что стало идиоматичным:
networkManager.fetchData { [weak self] data in
guard let self else { return }
// self теперь strong ссылка внутри этого блока
self.updateUI(with: data)
}
Этот синтаксис (без повторения self в guard) появился в Swift 5.0 и делает код чище.