読者です 読者をやめる 読者になる 読者になる

iPhoneのObjective-Cのメモリー管理

iPhone

最近Bluetooth通信のプログラムを書いてるですが、突然落ちたりするので調べて行くと参照しているオブジェクトが既に解放されていたりとメモリー管理の問題がよく発生していました。そこで、もう一度Objective-Cのメモリー管理に付いて学んでみました。

詳解 Objective-C 2.0

インスタンスの生成・解放

iPhone用のObjective-Cではガベージコレクションはサポートされてないので、オブジェクトの生成・解放はプログラムが正しく面倒をみてあげないとメモリーリークしたり、解放済みのオブジェクトを参照したりして誤動作してしまいます。

Objective-Cではオブジェクトの生成・解放を管理するために、リファレンスカウンターを使っています。これは生成されたオブジェクト(メモリー)が参照されている数で、オブジェクトを利用する場合このリファレンスカウンターを +1 し、不要になったら -1 します。ちなみに、オブジェクトが生成された時点でのリファレンスカウンターは1です。
そして、リファレンスカウンターが 0 のオブジェクトはだれも使ってない不要なオブジェクトなので解放してよいことになります。

メモリー管理メソッド

  • alloc オブジェクトの生成を行います、この中でメモリーが確保さ、リファレンスカウンターは1に設定される。
  • dealloc オブジェクト(メモリー)を解放メソッド。クラスの中で独自に生成しているオブジェクトの解放等を行う為に、クラスには定義する必要がある。ただしこのメソッドをアプリから呼び出してはいけない。
  • retain オブジェクトを参照する事を知らせるメソッド。リファレンスカウンターは +1されます。
  • relaese オブジェクトが不要になった事を知らせるメソッド。リファレンスカウンターは -1され 0 になれば deallocが呼び出されます。
  • copy 元になるオブジェクトをコピーし新しいオブジェクトを生成します。リファレンスカウンターは1に設定されます。
  • init オブジェクトの初期化で直接メモリー管理とは関係がありません。

自動開放 autorelease

オブジェクト(メモリー)の解放をOSに任すための仕組み。 alloc等で生成したオブジェクトに autoreleaseメッセージを送ると、そのオブジェクトは 自動開放プール に登録され、イベント待ち等で処理がOSに移った際に release が自動的に行われます。

また、[NSString stringWithFormat: ...]などでコンビニエントコンストラクターで生成されたオブジェクトは最初から自動開放プールに登録されています。

新規作成したオブジェクトを autorelease にしてしまえば解放の事を忘れる事ができるので、 Google Objective-C Style Guide , (日本語訳) でも autorelease が推奨されています。

autorelease の注意点(1) イベントをまたいで有効なオブジェクト

一つのメソッド内で役目を終えてしまうオブジェクトや1つのイベント処理内で役目を終えてしまうオブジェクトには autorelease は最適ですが、イベントの処理後に他のイベント等で使うオブジェクトが autoreleaseの場合、イベント待ちに入った瞬間に解放されてしまい、そのオブジェクトを別のイベントで参照しようとした瞬間に問題が発生します。

そこで、イベントをまたいで有効なオブジェクトは

  • alloc でオブジェクトを生成する
  • コンビニエントコンストラクターで生成されたオブジェクトは retain メッセージを送る

のどちらかを行う必要がります。 また、そのオブジェクトが不要になった場合は責任もって release または autorelease する必要があります。

@flexfrank さんが指摘してしてくれたのですが、イベントをまたいで有効なオブジェクトは以下くらいだと思います。

グローバル変数はあまり使わないでしょうから、通常はインスタンス変数のみ autorelease でないオブジェクトを代入するようにすれば良さそうです。

autorelease の注意点(2) ループ内で大量にオブジェクトを使い捨てする

ループ内で大量にオブジェクトを使い捨てするようなコードが在った場合 autorelease なオブジェクトであったとしても、OS に戻るまでは解放されませんのでメモリーや自動開放プールを使いこなしてしまいます。

そもそも、ループ内で大量のオブジェクトを使い捨てするようなコード自身が問題ですが・・・・

どうしても、ループ内で大量のオブジェクトを使い捨てるようなコードを使いたい場合は、自前で自動開放プールを作り適宜解放する方法があります。詳細は書籍等を参照して下さい。 (1/17追加)

autorelease でないオブジェクトの注意点

autorelease でないオブジェクトを変数に代入する際の注意点として、元々その変数に入っていたオブジェクトのrelease があります。
元々その変数に入っていたオブジェクトは不要になっているので、 release するのを忘れないようにしないといけません。

if (var1 != new_value) {
    [var1 release];
}
var1 = [new_value retain];

または、autorelease を使うこともできます。

[var1 autorelaese];
var1 = [new_value retain];

ちなみに、宣言プロパティ (@property と @sysnthesize)で作られるインスタンス変数へのセッタ(プロパティへの代入)は上の if を使ったコードが使われるので、インスタンス変数への代入は全て宣言プロパティを使うようにすればこの問題は発生しません。
ただし、プロパティを宣言したインスタンス変数は外からアクセス出来るようになってしまうので、全てのインスタンス変数にプロパティを宣言するのはどうかと思います(よくサンプルコードで見かけますが・・・)。

デバイス関連オブジェクトの注意点

GPSBluetoothなどのデバイスを扱っているクラスを頻繁に、例えば viewWillAppear:メソッドで alloc し、 viewWillDisappear:メソッドで release すると、View切り換えを何度か行うと落ちたり、変な動作をする事があります。
これらのオブジェクトの allocはアプリ起動に(例えば viewDidLoad で)1回のみ行い、解放は終了時に(例えば viewDidUnLoad で)1回のみ行うようにした方が安全そうです。

メモリーリークの検出 (1/17追加)

メモリーリークの検出には Instruments が便利です。
ただし、リークでないものがレポートされたりと必ずしも完璧に指摘してくれるのわけではないので結果をちゃんと考慮しながら使って下さい。