Swiftでは、クロージャは周囲のコンテキストから値や参照を「キャプチャ」することができます。キャプチャされる値が値型(struct、enum)の場合、クロージャはデータをコピーします。参照がキャプチャされた場合(例えばクラス)、クロージャはクラスのインスタンスへの参照を保持しますが、[weak self]や[unowned self]を使用しないとretain cycleが発生する可能性があります。
キャプチャメカニズムは、非同期タスクやコールバックを実装するために役立ちますが、不適切な使用はメモリ管理を妨げます。
例:
class MyClass { var value = 10 func doSomething() { let closure = { [weak self] in print(self?.value ?? "nil") } closure() } }
この例では、非同期処理が行われるため、強い参照サイクルを回避するために[weak self]を使用しています。
質問:「クロージャでselfをキャプチャする際、必ず[weak self]を使用することでメモリリークを防げますか?」
回答: いいえ、クロージャ内に強い参照が発生する場合(例えば中間変数を介して)、[weak self]を使用してもretain cycleが発生する可能性があります。例えば:
let closure = { [weak self] in let strongSelf = self strongSelf?.doSomething() }
クロージャが長期間生存するオブジェクト(例えばNotificationCenter)にバインドされた場合、selfに対して他の参照が存在するとretain cycleを引き起こす可能性があります。
物語1
iOSアプリケーションのプロジェクトで、開発者がネットワークリクエストにクロージャを渡す際に
[weak self]を指定し忘れました。その結果、リクエストが実行されている間、ビューコントローラーのオブジェクトは解放されず、ユーザーが画面を閉じてもメモリリークが発生しました。
物語2
タイマーにサブスクリプトする複雑なシステムでは、
weakを使用せずにクロージャを介してオブジェクトがサブスクリプションを行い、画面がメモリから削除された後も動作を続けました。これにより、もはや存在しないUI要素が再び呼び出され、アプリケーションがクラッシュしました。
物語3
データキャッシュの実装時に、クロージャがキャッシュの長生きするオブジェクト内で
[unowned self]のアノテーションなしでselfへの参照をキャプチャしました。元のオブジェクトが破棄された後、クロージャからselfを参照しようとすると、すでに解放されたメモリへのアクセスが発生し、アプリケーションがクラッシュしました。