iPhoneSDK + ObjectiveCにおけるメモリ管理のはまり所

まずは公式のObjectiveCのメモリ管理を読むべきである。(読まないと始まらない)
http://developer.apple.com/jp/documentation/cocoa/Conceptual/ObjectiveC/4objc_runtime_overview/chapter_8_section_2.html


そして、この辺が具体的なサンプルコードもあってよくまとまってる

http://wwwa.dcns.ne.jp/~nito/CocoaClub/article01.html
http://wwwa.dcns.ne.jp/~nito/CocoaClub/article02.html
http://wwwa.dcns.ne.jp/~nito/CocoaClub/article03.html
http://wwwa.dcns.ne.jp/~nito/CocoaClub/article04.html


さて、そしたら本題であるが、UIImageViewにUIImageをセットした場合、UIImageの参照カウント(retainCount)が増える。

UIImage* image1 = [[UIImage alloc] initWithContentsOfFile:imgPath];
NSLog( @"A cnt %d", image1.retainCount );
[imageViews1 setImage:image1];
NSLog( @"B cnt %d", image1.retainCount );
[image1 release];
NSLog( @"C cnt %d", image1.retainCount );
[image1 release];
NSLog( @"D cnt %d", image1.retainCount );

==========出力結果========================
A cnt 1
B cnt 2
C cnt 1
Dで解放後のオブジェクトにアクセスしたとして止まる

UIImageViewにsetImageでセットするとその際にretainが呼ばれるようである。さらに古いイメージ(先にセットされてたイメージ)はreleaseされるようだ。


最初、アップルのサンプルを読んだ際に、setImageとかAddSubViewした後に、即座に作ったオブジェクトをreleaseしてるのを見てなんでこんな書き方するんだろうと思ってたけど、要はセット先で後は管理してくるって事らしい。


UIImageViewの定義を追ってみると、imageプロパティにはretainとついている。これが、ここにセットしたオブジェクトはこっちで管理するよって合図になっている。

@property(nonatomic,retain) UIImage *image;

他のNSObjectから派生されたオブジェクトも基本的にこういう動作するものと考えた方がいい。(setしたりAddしたりする場合は、大概こういう挙動をする)
【コラム】ダイナミックObjective-C (104) プロパティ(4) - プロパティの属性 | エンタープライズ | マイナビニュース


また、さらに言うと↓のように[.]はsetterに変換されるので、僕みたいなC言語脳で、単に構造体のメンバ変数にポインタ代入しただけなんだって思ってると痛い目をみる

imageViews1.image = image1;
     ↓これは実質こう
[imageViews1 setImage:image1];
     ↓さらにsetImageの内部では・・・
[image release];  //古いイメージがあるならリリース
image = image1;
[image retain];   //渡されたオブジェクトの参照カウンタ増やす(所有権持つ)

この辺、把握できると便利なんだが、ドキュメント一読しただけでは分かりずらい気がするので、はまりポイントとして備忘録がてらメモっとく。

UIImage* img1 = [[UIImage alloc] initWithContentsOfFile:imgPath1];
UIImage* img2 = [[UIImage alloc] initWithContentsOfFile:imgPath2];
NSLog( @"A cnt %d %d\n", img1.retainCount, img2.retainCount );
imageView1.image = img1;    //img1はここでretainされる
NSLog( @"B cnt %d %d\n", img1.retainCount, img2.retainCount );
imageView1.image = img2;    //im1はrelease img2はretainされる
NSLog( @"C cnt %d %d\n", img1.retainCount, img2.retainCount );
imageView1.image = nil;     //img2がreleaseされる
NSLog( @"D cnt %d %d\n", img1.retainCount, img2.retainCount );
[img1 release];  //ここでretainCount 0となり解放(dealloc)される
[img2 release];  //ここでretainCount 0となり解放(dealloc)される

==========出力結果========================
A cnt 1 1
B cnt 2 1  ←セットしたimg1がretainされてる
C cnt 1 2  ←img1はreleaseされ、セットしたimg2がretainされる
D cnt 1 1  ←img2がrelease、両方ともセットする前に戻る

セットしたオブジェクトの参照を解きたければ、nilをセットするとよい。(セットした先のクラスがreleaseの面倒までみてくれる)