hishidaの開発blog

EBシリーズ(EBPocket,EBWin,EBMac,EBStudio),KWIC Finder,xdoc2txt,読書尚友の開発者ブログ

C配列にObjective-Cのオブジェクトを保存するのは危険?

やっと安定したと思っていたEBPocket for iOSだが、複合検索で外字を選択すると異常終了するという報告をいただいた。
http://ebstudio.info/wforum_ebppc/hatenacamera.cgi?mode=allread&no=2686&page=0
エミュレータで調べたところ、32bit OSでは問題が起きず、64bit OSだけ異常終了するらしい。NSStringの文字列がいつの間にかautoreleaseされて不正参照になっていることまでわかったが、原因がわかるまでしばらく時間がかかった。
結論的には、C配列にNSString*のオブジェクト参照を入れていたためだった。

問題の個所はこんな感じで、NSString*の配列m_keywordをクラス変数として静的に確保していた。

#define	MAX_CPLX_GRP	10
@interface ComplexSearchViewController : UIViewController 
{
	//	省略

	NSString*m_keyword[MAX_CPLX_GRP];
}

クラスオブジェクトの生存中は、クラス変数のm_keywordに代入した文字列も生存するものだとなんとなく思いこんでいた。
(Objective-Cのメモリ管理の基本であるalloc/init/retain/releaseなどについては、一応理解しています)
だがよく考えてみれば、m_keyword
は単なるC配列なので、代入してもNSStringの参照カウントは増えない。これではどこかのタイミング(関数の出口など)でautoreleaseされるのは当然だ。
そこで次のようにC配列をやめてNSMutableArrayにしてみた。NSMutableArrayなら代入すれば参照カウントがインクリメントされてautoreleaseされなくなるのではないか。
NSMutableArrayでは C配列と同様に 変数名[添字] の形で代入や参照が書けるので、ソースの修正は最小限にできる(本当はreplaceObjectAtIndex:withObject:みたいな長ったらしい名前のメソッドがある。[]はいわゆるシンタックスシュガー)。

@interface ComplexSearchViewController : UIViewController 
{
	//	省略

	NSMutableArray*m_keyword;
}

- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
        // Custom initialization
        m_keyword = [[NSMutableArray alloc]initWithCapacity:MAX_CPLX_GRP];
    }
    return self;
}

- (void)dealloc {
	//	省略
    [m_keyword release];
	
    [super dealloc];
}

案の定、これで落ちなくなった。万歳。
Objective-Cはメモリ管理に気を使う。
今はiOS向けの新規案件は、より進化した言語であるSwiftの利用が増えているのではないだろうか。私もこれから何かiOS向けに書くとすれば、Swiftを選ぶ。読書尚友のiOS版をSwiftで作ってみてもいいかもしれない。