[unowned self] в Swift: когда он нужен, а когда опасен?

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Вечная дилемма: ставить [unowned self] или нет?

Если вы когда-нибудь писали на Swift и сталкивались с замыканиями (closures), то наверняка задавались вопросом: "А нужно ли мне писать [unowned self] внутри каждого замыкания, где я использую self?".

Это один из самых популярных вопросов на Stack Overflow (десятки тысяч просмотров), и не зря. В 2025 году, с появлением новых фич Swift (например, улучшенного анализатора生命周期 и более строгих проверок компилятора), ответ стал еще более нюансированным.

В этой статье мы не просто дадим сухой ответ "да" или "нет". Мы разберем, как на самом деле работает управление памятью в Swift, когда [unowned self] — это спасение, а когда — бомба замедленного действия, и какие лучшие практики использовать в современных проектах.

Поехали! 🚀

Коротко об ARC и retain cycles (для новичков)

Прежде чем говорить о [unowned self], давайте вспомним, как работает Automatic Reference Counting (ARC).

ARC — это механизм подсчета ссылок. Каждый объект в Swift (экземпляр класса) имеет счетчик ссылок. Когда вы создаете новую ссылку на объект (например, присваиваете его свойству или кладете в массив), счетчик увеличивается. Когда ссылка исчезает — уменьшается. Когда счетчик доходит до нуля, объект уничтожается.

Проблема возникает, когда два объекта ссылаются друг на друга (циклическая ссылка). Представьте:

class Parent {
    var child: Child?
}

class Child {
    var parent: Parent? // Сильная ссылка обратно на Parent
}

Если создать пару Parent и Child и установить обе ссылки, они будут держать друг друга вечно. ARC никогда не обнулит их счетчики. Это называется retain cycle (цикл сильных ссылок).

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

Weak vs Unowned: в чем разница?

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

weak self

  • Всегда optional (self?).
  • Ссылка не увеличивает счетчик ARC.
  • Когда объект уничтожается, weak ссылка автоматически становится nil.
  • Безопасно — вы всегда проверяете guard let self = self else { return }.

unowned self

  • Не optional (self без знака вопроса).
  • Ссылка не увеличивает счетчик ARC.
  • Когда объект уничтожается, unowned ссылка не становится nil. Она становится "висячей" (dangling pointer).
  • Обращение к такой ссылке после уничтожения объекта вызывает крэш (EXC_BAD_ACCESS).

Ключевой момент: unowned — это обещание компилятору: "Я гарантирую, что объект будет жив, пока выполняется это замыкание". Если вы ошиблись — приложение упадет.

Когда использовать [unowned self] (а когда нет)

Давайте разберем реальные сценарии из практики iOS-разработки в 2025 году.

Случай 1: [unowned self] — это ок (и даже предпочтительно)

Используйте [unowned self], когда вы абсолютно уверены, что self будет жив на момент выполнения замыкания. Типичный пример — замыкание, которое выполняется синхронно внутри метода объекта, и объект гарантированно не будет уничтожен до завершения замыкания.

class DataFetcher {
    var data: Data?
    
    func fetchData() {
        // Этот closure выполняется синхронно внутри метода
        let processClosure = { [unowned self] in
            // self точно жив, потому что мы находимся внутри его метода
            self?.data = Data() // Обратите внимание: даже с unowned мы используем self? 
            // В Swift 5.8+ это безопасно, компилятор подсказывает
        }
        processClosure()
    }
}

Более частый пример — замыкание, которое является коллбэком для анимации UIView.animate, которая длится 0.3 секунды. Если ViewController будет уничтожен во время анимации (что маловероятно), крэш не страшен? Нет, страшен! Но вероятность настолько мала, что многие используют [unowned self].

Лучшая практика 2025: Даже в таких "безопасных" случаях, используйте weak + guard. Это стоит одной строчки кода и спасает от редких, но неприятных багов. Если вы пишете библиотеку или фреймворк — unowned вообще под запретом.

Случай 2: [unowned self] — это опасно (избегайте)

Никогда не используйте [unowned self] в асинхронных замыканиях, где self может быть уничтожен до вызова замыкания. Классика — сетевой запрос.

class NetworkManager {
    func loadData(completion: @escaping () -> Void) {
        DispatchQueue.global().async { [unowned self] in
            // Self может быть уже deallocated!
            // Если пользователь ушел с экрана, а запрос еще висит
            self?.processData() // КРЭШ!
        }
    }
    
    func processData() {
        print("Processing...")
    }
}

В этом случае всегда используйте [weak self] и проверяйте guard let self = self.

Случай 3: [unowned self] в замыканиях, которые хранятся как свойства

Это самый коварный случай. Представьте, что у вас есть класс, который хранит замыкание как свойство (например, обработчик нажатия кнопки).

class ButtonHandler {
    var handler: (() -> Void)?
    
    func setup() {
        handler = { [unowned self] in
            self.doSomething()
        }
    }
    
    func doSomething() {
        print("Done")
    }
}

Здесь handler (замыкание) захватывает self. А self (объект ButtonHandler) хранит handler. Это цикл! Если не использовать [unowned self] или [weak self], будет утечка памяти.

Но если вы используете [unowned self], а потом кто-то вызовет handler() после того, как объект ButtonHandler уничтожен — крэш.

Блоги

Рекомендаций по книгам