欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Swift Tips 022 - Overriding self with a weak reference

程序员文章站 2022-06-02 12:46:21
...

Swift Tips 022 - Overriding self with a weak reference

代码截图

Swift Tips 022 - Overriding self with a weak reference

代码出处: Swift Tips 022 by John Sundell[1]

小笔记

这段代码在说什么

在处理逃逸闭包内部的逻辑时,我们通常会使用 weak self 的方式来避免循环引用。为了在闭包里面正确的使用 self 变量,我们需要通过可选绑定的方式将原先的 self 重新命名并使用。

在 Swift 4.2 之前,self 是全局保留关键字,所以如果在逃逸闭包中把 self 标记为 weak 后,还想继续使用 self 就需要使用 ` 将 self 包起来:

guard let `self` = self else { return }

而在 Swift 4.2 之后,基于 Allow using optional binding to upgrade self from a weak to strong reference[2] 提案,可选绑定中的 self 不再作为保留关键字。我们完全可以光明正大的这么写了:

guard let self = self else { return }

内存泄漏与闭包里的 self

在开始下面的内容前,我们先回顾一下内存泄漏和闭包里的 sefl 这个话题。

假设我们有这样一段代码:

doSomething(then: {
  // do something else
})

由于 Swift 的内存管理机制,如果我们在闭包里面使用一些局部变量的话,闭包就会捕获这个变量,就像下面的代码一样:

var dog = Dog()
doSomething(then: {
  dog.bark() // dog must be captured so it will live long enough
})

Swift 编译器会自动管理 dog 的引用计数,这个 dog 会被 doSomething 的闭包 捕获,从而增加引用计数,即使跳出这段代码的作用域,dog 实例仍然会被 doSomething 的闭包所持有。

这种形式在上面的例子中似乎并无大碍,但有时候却会带来一些问题。最常见的一种情况就是循环引用。

试想一下:某个实例拥有一个闭包属性,而这个属性又会调用自身的一些实例方法或者属性时,它们就会产出如下图所示的引用关系。

Swift Tips 022 - Overriding self with a weak reference

根据前面的例子,我们知道只要闭包不被销毁,它就会一直持有 self,而另一边,闭包作为实例的一个属性,它们是同生死,共存亡!

你瞧,它们谁也无法释放对方......然后,内存泄漏了!

避免循环引用

为了避免循环引用,Swift 提供了捕获列表来让开发者决定如何持有相关变量。拿之前的 dog 代码举例说明,它就变成了如下的样式:

let dog = Dog()
doSomething(then: { [weak dog] in
    // dog is now Optional<Dog>
    dog?.bark()
)

此时,当我们再次跳出这段代码的作用域后,闭包里 dog 将变为 nil。除非我们还在其他地方,通过一些途径强持有了 dog 实例。

同理,捕获列表也支持 self:

doSomething(then: { [weak self] in
    self?.doSomethingElse()
)

不过由于此时的 self 变成了一个可选类型,所以如果我们要区分不同状态下的代码行为,我们通常还会在闭包内部创建一个对 weak self 的强引用。

doSomething(then: { [weak self] in
    guard let strongSelf = self { else return }
    strongSelf.doSomethingElse()
)

在上面的这个例子中,我们确保了 strongSelf 是一定有值的,而且当 self 为 nil 的时候,不会执行剩下的代码。

可选绑定里的 self

虽然在许多 Swift 项目中能够看到 strongSelf 的写法,但开发者还是觉得这种方式有点不那么时髦,总想着要弄点什么新鲜玩意儿来!

果不其然,Swift 社区里的人发现了在闭包里还可以进行如下的操作:

guard let `self` = self else { return }

这种 'trick' 的方式能够让我们统一 self 的写法,也让代码看起来变得更顺眼了一些。

但有意思的是,我们的 Chris,也就是 Swift 之父,亲口承认了这个所谓的“新特性”,其实是一个 Swift 编译器的 bug[3]

但社区里的开发者并没有打算放弃努力!

终于,在 Swift 4.2 的时候,Swift 团队接受了开发者提出的相关 proposal 并提供了在可选绑定中不再将 self 作为保留关键字的特性。所以我们现在能够看到如下的写法:

guard let self = self else { return }

当然取消了这个限制后也意味着 self 可能不一定是 self 了:

var number: Int? = nil
if let self = number {
    print(self) // 这里的 self 是 number:Int
}

所以,即使如此,还是希望大家在正确的地方将 self 作为可选绑定的变量名,免得造成其他的困扰。

参考资料

[1]

Swift Tips 022 by John Sundell: https://github.com/JohnSundell/SwiftTips#22-overriding-self-with-a-weak-reference

[2]

Allow using optional binding to upgrade self from a weak to strong reference: https://github.com/apple/swift-evolution/blob/master/proposals/0079-upgrade-self-from-weak-to-strong.md#allow-using-optional-binding-to-upgrade-self-from-a-weak-to-strong-reference

[3]

bug: https://github.com/apple/swift-evolution/blob/master/proposals/0079-upgrade-self-from-weak-to-strong.md#relying-on-a-compiler-bug

Swift Tips 022 - Overriding self with a weak reference