クロージャは参照する変数をキャプチャし、それらを生かし続けます。クロージャが self をキャプチャし、self もまたそのクロージャを(格納プロパティとして)保持すると、互いを永遠に生かし続けます。これがメモリをリークさせる循環参照(retain cycle)です。[weak self] のようなキャプチャリストがその循環を断ち切ります。
ARCはオブジェクトの参照カウントがゼロになったときにそれを解放します。強参照のキャプチャはそのカウントを増やします。
final class Downloader {
var onDone: (() -> Void)? // stored closure -> strong
func start() {
// BAD: closure captures self strongly, self holds closure -> cycle
onDone = { self.cleanup() }
// GOOD: weak capture, self can deallocate
onDone = { [weak self] in
guard let self else { return } // safely unwrap; bail if gone
self.cleanup()
}
}
func cleanup() {}
}
[weak self] — self はOptionalになり、オブジェクトが解放されていれば nil になります。オブジェクトがクロージャより長生きする可能性がある場合の安全なデフォルトです。[unowned self] — 非Optionalで、ARCのオーバーヘッドはありませんが、self がすでに解放されているとクラッシュします。self がクロージャより確実に長生きする場合にのみ使ってください。Task { [weak self] in
let data = await self?.fetch() // self? short-circuits if deallocated
}
非エスケープクロージャ(例: map、filter、forEach)は同期的に実行され、キャプチャを即座に解放します。循環はなく、[weak self] も不要です。リスクがあるのはエスケープする、格納されたクロージャです。completion handler、Timer、NotificationCenter、Combineのsink、delegateブロックなどです。
循環参照はiOSで最も一般的なメモリリークであり、Instrumentsがメモリの増加を示すまで目に見えません。クロージャがいつエスケープし、なぜ [weak self] が必要なのかを正確に知ることは — どこでも反射的にそれに手を伸ばすのとは対照的に — ARCを理解しているエンジニアと、それをカーゴカルト的に真似ているエンジニアを分けます。
ジュニアからシニアまで、詳細な回答付きのIT面接質問ライブラリ。
寄付する