トップ «前の日記(2011-05-31) 最新 編集

じじぃの日記、ツッコミ可

Twitter: @jijixi_org
Xbox Live: jijixi

初心者が書いた OCaml 入門
Spotlight tips サイト内リンク集
1970|01|02|
2003|10|11|12|
2004|01|02|03|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|11|
2011|05|
2012|01|

2012-01-25 [長年日記]

% [objc] ARC の最適化バグらしきものを踏んだ件

-O0だと問題無いのに最適化がオンだと参照カウントがおかしくなるケースがあるようだ。 iOSシミュレータでも実機でも同じように落ちる。Mac OS X だとどうなるかは未確認。 調べるのめんどくさいから既知の問題なのかは知らない。 その辺詳しい人で、もし未知のバグだよってことなら報告しておいてくれると嬉しいかも。 どこに報告すりゃいいのかわからんし。

環境はXcode 4.2.1 + iOS SDK 5で、再現コードは以下のような感じ。

+ (NSArray *)arrayForTestWithObject:(id)element {
    NSArray *aResult = nil;
    @autoreleasepool {
        NSArray *a = [NSArray arrayWithObjects:element, nil];
        if (rand() > 0) { // (1)
            aResult = [a mutableCopy];
        }
    }
    return aResult;
}

- (NSArray *)arrayForTest {
    return [[self class] arrayForTestWithObject:@"hoge"]; // (2)
}

- (void)testARC {
    NSArray *a = [self arrayForTest];
    NSLog(@"%@", a);
}

適当なところにこれをコピペして最適化オンの状態で (Releaseビルドにすると手っ取り早い) testARC メソッドを呼ぶと (2) のところで落ちる。 シミュレータで Enable Zombie Objects を ON にしていれば、

*** -[__NSArrayM retain]: message sent to deallocated instance 0x6b2c060

ログにこんなのが出力されるはず。

arrayForTestWithObject: 内で aResult の参照数の調整がおかしなことになっている模様。 (1)のところが肝らしく、例えばここの条件を YES (常に真) にしたりすると落ちなくなる。 if を取っ払っても同じように落ちない。 さらに、@autoreleasepool ブロックを取っ払えば問題は起きないので、@autoreleasepool の中で条件によって変数にアサインしたりしなかったりってことがあると危険ぽい。

そんなこんなで半日潰れた今日であった。

ちなみに、上記の例で言うと変数用意しておいてどうのなんてことはせずに、いきなりreturnしてしまえば問題なかったりする。

+ (NSArray *)arrayForTestWithObject:(id)element {
    @autoreleasepool {
        NSArray *a = [NSArray arrayWithObjects:element, nil];
        if (rand() > 0) {
            return [a mutableCopy]; // (3)
        }
    }
    return nil;
}

(3)の部分、@autoreleasepool だとこう書けるので楽だし、上記の問題のことを考えればむしろこう書いた方がいいんだろうね。 まあ、今回は元々自分でNSAutoreleasePoolを使ってたコードをコンバートしたものだったから、こういう事態になったんだけど。

(追記 2012/3/30)

Xcode 4.3.2 で追試したところ上記 (3) の例でもダメなのを確認。 当時勘違いしていたのか、その後状況が変わったのかは不明だが、現状では条件によってnilになり得る値を返したい時は @autoreleasepool ブロックを避けた方が良さそう。 ほんとにどうしても @autoreleasepool が使えないと困るって場合には以下のようなことをすれば一応回避できるっぽい。 いかにも冗長でしかもアホっぽいけど。

+ (NSArray *)arrayForTestWithObject:(id)element { // (4)
    NSArray *aResult = nil;
    @autoreleasepool {
        NSArray *a = [NSArray arrayWithObjects:element, nil];
        if (rand() > 0) {
            aResult = [a mutableCopy];
        }
    }
    return ([aResult isKindOfClass:[NSArray class]] ? aResult : nil); // (5)
}

不思議なことに、(5) の条件をただの nil チェックだけにするとダメ。とにかく何かメソッド呼んでればかまわないのかもしれないので、もう少しアホっぽく見えないダミー記述を考えた方がいいかもしれない。

NSLog(@"%@", aResult);
return aResult;

なんてのでも良かった。ひどいなw

(/追記)

(追記 2013/1/18)

Xcode 4.5.2 + iOS SDK 6.0 では直ってる模様。

(/追記)

お名前:
E-mail:
コメント:

トップ «前の日記(2011-05-31) 最新 編集

日記ってのは本来、自分で読み返すためにあるもんだよなあ……
もしくは有名人になったら死後に本になったりとかか?

RSS はこちら

jijixi at azito.com