メモリー管理について
「alloc」や「new」で始まる名前のメソッドや「copy」を含む名前のメソッド(alloc、newObject、mutableCopy 等)を使用してオブジェクトを作成したときや、オブジェクトにretainメッセージを送信したときは、そのオブジェクトの所有権が発生します。
オブジェクトに所有権がある場合、releaseまたはautoreleaseを使用してオブジェクトを解放(所有権を放棄)する責任があります。それ以外の方法でオブジェクトを受け取った場合、そのオブジェクトを解放してはいけません。
メモリ確保とリテインカウント
値とオブジェクト
はじめに「値」と「オブジェクト」の違いを認識しておきます。
値とは、変数の値を意味します。変数は型が決まっており、型により長さ(必要なメモリのビット長)が決まります。
オブジェクトとは、インスタンスを意味します。インスタンスの実態はヒープ領域に確保され、その先頭アドレスが変数に格納されます。
例えば、CGFloat型の変数f とあるクラスのインスタンス(SomeClass)*sc があるとします。
fは、決まった長さで最初から確保されているためメモリ管理不要で値が直接格納されています。scは、ヒープ領域にインスタンスが確保されその先頭アドレスが格納されています。メモリ管理が必要となります。
リテインカウント(参照カウント)
すべてのオブジェクトは参照カウンタを持っています。この参照カウンタを使ってメモリ管理します。
参照カウンタの値は、そのオブジェクトが参照されている数です。使用中に対象のオブジェクトがメモリから解放される事を防ぐために、使用するときカウントを増やし、使用しなくなったらカウントを減らします。誰も使わなくなったらカウンタが 0 になり解放されます。インスタンスを作った時の参照カウンタは1になるため、インスタンスを作った場合、カウントを増やす必要はありませんが使用しなくなったらカウントを減らす必要があります。
これだけのシンプルなルールでメモリ管理します。カウントを増やすことは所有権を持つ事を意味します。
オブジェクトの解放
参照カウンタがゼロになったらオブジェクトを解放します。解放時の処理はdeallocで記述しますが、deallocメソッドを直接呼ぶことはなく、releaseメソッドから呼ばれます。iOSでは、ガベージコレクションの機能がないので、きちんとメモリを解放する必要があります。解放されていないメンバー変数をdeallocメソッドで解放、解放されていないローカル変数を再度値が代入される前や関数の return前に解放するなどなど。
メモリ管理
処理 | 意味 |
---|---|
alloc | allocはNSObjectクラスのクラスメソッドで、インスタンスを生成します。実際には初期化(init)が必要です。allocすると参照カウンタは1となります。 |
retain | retainはインスタンスメソッドで、参照カウンタを一つ増やします。 |
release | 参照カウンタを一つ減らします。retainとreleaseは対と考えるようにします。所有権を得たら(retain)必ず解放(release)します。参照カウンタがゼロになったらdeallocが呼ばれます。解放を意識し解放したらnilを代入するようにしましょう。 |
dealloc | インスタンスを解放します。直接deallocを呼び出すコーディングをすることはありません。参照カウンタがゼロになったら呼ばれます。解放したオブジェクトにメッセージを送るとクラッシュします。つまりallocとdeallocは必ず対応しなければクラッシュかリークの原因となります。 |
autorelease | autoreleaseもNSObjectのメソッドです。直近(スタックの一番上)のオートリリースプールに登録します。autoreleaseとして作られたインスタンスは release する必要はありません。 |
copy、mutableCopy | allocと同様にコピーしたインスタンスの参照カウンタ1となります。コピー元のリテインカウントだけはコピーしません。 |
基本ルール
自分が alloc, copy, retain したオブジェクト以外は release しては行けない
他人の作成したオブジェクトを自分が保持し続けたいなら retain する
自分が retain したオブジェクトは必ず release する
このことが、メモリ管理の基本です。
セッターメソッド
別のオブジェクトを受け取り、自分のインスタンス変数として保持する際のセッターのひな形があります。このひな形を利用することで、リークが発生やクラッシュを避けることが出来ます。
- (void)setFoo:(TypeOfFoo *)aFoo { if (memberVariableFoo_ != aFoo) { [memberVariableFoo_ release]; memberVariableFoo_ = [aFoo retain]; } }
- (void)setBar:(TypeOfBar *)newBar { if (memberVariableBar_ != newBar) { [memberVariableBar_ release]; memberVariableBar_ = [newBar copy]; } }
※ インスタンス変数が文字列の場合、単にretainするのではなくコピーした方が良いか検討しましょう。
autorelease
autoreleaseとして生成されたインスタンスは、自分で release を行う必要がありません。とても便利に思えるかもしれないが、メモリの少ない環境ではメモリ不足に陥ることがあるので注意が必要です。
autoreleaseを制御する仕組みとして、NSAutoreleasePoolクラスがあります。すべてのインスタンスはどれでもautorelease メッセージを送ると、最も最近作られたオートリリースプールに登録されます。オートリリースプールが解放される時に、登録されているインスタンスにreleaseメッセージを送ることで解放します。
Run Loop でのイベント処理の最後にオートリリースプールが解放され、そのタイミングでインスタンスが解放されます。
この仕組みを実現するため、Run Loopは常に
オートリリースプールを作る
イベント処理を行う
オートリリースプールを解放する
メモリ消費を下げる手段
自分でオートリリースプールを作り、処理を行った後でオートリリースプールを開放することで、オートリリースプールに登録済みのオブジェクトをRun Loopに戻る前に解放することが出来ます。
NSAutoreleasePool *localPool = [[NSAutoreleasePool alloc] init]; // なんらかの処理 // localPoolが使用される [localPool release];
autoreleaseのオブジェクトが途中で解放されてしまう?
通常は、処理がメインのイベントループ内でイベントループに戻らないのであれば不意にリリースされる心配はありません。ただし関数から抜ける(return)とその戻り先が Run Loopだとしたら解放されてしまうかもしれません。
注意が必要なのは、スレッド処理です。別スレッドの場合、それぞれオートリリースプールを作らなければなりません。スレッド処理中にメインのRun Loopがプールを解放する可能性があるからです。
オートリリースプールの解放で、登録済みオブジェクトにreleaseメッセージが送られます。この時点でリテインカウントが2以上のものはdeallocされずに残ります。autorelease済みであってもretainすることで所有しRun Loopに関係なく使い続けることは可能です(管理が複雑になるのでお勧めしません)。
メモリ管理と書き換え
自分が所有しているインスタンスでも、誰かが書き換える場合があります。インスタンス変数など自分が所有のオブジェクトでも、他から参照されていれば書き換えられる可能性があります。特に文字列の場合、可能性も高く影響もおおきいので注意が必要です。
書き換えを完全に防ぐにはセッターでコピーする。文字列はセッターで受け取った時にコピーするか、プロパティでcopy属性にすると安心です。
ただし、コピーはメモリとパフォーマンスの面で注意が必要です。コピーすることで余分なメモリを消費し、コピー処理にCPUに負荷がかかるので時間を消耗します。
メモリ不足
iOSが動作するデバイスは、あまり多くのメモリを搭載していません。また、仮想記憶の仕組みでメモリをスワップするということも行いません。メモリが不足したらソフトウェアで通知してきます。iOSのアプリケーションを作る場合、メモリの使用量をきちんと把握し適切な利用を心がける必要があります。他のアプリケーションが動作していることも考慮しておく必要があります。
メモリ警告をシミュレート
SDKのシミュレータは、実行中のアプリケーションへメモリ警告を通知することができます。シミュレータは、Mac OS X環境で動作しているため実際にはメモリ不足にはなりません。(先に Mac OS X が動作しなくなってしまいます。)
そこで、シミュレータのメニューから「メモリ警告をシミュレート」を実行することでメモリが不足している状態をシミュレートしてくれます。メモリ警告が発生するとアプリケーションは表示していないビューを消すなどの対応処理が動きます。
「メモリ警告をシミュレート」した後にすべての操作を確認(タブやナビゲーション切替え等)し、すべてのビューが正常に表示できるか確認してください。
メモリ不足時の対処
メモリ不足の状態になるとアプリケーションデリゲートメソッドや各コントローラーのメソッドが呼ばれます
- - (void)applicationDidReceiveMemoryWarning
- - (void)didReceiveMemoryWarning
これらのメソッドを利用して、メモリ不足にアプリケーションを正常に動作させるような処理を記述します。
何をするか?何を解放するか?
メモリ不足が発生した時点で、直接利用していないオブジェクトなどで再度作成可能なものはできるだけ解放します。大きなオブジェクトを解放するのが効果的です。
ファイルからロードしている画像や音声といった大きなデータを検討します。nilであればファイルから読み込むといったコーディングにしておけば、必要となったときに再度読み込まれます。オブジェクトを解放したら、nilを代入しておきます。キャッシュデータなどディスクから再度読み込めなおせるものや、中間結果等もう一度計算可能なデータも解放してよいか検討すると良いでしょう。パフォーマンスとの兼ね合いで判断すると良いでしょう。