iBooks にある “The Swift Programming Language” の勉強メモ。Objective-C と C を普段書いている自分から、ちょっと馴染みがないものを特にまとめておきます。目次は こちら。
今回は、Automatic Reference Counting に関して。
Automatic Reference Counting
Swift はメモリ管理をする際に、ARC を使用します。基本的にメモリ管理は Swift に委ねて問題ありません。
1. ARC 概要
メモリ管理は、 リファレンスタイプであるクラスで特に問題になります。クラスのインスタンスが作成されるたびに、ARC は新しくメモリを割り当てます。そして、必要がなくなった際に、解放され再利用されます。もし、解放された後、そのインスタンスを使用とすれば、アプリはほとんどの場合クラッシュします。
このため、インスタンスが必要がなくなったタイミングを判定する必要があります。ARC は、いくつのプロパティ・コンスタント・変数がそのインスタンスのリファレンスを持っているかを数えています。少なくとも 1 つからでも参照されている場合、そのインスタンスは解放されることはありません。このプロパティ・コンスタント・変数がインスタンスを参照していることを strong reference
といいます。
基本的な ARC の動作を例と一緒にみていきます。
1 2 3 4 5 6 7 8 9 10 | class Person { let name: String init(name: String) { self.name = name println("\(name) is being initialized") } deinit { println("\(name) is being deinitialized") } } |
とてもシンプルなクラスです。
1 2 3 | var reference1: Person? var reference2: Person? var reference3: Person? |
3 つの Optional な変数を定義します。
1 2 | reference1 = Person(name: "John Appleseed") // prints "John Appleseed is being initialized |
新しいインスタンスが作成されました。init
により、初期化されたことがアウトプットされます。この段階で、reference1
から Person クラスのインスタンスに対しての strong reference
があると言うことができます。少なくとも 1 つの strong reference
があれば、メモリが解放されることはないので、このインスタンスはメモリ上にまだ存在します。
そして、
1 2 | reference2 = reference1 reference3 = reference1 |
この代入により、3 つの strong reference
がこの 1 つのインスタンスに作られました。
1 2 | reference1 = nil reference2 = nil |
上記のコードで、2 つの strong reference
がなくなりました。最初にアサインされた reference1
が削除されても、まだ reference3
からの strong reference
が残っているので、このインスタンスへのメモリは解放されません。
ここで、
1 2 | reference3 = nil // prints "John Appleseed is being deinitialized |
この段階で、全ての strong reference
がなくなりましたので、ARC はメモリを解放します。その前に、deinit
が呼ばれるので、その旨がアウトプットされます。
2. Strong Reference Cycles
ARC の問題は、プログラマーがメモリ管理に対して気を配らなければならない場合があることです。それが、Strong Reference Cycles という問題です。これは、インスタンスへの参照(strong reference
)が決して 0 にならない状況が考えられるためです。このインスタンスへの参照の数が 0 にならない場合、このインスタンスへのメモリは解放されません。
この問題を解決するために、参照を strong
として定義するのではなく、weak
や unowed
と定義することができます。
まずは、具体的に weak
や unowed
を使用する前に、どのような状況でこの Strong Reference Cycles が起こるのか紹介します。
1 2 3 4 5 6 7 8 9 10 11 12 13 | class Person { let name: String init(name: String) { self.name = name } var apartment: Apartment? deinit { println("\(name) is being deinitialized") } } class Apartment { let number: Int init(number: Int) { self.number = number } var tenant: Person? deinit { println("Apartment #\(number) is being deinitialized") } } |
先ほどの Person クラスのプロパティの 1 つに Apartment へのインスタンスがあります。また、その Apartment にも Person への参照があります。
1 2 | var john: Person? var number73: Apartment? |
これらの参照を保持する変数を定義します。
1 2 | john = Person(name: "John Appleseed") number73 = Apartment(number: 73) |
メモリ割り当て、初期化。そして、ついに Person に Apartment を、また、Apartment に Person を関連付けます。
1 2 | john!.apartment = number73 number73!.tenant = john |
!
は、参照を持っている変数からインスタンス自身にアクセスします。C でいう ->
でしょうか。
イメージで見ると現在は、こんな感じです。
そして、2 つの変数が持っている strong reference
をなくします。
1 2 | john = nil number73 = nil |
この際に、deinit
に定義されている println は呼ばれません。つまり、メモリ解放は行われていません。まだ 下記のイメージのように Person のインスタンスと Apartment のインスタンスに対する strong reference
が存在するからです。この 2 つ変数に nil をセットしたので、基本的にこのインスタンスにアクセスする方法はなく、メモリリークしています。