简体   繁体   English

奇怪的弱自我和保留周期行为

[英]Weird weak self and retain cycle behaviour

Let's consider following code:让我们考虑以下代码:

// Just for easier testing
protocol Printer {
    var delayer: Delayer { get }
}

// Retain cycle
class Printer1: Printer {
    private func action() {
        print("action")
    }
    
    private(set) lazy var delayer: Delayer = {
        return Delayer(action)
    }()
    
    deinit {
        print("deinit")
    }
}

// Works fine, but weak mess
class Printer2: Printer {
    private func action() {
        print("action")
    }
    
    private(set) lazy var delayer: Delayer = {
        return Delayer { [weak self] in self?.action() }
    }()
    
    deinit {
        print("deinit")
    }
}

// Questionable hack, but works fine
class Printer3: Printer {
    private func action() {
        print("action")
    }
    
    private(set) lazy var delayer: Delayer = {
        return Delayer(weakAction)
    }()

    // computed property or function is also fine here
    private lazy var weakAction: () -> Void = {
        return { [weak self] in
            self?.action()
        }
    }()
    
    deinit {
        print("deinit")
    }
}

// Retain cycle
class Printer4: Printer {
    private func action() {
        print("action")
    }
    
    private(set) lazy var delayer: Delayer = {
        weak var welf: Printer4? = self
        return Delayer(welf?.action ?? {})
    }()
    
    deinit {
        print("deinit")
    }
}

// Works fine
class Printer5: Printer {
    private func action() {
        print("action")
    }
    
    private(set) lazy var delayer: Delayer = {
        weak var welf: Printer5? = self
        return Delayer { welf?.action() }
    }()
    
    deinit {
        print("deinit")
    }
}

class Delayer {
    private var action: () -> Void
    
    init(_ action: @escaping () -> Void) {
        self.action = action
    }
    
    func run() {
        DispatchQueue.main.asyncAfter(deadline: .now() + 3) { [weak self] in
            self?.action()
        }
    }
}

So we have a Printer class which contains a Delayer class that takes the action on Printer and performs it delayed.所以我们有一个打印机 class,它包含一个延迟器 class,它对打印机执行操作并延迟执行。

We call it something like this:我们这样称呼它:

var printer: Printer? = PrinterX()

printer?.delayer.run()

DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
     printer = nil
}

It is clear why Printer1 creates retain cycle.很清楚为什么 Printer1 创建保留循环。 Action is passed into delayer with implicit strong self which cannot be released because Delayer is owned by Printer. Action 被传递到具有隐式强自我的 delayer 中,由于 Delayer 归 Printer 所有,因此无法释放。

Printer2 is the intended way in my opinion. Printer2 是我认为的预期方式。 Obviously doesn't create retain cycle, but it is kind of mess to write all the time.显然不会创建保留循环,但一直写起来有点乱。 Thats why I started experimenting with other solution.这就是为什么我开始尝试其他解决方案的原因。

I don't understand why Printer3 doesn't create retain cycle.我不明白为什么 Printer3 不创建保留周期。 Because weakAction is property on self, so passing it like that into Delayer should create strong reference like in Printer1.因为weakAction是自身的属性,所以像这样将它传递给 Delayer 应该像在 Printer1 中一样创建强引用。

I also don't understand why Priner4 does create retain cycle.我也不明白为什么 Priner4 会创建保留周期。 welf is local weak reference to self, so it should not increase the reference count when passing it into the Delayer. welf是对 self 的局部弱引用,因此在将其传递给 Delayer 时不应增加引用计数。

Strangely enough using the welf inside closure in Printer5 doesn't create retain cycle.奇怪的是,在 Printer5 中使用welf inside 闭包不会创建保留循环。

Questions问题

  1. Can anyone please explain to me this weird behavior on Printer3, Printer4 and Printer5谁能向我解释一下 Printer3、Printer4 和 Printer5 上的这种奇怪行为
  2. I am tempted to use the Printer3 solution.我很想使用 Printer3 解决方案。 Is it safe to use?使用安全吗? As it seems almost like a bug, can I use it without worrying about it being fixed in future versions and therefore creating retain cycle in my app?因为它看起来几乎像一个错误,我可以使用它而不用担心它会在未来的版本中被修复并因此在我的应用程序中创建保留周期吗?

First of all, all printers are creating and retaining their own Delayer.首先,所有打印机都在创建并保留自己的 Delayer。 The delayer takes a closure and, in turn, retains that closure.延迟器获取一个闭包,然后保留该闭包。

Let's try to walk through them one by one.让我们试着一一过一遍。

Printer1打印机1

As you stated yourself, it's pretty clear why it's creating a retain cycle.正如您自己所说,很清楚为什么要创建保留周期。 You are passing the self.action instance method as the closure to the Delayer, and since all closures are reference types, passing self.action will retain its surrounding scope (which is Printer1).您将self.action实例方法作为闭包传递给延迟器,并且由于所有闭包都是引用类型,因此传递self.action将保留其周围的 scope(即 Printer1)。

Printer2打印机2

Again, pretty obvious here.同样,这里很明显。 You're explicitly capturing a weak reference to self inside the closure you're passing to Delayer, hence not creating a retain cycle.您在传递给 Delayer 的闭包中明确地捕获了对 self 的弱引用,因此没有创建保留循环。

Printer3打印机3

Here, a retain cycle is not created, because the self.weakAction property is called immediately, and its result (a closure which holds a weak reference to self) is passed on to Delayer.在这里,没有创建循环保留,因为self.weakAction属性被立即调用,其结果(一个包含对 self 的弱引用的闭包)被传递给 Delayer。 This, in effect, is the exact same thing as what's happening in Printer2 .这实际上与Printer2中发生的事情完全相同。

Printer4打印机4

First, you're capturing a weak reference to self, and then fetching welf?.action and passing the result into Delayer.首先,您捕获对 self 的弱引用,然后获取welf?.action并将结果传递给 Delayer。 Again, welf?.action is called immediately, and the result (a pointer to an instance method) is passed on to Delayer.同样,立即调用welf?.action ,并将结果(指向实例方法的指针)传递给 Delayer。 The weak reference to self is only kept for the duration of the surrounding scope (the lazy var creation scope), and passing the action instance method will retain self.对 self 的弱引用仅在周围 scope(惰性 var 创建范围)的持续时间内保留,传递action实例方法将保留 self。 This is identical to Printer1 .这与Printer1相同。

Printer5打印机5

Here, you're first creating a weak reference to self, and then you're capturing that weak reference inside a new closure that is passed to Delayer.在这里,您首先创建对 self 的弱引用,然后在传递给 Delayer 的新闭包中捕获该弱引用。 Since self is never directly referenced in the passed closure, it will not capture self in that scope, only the welf weak reference.由于传递的闭包中从未直接引用过self ,因此它不会捕获 scope 中的self ,只会捕获welf弱引用。 This is pretty much identical to Printer2 , but with a slightly different syntax.这与Printer2几乎相同,但语法略有不同。

Personally, I would opt for the Printer2 way (creating a new closure, retaining a weak reference to self and using that to call self?.action ).就个人而言,我会选择Printer2方式(创建一个新的闭包,保留对 self 的弱引用并使用它来调用self?.action )。 It makes for the easiest code to follow (as opposed to retaining a variable with a closure that weakly captures self).它使代码最容易遵循(而不是保留一个带有弱捕获自我的闭包的变量)。 But, depending on what you're actual use case is, it might of course make sense.但是,根据您的实际用例,它当然可能有意义。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM