iOSのプログラムを作ってて、少しトラブったので調査してみました。NSMutableArrayのオブジェクトを作成して、それをクラス内で共通オブジェクトとして使用する予定で以下のようなコードを書きました。
@interface Test : NSObject
{
NSMutableArray* testArray;
}– (void)addValue:(NSNumber)num;
@end
@implementation Test
– (void)init
{
self = [super init];
if (self) {
testArray = [NSMutableArray array];
}
}– (void)addValue:(NSNumber)num
{
[testArray addObject:num];
}– (void)dealloc
{
[testArray release];
[super dealloc]
}@end
この初期化メソッドをUIViewController派生クラスのloadViewメンバから呼び出したのですが、その後addValueメンバを呼び出した時にNSMutableArrayのaddObjectメンバでエラーになりました。調べてみると、この時点でtestArrayの参照カウンタが0に。これはつまり、オブジェクトが既に回収されてしまっている事を示します。testArrayのreleaseを呼び出していないにも関わらずです。
そこでオブジェクト生成をクラスメソッドのarrayから以下のオブジェクトメソッドに変更してみました所、参照カウンタが0にならずに正常に動作します。
testArray = [[NSMutableArray alloc] init];
さてさて何故でしょう。
調べてみたところ NSArray の array メソッドは内部で autorelease を呼び出すそうです。つまり、[NSMutableArray array]は正確には以下のコードに相当します。
testArray = [[[NSMutableArray alloc] init] autorelease];
そして、このautoreleaseが回収されるのはUIKitのコントロールクラスのイベントメソッドから戻ったタイミングのようです。つまり、loadViewメンバ処理を抜けた段階でautoreleasepoolに登録されたオブジェクトは回収されてしまっていたのでした。原因が分かれば後は修正方法ですが2通りあります。
1. ちゃんとretainする。
testArray = [NSMutableArray array];
[testArray retain];
明示的に参照カウンタをインクリメントするので、メモリ管理が厳密にできます。
2. propertyを使う。
@interface Test : NSObject
{
NSMutableArray* testArray;
}
@property (nonatomic, retain) NSMutableArray* testArray;
@end
そしてオブジェクト生成部分を以下のようにします。
self.testArray = [NSMutableArray array];
このようにretain宣言したプロパティを使えば、プロパティに代入したタイミングで参照カウンタが+1されるので正しく動作します。
しっかし、個人的にはこのautoreleaseは嫌やなぁ。(>_<)
そもそもオブジェクトが生成された階層と別の階層で開放されるのはバグの温床になるし、オブジェクトの寿命管理が厳密にできていないのも気持ち悪い。それに、呼び出し元クラスがautoreleasepoolを使用している事が前提になるから、関数仕様にも冗長な要求が発生するしね。やはり alloc+init+release で厳密にオブジェクトの寿命管理をする方が性にあってるようです。