Swiftのクロージャは、周囲のコンテキストから変数や定数への参照をキャプチャして保存できる自己完結型のコードブロックです。コールバック、非同期処理を整理し、コードの実行を変数の値として保持することを可能にします。
var counter = 0 let incrementer: () -> Void = { counter += 1 } incrementer() print(counter) // 1
[weak self]、[unowned self])を使って明示的にキャプチャのルールを定義できます。selfをキャプチャする際の可能なretain cycleです。実際のキャッチリスト内での
[weak self]または[unowned self]の使用とstrongキャプチャの違いは何ですか?それにはどういうリスクが伴いますか?
回答:
[weak self]または[unowned self]を指定しない場合、クロージャはデフォルトでselfを強い参照でキャプチャしますが、これはクロージャとselfが互いに参照しあう場合にretain cycleを引き起こすことになります。[weak self]はselfを弱い参照(optional)でキャプチャし、[unowned self]は制御されない参照(non-optional)でキャプチャします。キャプチャリストの不適切な選択や不在は、メモリリークやクラッシュを引き起こします。
class Test { var closure: (() -> Void)? func setup() { closure = { [weak self] in self?.doWork() } } func doWork() { ... } }
物語
あるプログラマーは、非同期データロードを開始するUIViewController内のクロージャでキャプチャリストを使用しませんでした。その結果、コントローラーとそのビューは解放されず、クローズ後にメモリリークが発生しました。インフラ分析では、メモリ内で「ハング」しているコントローラーが何百も見つかりました。
物語
クロージャは[unowned self]を使用してselfをキャプチャしましたが、その際selfのライフサイクルがクロージャよりも早く終了していました。解放されたオブジェクトへのアクセスでクラッシュが発生し、クロージャ内でselfを使用する際にアプリがクラッシュしました。
物語
プロジェクト内で、ネストされたクロージャが二つの変数をキャプチャしました:一つはstrongで、もう一つはweakで。弱い参照があまりにも早くnullになり、クロージャに必要なデータが失われました。このバグはマルチスレッド環境でのストレステスト中のみに現れました。