Подтвердить что ты не робот

Ленивый цикл инициализации и сохранения

При использовании ленивых инициализаторов существует ли вероятность сохранения циклов?

В сообщении в блоге и в других местах [unowned self] отображается

class Person {

    var name: String

    lazy var personalizedGreeting: String = {
        [unowned self] in
        return "Hello, \(self.name)!"
        }()

    init(name: String) {
        self.name = name
    }
}

Я пробовал это

class Person {

    var name: String

    lazy var personalizedGreeting: String = {
        //[unowned self] in
        return "Hello, \(self.name)!"
        }()

    init(name: String) {
        print("person init")
        self.name = name
    }

    deinit {
        print("person deinit")
    }
}

Используется так

//...
let person = Person(name: "name")
print(person.personalizedGreeting)
//..

И обнаружил, что "person deinit" был зарегистрирован.

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

4b9b3361

Ответ 1

Я пробовал это [...]

lazy var personalizedGreeting: String = { return self.name }()

кажется, что нет циклов удержания

Правильно.

Причина в том, что немедленно примененное замыкание {}() считается @noescape. Он не сохраняет захваченный self.

Для справки: Joe Groff tweet.

Ответ 2

В этом случае вам не нужен список захвата, поскольку после экземпляра personalizedGreeting не упоминается ссылка self.

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

например.

class Person {
    var name: String

    lazy var personalizedGreeting: String = {
        _ in
        return "Hello, \(self.name)!"
        }()

    init(name: String) {
        self.name = name
    }

    deinit { print("deinitialized!") }
}

func foo() {
    let p = Person(name: "Foo")
    print(p.personalizedGreeting) // Hello Foo!
}

foo() // deinitialized!

Очевидно, что в этом случае нет риска сильного эталонного цикла и, следовательно, нет необходимости в списке захвата unowned self в ленивом закрытии. Причина этого заключается в том, что ленивое закрытие выполняется только один раз и использует только возвращаемое значение замыкания (лениво), создавая экземпляр personalizedGreeting, тогда как ссылка на self в этом случае не завершает выполнение закрытие.

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

class Person {
    var name: String

    var personalizedGreeting: (() -> String)?

    init(name: String) {
        self.name = name

        personalizedGreeting = {
            () -> String in return "Hello, \(self.name)!"
        }
    }

    deinit { print("deinitialized!") }
}

func foo() {
    let p = Person(name: "Foo")
}

foo() // ... nothing : strong reference cycle

Гипотеза: ленивые создания экземпляров автоматически фиксируют self как weak (или unowned) по умолчанию

Как мы рассмотрим следующий пример, мы понимаем, что эта гипотеза неверна.

/* Test 1: execute lazy instantiation closure */
class Bar {
    var foo: Foo? = nil
}

class Foo {
    let bar = Bar()
    lazy var dummy: String = {
        _ in
        print("executed")
        self.bar.foo = self 
            /* if self is captured as strong, the deinit
               will never be reached, given that this
               closure is executed */
        return "dummy"
    }()

    deinit { print("deinitialized!") }
}

func foo() {
    let f = Foo()
    // Test 1: execute closure
    print(f.dummy) // executed, dummy
}

foo() // ... nothing: strong reference cycle

I.e., f in foo() не деинициализируется и, учитывая этот сильный ссылочный цикл, можно сделать вывод, что self сильно фиксируется в экземпляре замыкания ленивой переменной dummy.

Мы также можем видеть, что мы никогда не создаем сильный ссылочный цикл в случае, если мы никогда не создадим экземпляр dummy, который бы поддерживал то, что максимально простое ленивое создание экземпляра можно рассматривать как область выполнения (как никогда достигнут if), который либо a) никогда не был достигнут (не инициализирован), либо b) достигнут, полностью исполнен и "выброшен" (конец области).

/* Test 2: don't execute lazy instantiation closure */
class Bar {
    var foo: Foo? = nil
}

class Foo {
    let bar = Bar()
    lazy var dummy: String = {
        _ in
        print("executed")
        self.bar.foo = self
        return "dummy"
    }()

    deinit { print("deinitialized!") }
}

func foo() {
    let p = Foo()
    // Test 2: don't execute closure
    // print(p.dummy)
}

foo() // deinitialized!

Дополнительное чтение по сильным опорным циклам см., например,