惰性初始化和保留周期

Ban*_*tor 21 memory-leaks memory-management lazy-initialization automatic-ref-counting swift

使用惰性初始化器时,是否有可能保留周期?

博客文章和许多其他地方[unowned self]可见

class Person {

    var name: String

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

    init(name: String) {
        self.name = name
    }
}
Run Code Online (Sandbox Code Playgroud)

我试过这个

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")
    }
}
Run Code Online (Sandbox Code Playgroud)

像这样使用它

//...
let person = Person(name: "name")
print(person.personalizedGreeting)
//..
Run Code Online (Sandbox Code Playgroud)

并发现"人员deinit"被记录下来.

所以似乎没有保留周期.根据我的知识,当一个块捕获自我并且当该块被自己强烈保留时,存在保留周期.这种情况看起来类似于保留周期但实际上并非如此.

Nik*_*uhe 57

我试过这个[...]

lazy var personalizedGreeting: String = { return self.name }()
Run Code Online (Sandbox Code Playgroud)

似乎没有保留周期

正确.

其原因是,立即应用关闭{}()被认为@noescape.它不会保留捕获的内容self.

供参考:Joe Groff的推文.


dfr*_*fri 5

在这种情况下,您不需要捕获列表,因为self在实例化personalizedGreeting.

正如 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!
Run Code Online (Sandbox Code Playgroud)

很明显,在这种情况下没有强引用循环的风险,因此不需要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
Run Code Online (Sandbox Code Playgroud)

假设:默认情况下,延迟实例化闭包会自动捕获selfweak(或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
Run Code Online (Sandbox Code Playgroud)

即,finfoo()没有被取消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!
Run Code Online (Sandbox Code Playgroud)

有关强引用循环的其他阅读,请参见例如

  • 我同意你说的,但它没有回答问题。在您的示例中,闭包引用了“self”。如果这是一个强引用,只要闭包没有被释放(这在属性初始化之前不会发生),它就会使实例保持活动状态。所以我能想到的唯一解释是:延迟属性初始化中的闭包总是自动弱捕获“self”(或者,更有可能是“unowned”)。这完全有道理并解释了“缺失”参考周期的观察行为。 (2认同)