
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 是产生循环引用的源头,需要谨慎处理。举个例子,下面伪代码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。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。