Память
November 15

Как возникают циклы сильных ссылок для замыканий?

В языке Swift замыкания представляют собой мощный инструмент, позволяющий создавать функциональный и элегантный код. Тем не менее, с этой силой приходит необходимость понимания ряда важных аспектов, включая особенности управления памятью.

Одной из основных проблем, которые могут возникнуть при работе с замыканиями, являются циклы сильных ссылок. В этой статье подробно рассмотрим, как формируются такие циклы для замыканий.

Что такое цикл сильных ссылок для замыканий?

Цикл сильных ссылок возникает, когда два или более объектов ссылаются друг на друга, удерживая свои сильные ссылки. Это препятствует освобождению памяти объектов, поскольку система продолжает считать их используемыми.

Проблема становится особенно актуальной, когда замыкания сохраняют ссылки на экземпляры классов. В этом случае замыкание может "захватить" self, создавая тем самым цикл ссылок.

Пример возникновения цикла сильных ссылок для замыканий

Чтобы проиллюстрировать проблему, рассмотрим класс User, который содержит замыкание для вывода информации о пользователе:

class User {
    let name: String
    let age: Int
    
    lazy var detailsClosure: () -> String = {
        return "User name is \(self.name), age is \(self.age)"
    }
    
    init(name: String, age: Int) {
        self.name = name
        self.age = age
        print("\(name) is being initialized")
    }
    
    deinit {
        print("\(name) is being deinitialized")
    }
}

В этом примере замыкание detailsClosure захватывает self, создавая тем самым сильную ссылку на user. В результате это приводит к следующему циклу:

  1. Экземпляр User имеет сильную ссылку на замыкание detailsClosure.
  2. Замыкание detailsClosure имеетсильную ссылку на self (экземпляр User).

Теперь давайте проверим, что произойдет, если создать экземпляр User и сразу же его освободить:

var user: User? = User(name: "Artem", age: 24)
user = nil

На экране будет следующее:

Artem is being initialized
Artem is being deinitialized

Это означает, что объект user успешно освобождается.

Теперь давайте вызовем замыкание detailsClosure между этими двумя строками:

var user: User? = User(name: "Artem", age: 24)
user?.detailsClosure()
user = nil

В этом случае результат будет следующим:

Artem is being initialized

Обратите внимание, что сообщение из деинициализатора не появляется, что указывает на то, что экземпляр User не был освобожден. Это подтверждает, что цикл сильных ссылок действительно возник.

Как избежать цикла сильных ссылок для этого примера?

Для того чтобы избежать цикла сильных ссылок в замыканиях, необходимо использовать [capture list] и ключевые слова weak или unowned.

Рассмотрим ситуацию, когда у нас нет полной уверенности в том, будет ли объект user существовать во время обращения к detailsClosure. В этом случае целесообразно воспользоваться [capture list] и ключевым словом weak.

lazy var detailsClosure: () -> String = { [weak self] in
    return "User name is \(String(describing: self?.name)), age is \(String(describing: self?.age))"
}

При таком подходе вывод будет таким:

Для более глубокого понимания, как избегать циклы сильных ссылок в замыканиях, рассмотрим в следующих статьях.

А пока давайте поразмышляем над тем, что произойдет, если изменить наш код следующим образом:

А пока давайте поразмышляем над тем, что произойдет, если изменить наш код следующим образом:

class User {
    let name: String
    let age: Int
    
    lazy var detailsClosure: () -> String = { [unowned self] in
        return "User name is \(self.name), age is \(self.age)"
    }
    
    init(name: String, age: Int) {
        self.name = name
        self.age = age
        print("\(name) is being initialized")
    }
    
    deinit {
        print("\(name) is being deinitialized")
    }
}

var user: User? = User(name: "Artem", age: 24)
user = nil
user?.detailsClosure()

Ваши мысли и предположения по этому вопросу жду в комментариях!

👍 Если вам понравилась статья, не забудьте поставить лайк и подписаться на обновления, чтобы не пропустить новые материалы!