Swift 5.8 实现: SE-0365
相关介绍
在 closure 捕获列表中,如果显式捕获self
,则在 closure 使用时,则允许隐式使用self
。即在 closure 捕获列表中声明[self]
, 则 closure 内调用self.
的地方都可以不用书写该self.
。这个特性在SE-0269中提议。现在本篇提议想把这个特性支持扩展到weak self
的捕获上,并允许隐式self
和已解包的self
一样使用。效果就是如果在 closure 内 self
已经解包,则 closure 内调用 self
的地方可以不用写 self
。如下例的dismiss
方法调用:
class ViewController {
let button: Button
func setup() {
button.tapHandler = { [weak self] in
guard let self else { return }
dismiss()
}
}
func dismiss() { ... }
}
以前在 closure 中都是需要显式指定self
,目的是为了防止开发者无意之中产生循环引用。SE-0269其实放宽了这个规则,因为隐式self
不太可能引入潜在隐藏的循环引用(这里只是说可能性小一点,如果是嵌套使用,这个场景是容易出现的)。例如当self
在闭包的捕获列表中被显式捕获时:
button.tapHandler = { [self] in
dismiss()
}
SE-0269 其实没有实现对weak self
捕获的处理。所以tapHandler
如果捕获weak self
,则在 closure 中调用dismiss
,则需要显式使用self
。
button.tapHandler = { [weak self] in
guard let self else { return }
self.dismiss()
}
上例的处理是当前针对weak self
的普遍处理操作。其实可以看到self
已经在 closure 中被显式捕获,后面再显式要求使用self
显得没那么必要,相反,此时不使用self
,反而更加清晰。这是本篇提议想要解决的问题。
提议解决方案
只要self
已经解包,我们提议允许隐式self
作为weak self
的捕获,去掉self.
的显式写法。
利用上述改进,下面 closure 中的dismiss()
方法可以不用self.
也可以编译通过:
class ViewController {
let button: Button
func setup() {
button.tapHandler = { [weak self] in
guard let self else { return }
dismiss()
}
}
func dismiss() { ... }
}
详细设计
启用隐式 self
下面列举 6 种形式的可选解绑,全部支持这些情况,并在解包后的所有作用域范围内,都支持隐式self
。下面列表全部的可选解绑类型:
button.tapHandler = { [weak self] in
guard let self else { return }
dismiss()
}
button.tapHandler = { [weak self] in
guard let self = self else { return }
dismiss()
}
button.tapHandler = { [weak self] in
if let self {
dismiss()
}
}
button.tapHandler = { [weak self] in
if let self = self {
dismiss()
}
}
button.tapHandler = { [weak self] in
while let self {
dismiss()
}
}
button.tapHandler = { [weak self] in
while let self = self {
dismiss()
}
}
同样跟strong
和unowned
的隐式self
捕获一样,编译器会自动合成一个self.
。对于调用在某个 closure 里面调用self
的属性或者方法,则要用weak self
。
那么如果对weak self
没有解包而直接使用隐式调用用法,会发生什么?此时编译器会直接报错,告诉这种情况需要显式指定self?.
, 才能清晰地表达整个控制流。
button.tapHandler = { [weak self] in
// error: explicit use of 'self' is required when 'self' is optional,
// to make control flow explicit
// fix-it: reference 'self?.' explicitly
dismiss()
}
嵌套 closure
嵌套 closure 是产生循环引用的源头,需要谨慎处理。举个例子,下面伪代码couldCauseRetainCycle
如果可以正常编译,那么隐式的self.bar()
调用将会引入一个隐藏的循环引用。
couldCauseRetainCycle { [weak self] in
guard let self else { return }
foo()
couldCauseRetainCycle {
bar()
}
}
调用到foo()
都没有问题,但是 closure 继续调用couldCauseRetainCycle
, 而且其中bar()
在作用域捕获的当前的self
,而不是 guard 处解包的self
,结果一个隐藏的循环引用就产生了。所以对于嵌套的 closure, 为了使用隐式的self
,必须显式捕获self
。下面 3 种情况都可以,看下改进的代码示例:
// Not allowed:
couldCauseRetainCycle { [weak self] in
guard let self else { return }
foo()
couldCauseRetainCycle {
// error: call to method 'method' in closure requires
// explicit use of 'self' to make capture semantics explicit
bar()
}
}
// Allowed:
couldCauseRetainCycle { [weak self] in
guard let self else { return }
foo()
couldCauseRetainCycle { [weak self] in
guard let self else { return }
bar()
}
}
// Also allowed:
couldCauseRetainCycle { [weak self] in
guard let self else { return }
foo()
couldCauseRetainCycle {
self.bar() // 上面的 self, 已经 weak
}
}
// Also allowed:
couldCauseRetainCycle { [weak self] in
guard let self else { return }
foo()
couldCauseRetainCycle { [self] in
bar()
}
}
总结
针对 weak self
在 closure 中的捕获,在解包的前提下,我们可以在 closure 的作用域内使用隐式self
,来代替当前使用的显式self
。