Avatar ·

Нужно ли всегда использовать [unowned self] в замыканиях в Swift?

Всем привет! Я начинающий iOS-разработчик, и никак не могу разобраться с циклами сильных ссылок (retain cycles) в замыканиях. Вот простой пример: я делаю сетевой запрос внутри контроллера. ```swift class ProfileViewController: UIViewController { var nameLabel: UILabel! override func viewDidLoad() { super.viewDidLoad() fetchUserProfile() } func fetchUserProfile() { APIClient.shared.getUserProfile { [weak self] profile in self?.nameLabel.text = profile.name } } } ``` Я вижу, что многие используют `[weak self]`, но некоторые пишут `[unowned self]`. В чём разница? Когда нужно использовать `unowned`? Или лучше всегда использовать `weak`? Я слышал, что `unowned` может вызвать краш, если объект уже освобождён. Но тогда зачем он вообще нужен? Может, есть случаи, когда weak недостаточно или неудобно? Пожалуйста, объясните на примерах: в каких ситуациях unowned безопасен, а в каких — нет. И как вообще понять, что замыкание создаст цикл сильных ссылок? Заранее спасибо! <<>>

Когда использовать weak, а когда unowned?

Короткий ответ: нет, не всегда используйте unowned self. На самом деле, в большинстве случаев лучше использовать weak self, а unowned — только в строго определённых ситуациях.

Почему вообще нужны weak и unowned?

Swift использует Automatic Reference Counting (ARC). Когда вы захватываете self внутри замыкания, замыкание сохраняет сильную ссылку на объект. Если объект (например, ViewController) тоже хранит сильную ссылку на это замыкание (через свойство или как обработчик), возникает цикл сильных ссылок (retain cycle) — оба объекта не могут быть освобождены, происходит утечка памяти.

Чтобы разорвать цикл, вы используете weak или unowned:

  • weak — создаёт слабую ссылку. Объект может быть освобождён в любой момент, и ссылка автоматически станет nil. Поэтому вы всегда работаете с опционалом (self?).
  • unowned — тоже слабая ссылка, но предполагается, что объект никогда не станет nil во время выполнения замыкания. Если он всё-таки станет nil — приложение упадёт с ошибкой.

Когда unowned безопасен?

Только когда вы абсолютно уверены, что объект будет жив всё время, пока существует замыкание. Классический пример:

class MyManager {
    var onComplete: (() -> Void)?
    
    func setup() {
        // Замыкание живёт только пока жив self (MyManager)
        onComplete = { [unowned self] in
            self.doSomething() // self точно существует, т.к. замыкание — свойство self
        }
    }
    
    func doSomething() { print("Done") }
}

Здесь замыкание — это свойство self. Оно не может существовать дольше, чем self. Поэтому unowned безопасен.

Когда unowned ОПАСЕН?

Когда объект может быть освобождён до вызова замыкания. Самый частый случай — асинхронные операции:

class ProfileViewController: UIViewController {
    func fetchData() {
        APIClient.shared.getData { [unowned self] result in
            // !!! КРАШ !!! если пользователь ушёл с экрана, self уже nil
            self.updateUI(result)
        }
    }
}

Пользователь нажал "Назад", контроллер уничтожился, а замыкание всё ещё ждёт ответа от сервера. Когда ответ приходит — self уже нет, приложение падает.

Правило большого пальца

  • Всегда начинайте с weak self — это безопасно.
  • Используйте unowned ТОЛЬКО когда:
    • Замыкание — это свойство самого объекта (например, обработчик в классе).
    • Вы точно знаете, что объект переживёт замыкание (например, в синхронных вызовах).
    • Вы готовы к падению приложения в случае ошибки.

В 95% случаев weak self — правильный выбор. Не бойтесь писать self? — это безопасно и читаемо.

<<>>

Альтернативный взгляд: используйте unowned только для гарантированно синхронных замыканий

Есть ещё один удобный паттерн — когда замыкание вызывается синхронно, прямо внутри метода. Например, методы работы с коллекциями:

class Calculator {
    var values = [1, 2, 3]
    
    func process() {
        values.forEach { [unowned self] value in
            // forEach выполняется синхронно, self гарантированно жив
            self.addValue(value * 2)
        }
    }
    
    func addValue(_ val: Int) { values.append(val) }
}

Здесь forEach выполняется сразу, не откладываясь. self не может быть освобождён во время итерации, поэтому unowned безопасен и избавляет от лишних опционалов.

Но помните: если в будущем кто-то изменит forEach на асинхронный вызов — код упадёт. Поэтому лично я предпочитаю weak везде, где есть хоть малейшая неопределённость.