2012年10月21日日曜日

メモリ管理について個人的なまとめ1

Objective-C では「参照カウンタ」という方式でメモリ管理をしています。こういった直にメモりを弄るのは個人的にはじめてのことだったので個人的にまとめてみます。

とりあえず公式ドキュメント

メモリ管理に関しての公式ドキュメントは以下になります。

高度なメモリ管理プログラミングガイド - Apple Developer

大概のことはこのドキュメントで事足りてしまいます。Appleの公式ドキュメントは質が高いですね。

参照カウンタ方式について

上でも説明したようにObjective-C では「参照カウンタ」という方式でメモリ管理を行っています。以下簡単な説明。

参照カウンタ方式とは、生成したインスタンスが他のインスタンスからどれだけ参照されているかを参照カウントと呼ばれる整数値を付加し、生成したインスタンスの生存期間として用いる。インスタンスへの参照が変化するたびにこの値は随時書き換わり、その参照カウントが0になったものについてはインスタンスの破棄が許される。

参考 : 参照カウント - Wikipedia

参考記事をObjective-C的にまとめると

インスタンスを生成したら参照カウンタは1にセットされ、以後この生成したインスタンスが参照される度に参照カウンタがインクリメント(+1)され、生成したインスタンスを解放する際はreleaseメソッドを呼び出して参照カウンタをデクリメント(-1)します。その参照カウントが0になったときにdeallocメソッドが呼び出され、インスタンスはメモリから破棄されます。

解放破棄の区別がはっきりしていないので自分の解釈で書いています。今のところ「解放 = releaseメソッドを呼んで参照カウントをデクリメント」、「破棄 = deallocメソッドが呼ばれてインスタンスの参照がメモリから完全に消える」というように解釈しています。この辺曖昧です。

この参照カウンタ方式を怠り、確保したメモリを適切に解放しないとメモリリークを起こしてアプリが強制終了してしまいます。アプリが落ちる原因は以外とこのメモリ周りだったりします。自分も人のこと言える立場ではないのですが・・・(笑)

メモリ管理方法のルール

Cocoaプログラミングでのメモリ管理ではいくらかのポリシーが定められています。

  1. 自分が作成したオブジェクトはすべて自分が所有する
  2. retainメソッドでオブジェクトの所有権を獲得できる
  3. 所有するオブジェクトが不要になったら、その所有権を放棄する
  4. 自分が所有していないオブジェクトの所有権を放棄することはできない

参考 : 高度なメモリ管理プログラミングガイド - Apple Developer

自分で作成したインスタンスは自分が所有権を持ち、その所有権を獲得するにはretainメソッドを実行。インスタンスの放棄(releseメッセージを送る)は責任を持って所有者自ら行う。所有していないインスタンスについてはreleseメッセージを送ってはならない。とこんな感じ。

次は上に出ている所有権(オーナーシップ)に関して軽く触れます。

所有権(オーナーシップ)

公式ドキュメントで「メモリ管理モデルは、オブジェクトの所有という概念に基づいています。」と記述されており、これが「メモリオーナーシップ」と呼ばれていたりいなかったり。このオーナーシップは参照カウンタ方式を非常に大きな繋がりがあります。

インスタンスを生成する際に「alloc」、「new」、「copy」または「mutableCopy」したら、オーナーシップを持ちます。newメソッドはallocメソッドとinitメソッド両方を一度に実行したことと同じ働きをします。

「alloc」、「new」、「copy」または「mutableCopy」でインスタンスを生成した場合は生成時に参照カウントの状態が1になります。メモリの解放は所有者が責任を持って行います。逆に上の4つのメソッドを使わずにインスタンスを生成した場合はオーナーシップを持ちません。releseを実行しようとするとエラーになります。

参考 : 怠け者の為のObjective-Cのメモリ管理 - Nacho4d - programming notes

簡単な例

これまでのを踏まえて簡単な例を。

例1

オーナーシップについて。
NSData *date1 = [[NSData alloc] init];  //(1)
[date1 release];  //(2)
NSData *date2 = [NSData date];  //(3)
//[date2 release];  //(4)

NSString *str = [NSString stringWithFormat:@"%@",@"Objective-C"];  //(5)
//[str release];  //(6)

(1)では、alloc / initを実行してインスタンスを生成。allocしているためオーナーシップを持ち、この時点では参照カウントは1。

(2)では、(1)でallocしているのでオーナーシップを持っているので実行可能。参照カウントは-1されます。

(3)では、alloc、new、copyまたはmutableCopyを使っていないためオーナーシップは持っていません。(4)でreleseを実行するとエラー。

(5)では、これもオーナーシップは持っていません。(6)を実行するとエラー。

少し脱線しますが、(5)の生成方法((3)も同様)は「コンビニエンスコンストラクタ」というもので、クラスの一時的なインスタンスを生成して初期化したものを返します。これは自動的にautoreleaseされるインスタンスを返すためreleseは不要です。コンビニエンスコンストラクタについてはまたの機会に詳しく触れます。

例2

alloc/initについて。
NSData *data1 = [NSData alloc];
NSLog(@"data1 -> %d", [data1 retainCount]);  // 参照カウント1
data1 = [data1 init];
NSLog(@"data1 -> %d", [data1 retainCount]);  // 参照カウント1
allocとinitを分けて実行しています。allocのときだけ参照カウンタが上がっているのが分かります。

例3

copyについて。
NSData *data2 = [[NSData alloc]init];
NSData *data3 = [data2 copy];
    
NSLog(@"data2 -> %d", [data2 retainCount]);  //retainCountは、
NSLog(@"data3 -> %d", [data3 retainCount]);  //ともに2
    
NSLog(@"data2 -> %p", data2);  // 同一アドレス、
NSLog(@"data3 -> %p", data3);  // 0x6e82ef0出力
data3にdata2をcopyしています。data3、data2ともにretainCountは2、アドレスは同一のものが参照されているためcopyされているのが分かります。

例3

NSArrayについて。
NSArray *arr = [NSMutableArray alloc];
NSLog(@"arr -> %d", [arr retainCount]);  // 参照カウント-1
arr = [arr init];
NSLog(@"arr -> %d", [arr retainCount]);  // 参照カウント1
NSArray(またはNSDictionary などのコンテナ系クラスや NSValue など)はallocで参照カウンタに-1が入ります。initしてはじめて1が入ります。この仕様に関してこちらの記事の説明を抜粋します。

これらのクラスは alloc メソッドを再定義している可能性が高いです。alloc メソッドを再定義しない限り alloc で参照カウンタが1になると覚えておきましょう。
推測ですが Apple が提供しているライブラリは alloc したあと必ず init メソッドで初期化しないとプログラムが落ちる仕様になっているのだと思います。

参考 : iPhoneアプリ開発時のメモリ管理で気をつけること - A Day In The Life

原因は分からないのですが、NSArray *arr = [NSMutableArray alloc];とやっているところを、NSArray *arr = [NSArray alloc];というようにallocでNSArray型のメッセージを送った際に参照カウンタに16が入りました。原因は今のところ分からないです。

例4

NSMutableArrayについて。
NSObject *obj = [[NSObject alloc] init];
NSMutableArray *marr = [[NSMutableArray alloc] initWithObjects:obj, nil];
NSLog(@"marr -> %d", [marr retainCount]);  // 参照カウント1
NSLog(@"obj -> %d", [obj retainCount]);  // 参照カウント2
objの生成で参照カウント1、marrの生成で参照カウント1、またmarrの生成時にinitWithObjects:メソッドでobjの参照カウントに+1され合計2。

参照カウンタを操作するメソッド

インスタンスの生成と解放を操作するメソッドは以下のメソッド。参考までに。

メソッド名 説明
alloc 参照カウンタを1にする(インスタンスを作成するために呼ぶ)
new 参照カウンタを1にする(インスタンスを作成するために呼ぶ)
copy 参照カウンタを1にする(インスタンスの複製するときに呼ぶ)
mutableCopy 参照カウンタを1にする(インスタンスの複製するときに呼ぶ)
retain 参照カウンタ+1する
release 参照カウンタ-1する
autorelease 参照カウンタを後で-1する

参考 : 詳解 Objective-C 2.0 第3版

おわり

長くなりそうなので今日はこの辺で。

参考記事

高度なメモリ管理プログラミングガイド - Apple Developer
メモリ管理 - こたつつきみかん
Objective-Cにおけるメモリ管理についてのまとめ - らいふログ
怠け者の為のObjective-Cのメモリ管理 - Nacho4d - programming notes

0 件のコメント:

コメントを投稿