問題の歴史:
Objective-Cの進化とSwiftの登場に伴い、Appleではプログラマーがretain/releaseコマンドに明示的に関与することなく、アプリケーションのメモリを自動かつ安全に管理する方法が求められました。Swiftは、クラスオブジェクトへの参照の数を追跡し、最後の所有者が消えると自動的にメモリを解放するAutomatic Reference Counting(ARC)を使用しています。
問題:
Swiftはマルチパラダイム言語であり、値型(struct, enum)と参照型(class)を操作します。特に参照型においては、メモリを適切なタイミングで解放することが重要であり、そうでなければメモリリークが発生する可能性があります。retain cycle(循環参照)が発生する場合、2つのオブジェクトが互いに参照し合い、ARCがメモリを解放できなくなるという厄介な状況になります。
解決策:
SwiftはARCをクラスオブジェクトのみに適用します。参照の数が0になると、メモリが解放されます。retain cycleの問題を解決するために、weak/unownedといったキーワードが使用されます。
コード例:
class Person { var name: String var pet: Pet? init(name: String) { self.name = name } deinit { print("\(name)が解放されます") } } class Pet { var owner: Person? init() {} deinit { print("ペットが解放されます") } } var tom: Person? = Person(name: "Tom") var cat: Pet? = Pet() tom!.pet = cat cat!.owner = tom tom = nil cat = nil // 両方のオブジェクトは解放されない、retain cycleが発生
主な特徴:
値型にARCを適用できますか?
いいえ、ARCはクラスオブジェクトへの参照のみを追跡します。構造体とenumは値渡しされ、Swiftは自動的にメモリを解放します(通常はスコープから出るとき)。
相互に参照し合う2つのオブジェクトのプロパティにweak/unownedを使用しないとどうなりますか?
循環参照が発生し、ARCはそのオブジェクトに対するメモリを解放しなくなります。このようなコードはメモリリークを引き起こします — このような参照の片方には常にweak(またはunowned)を使用してください。
weakがあるのにunownedは何のために必要ですか?
unownedは、参照が他のオブジェクトのライフサイクル中は常に存在し、nilになることがない場合に必要です。weakはnil可能な参照、unownedはnilでない参照ですが、retainカウンタは増加しません。
コード例:
class A { var b: B? } class B { unowned var a: A // Bのライフサイクル中は常にnilではない }
2つのコントローラが互いに強い参照を持っている。一方はデリゲートプロパティを介して、もう一方は子コントローラの配列を介して。作成者はデリゲートにweakを使用しない。
利点: エラーはすぐには見えず、一見すべてが「機能する」。
欠点: アプリケーションの規模が増すにつれてメモリが解放されず、リークが発生;制限されたメモリを扱うとクラッシュの可能性がある。
デリゲートとイベントリスナーはweakとして設定され、コントローラの配列はコントローラが消えるにつれてクリーンアップされる。すべてがきちんと文書化されている。
利点: リークがなく、すべてのオブジェクトがタイムリーに解放され、パフォーマンスの低下がない。
欠点: デリゲートが予期せず失われる可能性がある(どこかで強い参照を忘れた場合);アプリケーションのアーキテクチャに注意が必要。