奇怪的弱自我和保留周期行为
Weird weak self and retain cycle behaviour
让我们考虑以下代码:
// 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()
}
}
}
所以我们有一个打印机 class,它包含一个延迟器 class,它在打印机上执行操作并延迟执行。
我们这样称呼它:
var printer: Printer? = PrinterX()
printer?.delayer.run()
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
printer = nil
}
很清楚为什么 Printer1 会创建保留循环。动作被传递到具有隐式强自我的延迟器中,由于延迟器归打印机所有,因此无法释放。
Printer2 是我认为的预期方式。显然不会创建保留循环,但一直写起来有点乱。这就是我开始尝试其他解决方案的原因。
我不明白为什么 Printer3 不创建保留循环。因为 weakAction
在自身上是 属性,所以将它像那样传递到 Delayer 应该像在 Printer1 中一样创建强引用。
我也不明白为什么 Priner4 会创建保留循环。 welf
是对self的局部弱引用,所以传入Delayer时不应该增加引用计数。
奇怪的是,在 Printer5 中使用 welf
内部闭包不会创建保留循环。
问题
- 任何人都可以向我解释 Printer3、Printer4 和 Printer5 上的这种奇怪行为
- 我很想使用 Printer3 解决方案。使用安全吗?由于它看起来几乎是一个错误,我可以使用它而不用担心它会在未来的版本中被修复并因此在我的应用程序中创建保留周期吗?
首先,所有打印机都在创建并保留自己的延迟器。延迟器采用闭包,然后保留该闭包。
让我们试着一一浏览。
打印机 1
正如您自己所说,很清楚为什么要创建保留周期。您正在将 self.action
实例方法作为闭包传递给延迟器,并且由于所有闭包都是引用类型,因此传递 self.action
将保留其周围的范围(即 Printer1)。
打印机2
同样,这里很明显。您在传递给 Delayer 的闭包中明确捕获了对 self 的弱引用,因此没有创建保留周期。
打印机 3
这里没有创建循环引用,因为 self.weakAction
属性 被立即调用,并且它的结果(一个包含对 self 的弱引用的闭包)被传递给 Delayer。这实际上与 Printer2
.
中发生的事情完全相同
打印机 4
首先,您要捕获对 self 的弱引用,然后获取 welf?.action
并将结果传递给 Delayer。同样,立即调用 welf?.action
,并将结果(指向实例方法的指针)传递给 Delayer。对 self 的弱引用仅在周围范围(惰性 var 创建范围)的持续时间内保留,并且传递 action
实例方法将保留 self。这与 Printer1
.
相同
打印机5
在这里,您首先创建一个对 self 的弱引用,然后在传递给 Delayer 的新闭包中捕获该弱引用。由于 self
从未在传递的闭包中直接引用,因此它不会在该范围内捕获 self
,只会捕获 welf
弱引用。这与 Printer2
几乎相同,但语法略有不同。
就我个人而言,我会选择 Printer2
方式(创建一个新的闭包,保留对 self 的弱引用并使用它来调用 self?.action
)。它使代码最容易遵循(而不是保留一个带有弱捕获自我的闭包的变量)。但是,根据您的实际用例,它当然可能有意义。
让我们考虑以下代码:
// 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()
}
}
}
所以我们有一个打印机 class,它包含一个延迟器 class,它在打印机上执行操作并延迟执行。
我们这样称呼它:
var printer: Printer? = PrinterX()
printer?.delayer.run()
DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
printer = nil
}
很清楚为什么 Printer1 会创建保留循环。动作被传递到具有隐式强自我的延迟器中,由于延迟器归打印机所有,因此无法释放。
Printer2 是我认为的预期方式。显然不会创建保留循环,但一直写起来有点乱。这就是我开始尝试其他解决方案的原因。
我不明白为什么 Printer3 不创建保留循环。因为 weakAction
在自身上是 属性,所以将它像那样传递到 Delayer 应该像在 Printer1 中一样创建强引用。
我也不明白为什么 Priner4 会创建保留循环。 welf
是对self的局部弱引用,所以传入Delayer时不应该增加引用计数。
奇怪的是,在 Printer5 中使用 welf
内部闭包不会创建保留循环。
问题
- 任何人都可以向我解释 Printer3、Printer4 和 Printer5 上的这种奇怪行为
- 我很想使用 Printer3 解决方案。使用安全吗?由于它看起来几乎是一个错误,我可以使用它而不用担心它会在未来的版本中被修复并因此在我的应用程序中创建保留周期吗?
首先,所有打印机都在创建并保留自己的延迟器。延迟器采用闭包,然后保留该闭包。
让我们试着一一浏览。
打印机 1
正如您自己所说,很清楚为什么要创建保留周期。您正在将 self.action
实例方法作为闭包传递给延迟器,并且由于所有闭包都是引用类型,因此传递 self.action
将保留其周围的范围(即 Printer1)。
打印机2
同样,这里很明显。您在传递给 Delayer 的闭包中明确捕获了对 self 的弱引用,因此没有创建保留周期。
打印机 3
这里没有创建循环引用,因为 self.weakAction
属性 被立即调用,并且它的结果(一个包含对 self 的弱引用的闭包)被传递给 Delayer。这实际上与 Printer2
.
打印机 4
首先,您要捕获对 self 的弱引用,然后获取 welf?.action
并将结果传递给 Delayer。同样,立即调用 welf?.action
,并将结果(指向实例方法的指针)传递给 Delayer。对 self 的弱引用仅在周围范围(惰性 var 创建范围)的持续时间内保留,并且传递 action
实例方法将保留 self。这与 Printer1
.
打印机5
在这里,您首先创建一个对 self 的弱引用,然后在传递给 Delayer 的新闭包中捕获该弱引用。由于 self
从未在传递的闭包中直接引用,因此它不会在该范围内捕获 self
,只会捕获 welf
弱引用。这与 Printer2
几乎相同,但语法略有不同。
就我个人而言,我会选择 Printer2
方式(创建一个新的闭包,保留对 self 的弱引用并使用它来调用 self?.action
)。它使代码最容易遵循(而不是保留一个带有弱捕获自我的闭包的变量)。但是,根据您的实际用例,它当然可能有意义。