Allow implicit self for weak self captures, after self is unwrapped
01 何が問題だったのか
クロージャ内では、意図しない retain cycle を防ぐ目的で、長らく self. を明示することが求められてきました。SE-0269 ではこの要件が緩和され、キャプチャリストで明示的に self をキャプチャしているクロージャでは self. を省略できるようになりました。
button.tapHandler = { [self] in
dismiss()
}
しかし SE-0269 では weak self キャプチャの扱いは future direction として残されており、[weak self] の場合は self をアンラップしたあとも self. を明示的に書く必要がありました。
button.tapHandler = { [weak self] in
guard let self else { return }
self.dismiss()
}
weak self の場合もキャプチャリストで self を明示しており、すでに retain cycle の意図は十分に表現されています。それにもかかわらず本文で self. を書かせ続けるのは一貫性を欠き、クロージャ本体に余計なノイズを増やしているだけでした。
02 どのように解決されるのか
[weak self] でキャプチャしたクロージャでも、self がアンラップされたあとのスコープでは self. を省略できるようにします。
class ViewController {
let button: Button
func setup() {
button.tapHandler = { [weak self] in
guard let self else { return }
dismiss()
}
}
func dismiss() { ... }
}
アンラップとして認められる形
次のいずれの書き方でも、self が non-optional として扱われるスコープで 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
while let self {
dismiss()
}
}
アンラップ前に self のメンバを暗黙で呼び出そうとするとエラーになり、self?. を明示するよう促す fix-it が提示されます。
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()
}
また、アンラップ結果がクロージャの self キャプチャ以外の値も指しうる場合(例えば self ?? someOptionalWithSameTypeOfSelf のような形)は、暗黙の self. は許可されません。意味が曖昧になるのを避けるためで、その場合は従来どおり self. を明示する必要があります。
ネストしたクロージャ
ネストしたクロージャは retain cycle の温床になりやすいため、SE-0269 と同じ方針が適用されます。[weak self] クロージャの内側にさらにクロージャを書く場合、内側のクロージャで暗黙の self を使うには、内側でも改めて self を明示的にキャプチャする必要があります。
// 不可:
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()
}
}
// 可:
couldCauseRetainCycle { [weak self] in
guard let self else { return }
foo()
couldCauseRetainCycle { [weak self] in
guard let self else { return }
bar()
}
}
// これも可(内側で self. を明示):
couldCauseRetainCycle { [weak self] in
guard let self else { return }
foo()
couldCauseRetainCycle {
self.bar()
}
}
// これも可(内側で [self] キャプチャ):
couldCauseRetainCycle { [weak self] in
guard let self else { return }
foo()
couldCauseRetainCycle { [self] in
bar()
}
}
アンラップ前の暗黙 self は導入しない
「アンラップ前の dismiss() を暗黙に self?.dismiss() として扱う」といった拡張は意図的に見送られています。そうした挙動は、self が nil のときに式が実行されないという暗黙の制御フローを生み、読み手にそれが伝わりません。これに関しては従来どおり self?.dismiss() を書く必要があります。