【たのしいCocoa】06_メモリ管理

「たのしいCocoaプログラミング[Leopard対応版]」を元に、要約メモしておきたいと思います。
ガベージコレクションによるメモリ管理
■ ガベージコレクションとは
メモリ上の使われなくなったオブジェクトをゴミとみなして、定期的にそれを回収。そしてメモリ領域を再利用できるようにするのがガベージコレクション。
■ Objective-C とガベージコレクション
Objective-C 2.0 から使用可能なので、Mac OS 10.5 移行じゃないと使えない。ガベージコレクションの仕組みとして、ゴミ回収するときは一旦アプリケーションの実行を停止しなくてはいけない。このゴミ回収は、結構時間がかかる、パフォーマンスを最重要視するアプリケーションの場合は、あえてガベージコレクションを使用せずに手動でメモリ管理をする場合もある。iPhone では、Objective-C は使えるが、ガベージコレクションは使えない。
■ ガベージコレクションの有効化
ガベージコレクションは、標準では無効化されている。有効化するには「プロジェクト > プロジェクト設定を編集」メニューを選択。ビルドタブを選択して、この中から「Objective-C Garbage Collection」という項目を選択。ポップアップメニューには、Unsupported、Supported、Required の3つがある。Supported は、主にプラグインを利用するためのアプリケーションのために用意されている項目。ガベージコレクションに対応していないプラグインでも読み込んで実行できるようにするためのもの。Required は全てのコードをガベージコレクション対応のものとして扱う。通常は Required を選択する。
■ ガベージコレクションプログラミングの定石
ガベージコレクションの仕組みから、必要なオブジェクトはなんらかの形で参照しておくこと。参照されていないものはゴミとして回収されてしまう。逆に、いらなくなったオブジェクトは参照を解除する。そのためには変数に nil を設定する。
参照カウンタによるメモリ管理
ガベージコレクションを使わないメモリ管理。
■ 参照カウンタ
あるオブジェクトに対して、必要ならオブジェクトを参照する(refer)、または保持する(retain)。必要なくなったら参照をやめる(release)。
■ retain、release メソッド
以下は、ルートオブジェクトである NSObject のメソッド。全てのオブジェクトに対して呼び出せる。
NSObject
1 2 | - (id) retain このオブジェクトを参照する。参照カウンタを1上げる。返り値として自分自身を帰す。 |
NSObject
1 2 | - (void) release このオブジェクトに対する参照をやめる。参照カウンタを1下げる。参照カウンタが0になると、このオブジェクトは開放される。 |
retain メソッドを呼ぶと、返り値としてそのオブジェクト自体が返ってくる。
■ オブジェクトの確保から開放までの流れ
- 【1】AppController は、タイトルを表すオブジェクトをつくる。これによってメモリが確保される。このとき、文字列オブジェクトの参照カウンタは1。インスタンス化されたオブジェクトの初期値は1になる。
- 【2】AppController は、ボタンにタイトルを設定する。setTitle などのメソッドを使う。ボタンは、そのメソッドの中で、この文字列はまだ必要だと判断して、retain メソッドを呼び出す。これで、参照カウンタは1つ増えて2になる。ボタンでは、このオブジェクトを指し示すための変数をキープしておく必要がある。
- 【3】AppController からすると、このオブジェクトはもう必要ない。そこで、release メソッドを呼び出して参照をやめる。参照カウンタは1つ減り、1となる。これ以降、AppController 側では、文字列オブジェクトのための変数を捨ててしまっても構わない。
- 【4】ある程度処理が進んで、ユーザがウィンドウを閉じたとすると、その上にあるボタンも開放される。ボタンは開放されるときに自分が参照していた文字列オブジェクトに対して、もう必要なくなるので release メソッドを呼び出す。これで、参照カウンタが0になる。この時点で文字列オブジェクトは開放される。
クラスのインスタンス化
■ クラスのインスタンス化と初期化
クラスのインスタンス化を行うメソッドは alloc。これを呼ぶことでインスタンス化、つまりオブジェクトのためのメモリ管理が行われる。alloc はクラスメソッドなので、インスタンス化したいクラス名を指定して、alloc を呼ぶことになる。
NSObject
1 2 | + (id) alloc クラスをインスタンス化する。返り値は、そのインスタンスオブジェクトになる。 |
alloc でインスタンス化したら、必ず初期化をする。初期化メソッドはクラスごとに用意されている。基本的な初期化メソッドは init で、NSObject が持っているメソッドで、特別なメソッドを呼ぶ必要がないときはこれを呼ぶ。
NSObject
1 2 | - (id) init インスタンスを初期化する |
インスタンス化の基本は alloc と init を続けて呼ぶこと。例えば MyObject というクラスがあったらインスタンス化はこんな感じになる。
1 2 | MyObject* object; object = [[MyObject alloc] init]; |
この、[クラス alloc] init] と重ねて呼ぶのが、インスタンス化の基本その1。こうやって初期化すると、参照カウンタは1になる。誰かが release を呼ばない限り開放されない。
■ 自動開放
自動開放を使うと、その処理が終わった後、適切な時点で release メソッドを呼んでくれる。自動開放を使うには、autorelease メソッドを呼ぶ。
NSObject
1 2 | - (id) autorelease インスタンスを自動開放するようにする。返り値として自分自身を返す。 |
先ほどの MyObject を、インスタンス化してさらに自動開放する。
1 2 | MyObject* object; object = [[[Myobject alloc] inir] autorelease]; |
このように、[[[クラス名 alloc] init] autorelease] と3段重ねて呼ぶことになる。これがインスタンス化の基本その2。
■ インスタンスを作成するメソッド
1 2 | + (id) stringWithCString: (const char*) cString encoding: (NSStringEncoding) encoding |
これは引数としてCの文字列と、テキストエンコーディングを渡す。これを使って初期化したインスタンスを取得できる。そして、そのインスタンスはすでに自動開放されている。また、これと全く同じ引数をとる初期化メソッドも用意されている。
1 2 | - (id) initWithCString: (const char*) cString encoding: (NSStringEncoding) encoding |
こちらは初期化メソッドなので、alloc の後に呼び出すことになる。つまり、次の2つのオブジェクトは全く同じものになる。
1 2 3 4 5 6 7 | NSString* string0; string0 = [NSString stringWithCString:"new string" encoding:NSASCIIStringEncoding]; NSString* string1; string1 = [[[NSString alloc] initWithCString:"new string" encoding:NSASCIIStringEncoding] autorelease]; |
string0 はメソッドの呼び出し1回で自動開放されたインスタンスを作成している。それに対して string1 では3回メソッドを呼び出している。多くのクラスでは同じ引数を指定できる、自動開放されたインスタンスを作成するためのメソッドと、初期化を行うためのメソッドがある。用途に応じて便利な方を使う。
■ インスタンスの開放
参照カウンタが0になるとそのインスタンスは開放される。その開放される直前に呼び出されるメソッドが、dealloc。
NSObject
1 2 | - (void) dealloc インスタンスが開放される直前に呼び出される。 |
サブクラスでこのメソッドを上書きすることで、開放されるときに必要な処理を行うことができる。もしクラスの中でなんらかのインスタンスを保持していたらここで必ず開放しないと、メモリリークが起きる。RSSリーダの AppController クラスには、次のように dealloc メソッドを実装していた。
1 2 3 4 5 | - (void)dealloc { [document release]; [super dealloc]; } |
ここでは document というインスタンス変数の release メソッドを呼び出している。このようにインスタンス変数を開放している。
親クラスの dealloc の呼び出し
dealloc ではその後に [super dealloc] という文が続く。super というのは、親クラスのメソッドを呼び出すためのキーワード。ここでは AppController の親クラス、NSObject のdealloc を呼び出している。dealloc メソッドを上書きした場合はかならずこの一文を書いておく必要がある。
メモリ管理の定石
■ 一時的なオブジェクト
NSAutorelesePool
時限付きゴミ袋のようなもの。自動開放を行うには、まず NSAutoreleasePool のインスタンスを作る。その状態で autorelease メソッドを呼び出すと、インスタンスが NSAutoreleasePool に登録される。最後に NSAutoreleasePool を開放すると、登録されているすべてのオブジェクトの release メソッドを呼び出す。こうやって自動的な開放を実現している。
NSAutoreleasePool の作成と開放のタイミング
Cocoa アプリケーションを立ち上げると、その内部でイベントループと呼ばれるループが回ることになる。これはユーザからのキーボード入力やマウス操作を待ち受けるループ。このイベントループの内部なら、いつでも自動開放を使えるということになる。
ほとんどの処理は Interface Builder で登録したアクションをトリガーとして始まる。そしてアクションはイベントループから呼び出されて実行が始まり、終わるとループが一周する。ということは、アクション実行中は常に NSAutoreleasePool のインスタンスがある。そして、アクションメソッドが終わると、自動開放したオブジェクトは開放される。
■ メモリ管理の定石のまとめ
オブジェクトを保持するか、一時的に使うだけか判断する
そのメソッドの内部でしか使わないか、メソッドが終了した後でも何度も使うかの違い。
保持するメソッドは、参照カウンタを上げる
保持する必要があるオブジェクトに対しては、参照カウンタを1つ上げておく。他からもらったオブジェクトの場合は、retain メソッドを呼ぶ、自分でインスタンスを作成するときは alloc-init の組み合わせで作る。または、init… で始まる初期化メソッドを使う。
一時的なオブジェクトは自動開放する
インスタンスの作成を、alloc-init-autorelease の3段重ねで行う。または、それぞれのクラスに用意されている自動開放されたインスタンスを作成するためのメソッドを使う。
dealloc ですべて参照カウンタを下げる
保持しているオブジェクトは最後に開放しなくてはいけない。それを行うのが dealloc。dealloc メソッドを上書きして、保持しているインスタンス変数全ての release メソッドを呼び出す。あくまで、release メソッドを呼び出されて参照カウンタが0になったオブジェクトだけが開放される。
外部リンク
- »連載:目指せ!iPhoneアプリ開発エキスパート|gihyo.jp … 技術評論社
- »iPhoneネイティブアプリ開発の必読リンク5本 | iPhone 3G Wiki blog
- »【特集】iPhoneアプリケーション開発入門 | エンタープライズ | マイコミジャーナル
- »自作アプリをApp Storeで世界に向けて販売できる!! iPhone SDKプログラミング大全
