Swiftのメモリ管理を知る
Swiftの優秀な言語仕様のおかげで、私たちがメモリ管理について意識することは少なくなりました。一方で、Swiftのメモリ管理方式では循環参照が発生し、メモリリークが起こることもあります。本記事ではこのような問題の対処方法も含め、Swiftにおけるメモリ管理を解説します。
値型のメモリ管理
値型は、変数が使われなくなると(=スコープの範囲外に出ると)すぐに自動で解放されます。値型に関してはメモリ管理を考える必要はありません。
参照型のメモリ管理
参照型は、その名の通り複数の箇所から1つのメモリを参照するので、使われなくなったかどうかを単純に判定することができません。そこでSwiftでは、Automatic Reference Counting, 通称ARCという方式を用いて、メモリ領域を自動的に管理してくれます。他言語ではGC(ガベージコレク ション)という名で同様の仕組みが提供されていることが多いですが、ARCはGCの1種と言えます。
ARCはメモリ上のインスタンスを管理し、使われなくなったインスタンスは破棄してメモリを解放します。メモリ上のインスタンスが不要かどうかは、ARCでは参照カウントを用いて判定します。
参照カウント
参照カウントは、プロパティや変数、定数からインスタンスへの参照がいくつあるかをカウントするものです。参照元が増えた場合は参照カウントを1増やし、減った場合は参照カウントを1減らします。この参照カウントが0になった時、ARCは該当インスタンスが不要と判断し、メモリの解放を行います。
参照の種類
Swiftにおいて、参照の仕方は以下の3種類があり、これらを宣言時に指定することで参照の仕方を指定することができます。
- 強参照:
strong
- 弱参照:
weak
- 非所有参照:
unowned
指定がなければ強参照となります。強参照されたインスタンスは、先ほど説明したように参照カウントが1増やされます。
弱参照を行うと、参照カウントは変わりません。あるインスタンスを弱参照していたとしても、そのインスタンスが他に強参照されていなければメモリが解放されます。インスタンスが破棄された場合、弱参照元にはnilが代入されます。
非所有参照も同じく、参照カウントが変動しません。弱参照と異なる点は、変数にnilが代入されない点です。インスタンスが存在している間はunwrapせずに利用できますが、インスタンス解放後に変数にアクセスするとエラーが発生します。
循環参照
このように参照の種類がいくつか存在するのは、循環参照を防ぐためです。循環参照とは、インスタンス間でお互いを強参照しあった場合に参照カウントが0にならず、メモリ上にインスタンスが残り続けてしまう状態です。
上記の例では、johnがguitarを強参照しており、guitarもjohnを強参照している循環参照が起こっています。これを解決するためには、どちらかの参照をweakもしくはunownedにします。これによって循環参照は解消され、不要なインスタンスは破棄されます。
クロージャにおける循環参照
Swiftでは関数は第一級オブジェクトとして扱われるので、Int型やString型と同じように引数として渡したりインスタンス化したりすることができます。クロージャもインスタンスということになりますので、クロージャ内でインスタンスを利用した際に循環参照が起こる場合があります。
上記の例では、Person
インスタンスがgetFullName
インスタンスを参照しており、getFullName
インスタンスからPerson
インスタンスを参照しているので、循環参照が発生しPerson
インスタンスが破棄されません。こうした循環参照を避けるために、クロージャ内でインスタンスを利用する場合にはキャプチャリストを用います。キャプチャリストでは弱参照/非所有参照を指定することができるので、 クロージャの解放状況に依存せずにクラスのインスタンスを解放することが可能になります。
一方で、クロージャ内からインスタンスを強参照しても循環参照とならないケースもあります。クロージャがnon-escapingである場合です。
クロージャにはescapingとnon-escapingの2種類があり、宣言時に@escaping
をつけることでescaping closureとなります。escaping closureはスコープを抜けても保持されたままとなり、非同期的な実行が可能となります。non-escaping closureはスコープ内で実行され、スコープを抜けると破棄されます。.compactMap
などがnon-escaping closureにあたりますが、これらはスコープを抜けると破棄されるので循環参照が発生しません。
以上でSwiftのメモリ管理に関する解説を終わります。メモリリークを防ぐためにも、しっかり理解した上で実装しましょう。
参考文献