東北地方太平洋沖地震で世間は大変な状態です。
尊敬するブロガーさん達も、
様々な情報共有エントリーを投稿して、
少しでも役に立とうと頑張っておられます。
そんな中ですが、全く平常通りです。
面倒なんでしょうか。
所詮は他人事なんでしょうか。
何でだろ?自分でもわかりません。
と言うわけで、今回読む章は…
4 メモリ管理のパターン
iOS開発の様な、小型デバイス向けの開発では、
避けて通れないと思われる章です。
4.1 Cocoa touch のメモリ管理
iOS開発においては、ガベージコレクション *01不要になったメモリを自動で解放する仕組み。
お仕事していて、机の上が散らかったら、自動で片付けてくれるメイドさんとでも思って下さい。 が期待できないことが書かれています。
その代わり、Cocoa touchには「参照カウントベースのオブジェクト管理」
と呼ばれる手法があるので、それを活用するようにとも書かれています。
4.2 メモリ管理のパターン
メモリ管理で解決すべき問題は2つ。
1.メモリリーク
2.解放済みオブジェクトへのアクセス
「メモリリーク」は、
解放されないメモリ領域が蓄積されて、アプリの活動領域を圧迫すること。 *02片付けずに次の事を始めすぎて、机の上がいっぱいになったと考えて下さい。
「解放済みオブジェクトへのアクセス」は、
既に解放したオブジェクトにアクセスしようとすること。
大抵の場合、アプリのクラッシュを招く。 *03すでに片付けてしまった道具に手をのばして、突き指するようなもの。
4.2.1. 参照カウンタによるメモリ管理
「参照カウンタ」と呼ばれる手法によるメモリ管理方法の紹介。
ひと言で表すなら「オブジェクトの利用者数」。
カウンタによるメモリ管理には、3つのメソッドを使う。
1.retain
→ カウンタを上げる。
2.release
→ カウンタを下げる。
3.autorelease
→ イベントループ終了後に自動でreleaseを呼び出す。
参照カウンタがゼロにならない限りは、
どこかでそのオブジェクトが使われているので、
オブジェクトを解放してはいけない。
重要なのは、
各オブジェクトのカウンタ操作のタイミングと言う事になる。
4.2.2. インスタンス変数とオーナーシップ
オブジェクトの参照をインスタンス変数として持つことによるメモリ管理。
注意点としては、循環参照の問題がある。
複数のオブジェクト間でお互いの参照カウンタを上げ合い、
いつまでもメモリ解放されないケース。
これを回避するには、
カウンタの保持関係を相互にしないようにする必要がある。
また、「この本を貫く重要な原則」として、
「インスタンス変数は保持する」と言う点が挙げられている。
これは、
あるオブジェクトがあるとき、
それはその全てのインスタンス変数のオーナーとなっている。
と言うことらしい。
が、正直なところ、まだピンときていない。 *04自分の持つ知識で言うと「オブジェクト」と「インスタンス」は、ほぼ同義なのだが、どうも違うようだ。
この点については、今後の章で明らかになって行くと考えられるので、注意して読み解いていきたい。
あるいは、Objective-C独自の定義が存在する可能性もあるので、勉強しよう。
4.2.3. setter のパターン
インスタンス変数にオブジェクトを設定するためのメソッドを紹介。
「セッターの実装」自体はクラス設計には一般的な内容だが、
メモリ管理の視点からの落とし穴を交えて解説してくれている。
1.保持のない参照
- (void)setName:(NSString*)name { _name = name; }
参照の保持がされないため、_nameの解放タイミングがわからない。
メモリリークや解放済みオブジェクトへの参照を招く。
よって、参照を保持する仕組みが要る。
2.既存の参照を失う可能性
- (void)setName;(NSString*)name { _name = [name retain]; }
retainを用いることで、参照を保持する形。
しかし、_nameに既に参照が格納されていた場合、それが失われてしまう。
参照が失われると、解放されずメモリリークを招く。
よって、_nameに値を設定する前に、古い参照を解放する仕組みが要る。
3.自身の解放によるカラ参照の可能性
- (void)setName;(NSString*)name { [_name release]; _name = [name retain]; }
releaseにより解放された_nameがnameと同じものであった場合、
_name が release された段階で、nameも解放されてしまっている。
そこに name の retain を呼び出すと、
「解放されたオブジェクトの参照」が発生し、クラッシュする。
4.正解
- (void)setName;(NSString*)name { if ( _name != name ){ [_name release]; _name = [name retain]; } }
まず_name と name が別物であることを確認し、
その後、release と retain を行う。
5.nilチェックは不要 *05「nil」は、Objective-Cで使われる「空オブジェクト」。
「NULL」はC言語の「空ポインタ」なので、厳密には定義が異なる。
使用時は注意。
_name や、渡される引数のnilチェックは不要。
理由は以下。
Objective-Cでは、nilへのメッセージ送信が無視される
このため、_name release は、_name が nil であれば無視される。
また、name retain は、name が nil であれば、
_name に nil がセットされるだけ。
4.2.4. release のパターン
オブジェクト解放について。
要点はひとつ。
release直後にnilを格納しておくこと。
[_name release], _name = nil;
これにより、解放済みオブジェクトへのアクセスを回避できる。
4.2.5. dealloc のパターン
他のオブジェクトをインスタンス変数として持つ場合、
必ず dealloc メソッドを実装すること。
その dealloc メソッド内で、全てのインスタンス変数を release すること。
【例】
- (void)dealloc { // インスタンス変数を解放 [_identifier release], _identifier = nil; [_title release], _title = nil; [_link release], _link = nil; [_itemDescription release], _itemDescription = nil; [_pubDate release], _pubDate = nil; // 親クラスのdeallocを呼び出し [super dealloc]; }
また、Interface Builder でアウトレットとして接続を行うと、
そのオブジェクトはretainされることになるらしい。
これらも忘れずにdeallocでreleaseする様に気をつける。
【おまけ】ページ数は少ないが、大変に重要
この第4章、この本の中で最もページ数が少ない。 *067ページ
しかし、内容的にはとても重要。
最近は、冒頭に述べたガベージコレクションのおかげで、
あまり意識しない事が増えたが、メモリ管理などのリソース管理は大切だ。
アプリが落ちるのはまだ可愛い方で、
最悪の場合は、データ破損にもつながる。 *07さすがにOSのクラッシュまで行くケースは無い…と思いたい。
そちらはOS自体の信頼性の問題にも関わるので。
幸い、要点はシンプルだ。
・使っているか、いないかを確実に把握できる仕組みを作る。
・使い終えたインスタンスは確実に解放する仕組みを作る。
・解放したオブジェクトにアクセスしてしまう可能性を消す。
これをしっかりと肝に銘じておくようにしたい。
脚注
↩01 | 不要になったメモリを自動で解放する仕組み。 お仕事していて、机の上が散らかったら、自動で片付けてくれるメイドさんとでも思って下さい。 |
---|---|
↩02 | 片付けずに次の事を始めすぎて、机の上がいっぱいになったと考えて下さい。 |
↩03 | すでに片付けてしまった道具に手をのばして、突き指するようなもの。 |
↩04 | 自分の持つ知識で言うと「オブジェクト」と「インスタンス」は、ほぼ同義なのだが、どうも違うようだ。 この点については、今後の章で明らかになって行くと考えられるので、注意して読み解いていきたい。 あるいは、Objective-C独自の定義が存在する可能性もあるので、勉強しよう。 |
↩05 | 「nil」は、Objective-Cで使われる「空オブジェクト」。 「NULL」はC言語の「空ポインタ」なので、厳密には定義が異なる。 使用時は注意。 |
↩06 | 7ページ |
↩07 | さすがにOSのクラッシュまで行くケースは無い…と思いたい。 そちらはOS自体の信頼性の問題にも関わるので。 |