奇怪的弱自我和保留循环行为

发布于 2025-01-16 07:17:11 字数 2832 浏览 1 评论 0原文

让我们考虑以下代码:

// 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()
        }
    }
}

我们有一个 Printer 类,其中包含一个 Delayer 类,该类在 Printer 上执行操作并延迟执行。

我们这样称呼它:

var printer: Printer? = PrinterX()

printer?.delayer.run()

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

很明显为什么 Printer1 创建保留周期。 Action 被传递到带有隐式强 self 的 Delayer 中,但由于 Delayer 属于 Printer 所拥有,因此无法释放它。

我认为 Printer2 是预期的方式。显然不会创建保留周期,但一直写起来有点混乱。这就是为什么我开始尝试其他解决方案。

我不明白为什么 Printer3 不创建保留周期。因为 weakAction 是 self 的属性,因此将其传递给 Delayer 应该会像 Printer1 中那样创建强引用。

我也不明白为什么 Priner4 确实创建保留周期。 welf 是对 self 的本地弱引用,因此在将其传递到 Delayer 时不应增加引用计数。

奇怪的是,在 Printer5 中使用内部闭包的 welf 并不会创建保留周期。

问题

  1. 任何人都可以向我解释一下 Printer3、Printer4 和 Printer5 上的这种奇怪行为吗?
  2. 我很想使用 Printer3 解决方案。使用安全吗?由于它看起来几乎像一个错误,我可以使用它而不担心它在未来版本中被修复并因此在我的应用程序中创建保留周期吗?

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.

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. Action is passed into delayer with implicit strong self which cannot be released because Delayer is owned by Printer.

Printer2 is the intended way in my opinion. 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. Because weakAction is property on self, so passing it like that into Delayer should create strong reference like in Printer1.

I also don't understand why Priner4 does create retain cycle. welf is local weak reference to self, so it should not increase the reference count when passing it into the Delayer.

Strangely enough using the welf inside closure in Printer5 doesn't create retain cycle.

Questions

  1. Can anyone please explain to me this weird behavior on Printer3, Printer4 and Printer5
  2. I am tempted to use the Printer3 solution. 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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

负佳期 2025-01-23 07:17:11

首先,所有打印机都在创建并保留自己的 Delayer。延迟器接受一个封闭,然后又保留该封闭。

让我们尝试一一浏览它们。

Printer1

正如您自己所说,它创建保留周期的原因非常清楚。您将 self.action 实例方法作为 Delayer 的闭包传递,并且由于所有闭包都是引用类型,因此传递 self.action 将保留其周围范围(即打印机1)。

Printer2

同样,这里非常明显。您在传递给 Delayer 的闭包内显式捕获对 self 的弱引用,因此不会创建保留周期。

Printer3

这里,没有创建保留循环,因为立即调用 self.weakAction 属性,并且其结果(保存对 self 的弱引用的闭包)被传递给 Delayer。实际上,这与 Printer2 中发生的情况完全相同。

Printer4

首先,您捕获对 self 的弱引用,然后获取 welf?.action 并将结果传递给 Delayer。同样,立即调用 welf?.action,并将结果(指向实例方法的指针)传递给 Delayer。对 self 的弱引用仅在周围作用域(惰性 var 创建作用域)期间保留,并且传递 action 实例方法将保留 self。这与 Printer1 相同。

Printer5

在这里,您首先创建一个对 self 的弱引用,然后在传递给 Delayer 的新闭包中捕获该弱引用。由于 self 在传递的闭包中永远不会被直接引用,因此它不会捕获该范围内的 self,仅捕获 welf 弱引用。这与 Printer2 几乎相同,但语法略有不同。

就我个人而言,我会选择 Printer2 方式(创建一个新的闭包,保留对 self 的弱引用并使用它来调用 self?.action )。它使得代码最容易遵循(而不是保留带有弱捕获 self 的闭包的变量)。但是,根据您的实际用例,这当然可能是有意义的。

First of all, all printers are creating and retaining their own Delayer. The delayer takes a closure and, in turn, retains that closure.

Let's try to walk through them one by one.

Printer1

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).

Printer2

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.

Printer3

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. This, in effect, is the exact same thing as what's happening in Printer2.

Printer4

First, you're capturing a weak reference to self, and then fetching welf?.action and passing the result into Delayer. Again, welf?.action is called immediately, and the result (a pointer to an instance method) is passed on to 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. This is identical to Printer1.

Printer5

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. Since self is never directly referenced in the passed closure, it will not capture self in that scope, only the welf weak reference. This is pretty much identical to Printer2, but with a slightly different syntax.

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). 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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文