延迟初始化和保留周期

时间:2016-07-01 09:02:22

标签: swift memory-management memory-leaks automatic-ref-counting lazy-initialization

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

blog post和许多其他地方[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)
//..

发现" deinit"被记录了。

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

2 个答案:

答案 0 :(得分:54)

  

我试过这个[...]

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

似乎没有保留周期

正确。

原因是立即应用的关闭{}()被视为@noescape。它不会保留捕获的self

供参考:Joe Groff's tweet

答案 1 :(得分:4)

在这种情况下,您不需要捕获列表,因为在self实例化后没有引用personalizedGreeting

正如MartinR在他的评论中写道,当您删除捕获列表时,可以通过记录Person对象是否已被去除来轻松测试您的假设。

E.g。

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的属性会将强引用保留回{{ 1}}。 E.g:

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 中的f未被取消初始化,并且考虑到这个强大的参考周期,我们可以得出结论:foo()在惰性变量{{的实例化闭包中被强烈捕获1}}。

我们还可以看到,如果我们从不实例化self,我们永远不会创建强引用循环,这将支持最多一次的实例化闭包可以被视为运行时范围(很像a)从未达到if)a)从未达到(未初始化)或b)达到,完全执行和“扔掉”(范围结束)。

dummy

有关强参考循环的其他读数,请参见例如