Objective-Cを扱い始めて最初に戸惑ったのは、メモリの管理だ。
慣れるまでだいぶ苦労したので、ポイントをまとめておこうと思う。
ただし、ここにまとめる内容は超基本事項。とくにすばらしい機能ではない。。
しかしながら、数ヶ月ほどほかの言語を書いていると案外忘れてしまうものなので(汗)、自分のためにまとめておく。
そもそもリファレンスカウンタとは何か。
リファレンスカウンタ方式のメモリ管理では、そのインスタンスが何カ所から参照されているかを示すカウンタを用いてメモリを管理する。
同じインスタンスを複数のオブジェクトが参照しているとしても、複数個、同じオブジェクトを作成するのではなく、同一のオブジェクトを全員が参照するのだ。
はじめはだいぶ戸惑ったこの方式、慣れてしまえばお手の物。
インスタンスの生成と解放
何はともあれ、あるオブジェクトを使いたければ、インスタンスを生成をしなくてはならない。そして、使い終わったらもちろん解放(破棄)する必要がある(当たり前だが、解放しないと恐怖のメモリリーク!)。
ここでポイントとなるのは、生成の方法によって解放の仕方が異なること。
これを知らずに手探りで生成や解放をするのはとても危険。
|   | 生成 | オーナーシップ | 解放 |
| 一般的なインスタンス | init または copy | 生成元 | release |
| 一時的なインスタンス | クラス名で始まるインスタンス変数 | なし | 不要 |
このほかに、解放しないインスタンス変数も存在するが(例:シングルトンパターンの実現用)これについては別途まとめることにする。
例として、NSStringクラスのオブジェクトを生成、解放する場合、以下のようになる。
一般的なインスタンスオブジェクト
NSString *string = [[NSString alloc] initWithString:@"hoge"]; ....... ....... [string release]; |
生成されたオブジェクトのオーナーシップは、生成元が持つことになる。
したがって、生成元が解放まで責任を持つ必要がある。
また、init(もしくはcopy)にて生成されたオブジェクトは、その時点でリファレンスカウンタが+1となっているため、新たにretainを送る必要はない。
一時的なインスタンスオブジェクト
NSString *string = [NSString stringWithString:@"hoge"]; |
生成されたオブジェクトは、その時点で、自動解放プールに登録されているため、自ら解放する必要はない(してはいけない)。
一時的なインスタンスオブジェクトを生成するためのクラスメソッドは多数用意されている。
NSStringやNSArrayのクラスメソッドは中でもよく目にするものではないだろうか。
インスタンス生成方法の使い分け
一般的なオブジェクトと、一時的なオブジェクト、どのように使い分けるのがいいのだろう。
Objectiv-Cの勉強を始めた頃、この二つの方法をうまく使い分けられていなかった気がする。
一見、一時的なオブジェクトを生成しておけば、release忘れによるメモリリークもないし、なんだか安心な気がしていた。
が、それは大きな間違い。
なぜか。
メモリには限りがあるから!(ここから先は、iPhoneアプリの開発に限った話)
2011.11.12修正
何も注意しないでいると、iPhoneアプリの自動解放プールは、アプリ起動時にひとつ作られ、アプリ終了時に解放される。
つまり、ほとんど意味をなさない。。。
この状態で、一時的なオブジェクトを生成し続けると、結果、メモリ不足に悩まされるだろう。
AppKitを使っていれば、AppKitがイベントループごとに新しいNSAutoreleasePoolを生成してくれる。したがって、イベントループ内で生成された一時オブジェクトはイベントループの終了時にリリースされる。
参照:Memory Management Programming Guide
それでもメモリに不安な場合は以下の方法で回避する。
生成、解放をこまめに行う
まず一つ目の方法は、もちろん、生成と解放をこまめに行うこと。
むやみやたらに、stringWith… や arrayWith… などというクラスメソッドに頼らず、その都度、alloc と init を組み合わせてオブジェクトを生成する。
そして、使用後は必ずreleaseメッセージを送って解放する。
独自の自動解放プールを持つ
例えば、複数回(かなり多くの回数)まわる for ループの中で、大量のNSStringオブジェクトを生成して利用する必要がある場合を考えてみる。この場合、その都度オブジェクトの生成と解放を繰り返していてはオーバーヘッドも大きくなるだろう(推測)。
だからといって、すべてを一時的なインスタンスオブジェクトとして生成していると、メモリ不足となりアプリが強制終了してしまったり、動作が不安定になったりして困ることも。
そんなときは、ループ内で独自の自動解放プールを持つとよい。そして、ループを抜けるときに、この自動解放プールを解放する。
while (...) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
/* 一時的なオブジェクトを多数生成 */
��例)
NSString *string1 = [NSString stringWithString:@"hoge"];
.....
.....
.....
[pool release];
}
|
これで、ループ内でメモリが枯渇してしまうことは避けられる。
デバッグ時の注意
メモリまわりのデバッグをするときに注意したいこと。
それは、できるだけ実機テストを行うということ。
シミュレータでテストしていては、メモリは枯渇しない(仮にしたとしたら、実機では全く動作しないでしょう・・・)。
メモリ周りの問題はやっかいなことが多い。なかなか簡単に片付けることができなかったり、ときとして、設計変更を余儀なくされたり。。
開発終盤でこれでは辛いので、できるだけ早い段階で、メモリに関する設計ミスがないかは確認しておきたいところ。
静的解析の利用
以前にまとめたXcodeのツールであるBuild and Analyzeを使えば、生成と解放に関する記述ミスをある程度見つけてくれる。
例えば、initを使って生成したのにreleaseしていない、もしくは、一時オブジェクトとして生成したのにreleaseを送っている、など。
この二つを見つけられるだけでも、メモリリークや不正アクセスをかなり防ぐことができるので、開発初期段階ではこまめにチェックをしたいところ。
参考:iPhoneアプリ開発:XcodeのBuild and Analyze
参考図書
メモリ周りに関しては、知らなくてもある程度は動作してしまう。でも、ソフトウェアとしての品質を追求していくためには、絶対に押さえておくべきことと信じている私。
ということで、、、必死に勉強しました。以下、絶対おすすめ!


