ページ

2012年9月6日木曜日

mac:Objective-C @autoreleasepool と仲良くなる?

@autoreleasepool という難敵がいる。
どうも、関数、メソッドの戻り値と関連が深そうなのだが、 いまいち良く分からない。

xcode4 を使い始めて ARC になってから、更に良く分からなくなった。 

以下は、NSString にカテゴリを追加して、半角文字を1、全角文字を2のサイズとして、指定サイズ分の長さに調節してくれるメソッド。


@interface NSString(substringToHansize)
- (NSString *)substringToHansize:(NSInteger)size;
@end

@implementation NSString(substringToHansize)
- (NSString *)substringToHansize:(NSInteger)size
{
    NSString *ret;
    @autoreleasepool { // <= ※1
        int i = 0, length = 0;
        do {
            unichar uc = [self characterAtIndex:i];
            if (!((uc >= 0x20 && uc <= 0x7e) || (uc >= 0xa1 && uc <= 0xdf) || (uc >= 0xff61 && uc <= 0xff9f)))
                length++;
            if (++length > size)
                break;
        } while (++i < [self length]);
        ret = [self substringToIndex:i]; // <= ※2
    }  // <= ※1
    return ret;
}
@end


※1の部分で @autoreleasepool を使っているが、これをコメント等で外すと、

objc[3419]: Object 0x100114ed0 of class __NSCFString autoreleased with no pool in place - just leaking - break on objc_autoreleaseNoPool() to debug

のようなメッセージが出てくる。
どうやら、autoreleasepool が無いので、デバッグ用ので代用したっぽいのだが、一応デバッグモードで動作はする。

どこで @autoreleasepool を使っているかというと、※2の部分である。
NSString substringToIndex: を呼んでいるが、この戻り値(オブジェクト)の橋渡しとして @autoreleasepool が使われている。

というか、基本的に オブジェクトを戻り値にするメソッドは コンパイラが "可能" と判断しない限り、@autoreleasepool 経由になる。
もしくは、@autoreleasepool 経由だが、戻り値を呼び出し元に引き渡す際に @autoreleasepool からオーナーシップを排除するようだ。

例外として、メソッドの頭に init / copy 等が付いていたり、 NS_RETURNS_RETAINED の属性を付加していたりした場合、オーナーシップを引き渡すこと前提に動作するため、 @autoreleasepool は使用されないようだ。

NSString や NSArray のような リテラル表記出来る場合、(例えば return @"ABC"; のような書き方)データ領域をそのまま参照するため、インスタンスの消滅等の輪廻から外れているみたいだ。
また、それらのリテラル表記を使用して初期化したオブジェクト等も、コンパイラによって最終的なデータを作成されている場合もあり、これまた同じ結果になる。
(return [NSString stringWithString:@"ABC"]; 等)

なので、上記例のように、メソッド開始時に return値 の strong 変数を用意した後に @autoreleasepool を開始、return の直前に @autoreleasepool を終了させると、メソッド(関数)内で使われた @autoreleasepool が綺麗になるので、一番良いような気がする。

普通にアプリを作ると、 main関数 の始まりと終わりで @autoreleasepool があるので、これだけでイイやと思っていると、処理内で @autoreleasepool に大量にオーナーシップが溜まるので、こまめに開放処理するか、NSRunLoop等に処理を渡すかした方が良い。
(NSRunLoop に処理を渡すと、@autoreleasepool は消えてくれる)

ARC で面倒臭さで今のところNo.1な @autoreleasepool だが、果たして仲良くなれるだろうか・・・。