トップ 追記

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

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 では直ってる模様。

(/追記)


2011-05-26 [長年日記]

% [objc] こっそり忍び寄る未初期化変数の罠

以前 iOS アプリの開発でこんなことを書いてハマったことがある。

return self.someView.frame.size.height;

someView プロパティ (UIView のサブクラスの何か) には何も入っていない場合があるんだが、その場合、最初は 0 を返すもののしばらくすると変な値を返し始める。 デバッグ環境だとなかなかその現象が出ないので原因の特定に難儀したんだが、わかってしまえば「ああそうか、なんで気づかなかったんだ」という話。

Objective-C では nil に対するメッセージ (メソッド呼び出し) は何も起こらず nil が返ることになっているので、特にエラーを出す必要が無い場合にはつい nil かどうかのチェックを省略してしまうんだが、上記のような場合にはそれが仇になる。

UIView の frame プロパティの型は CGRect で、これはオブジェクトではなく構造体である。 構造体に関しては C そのものと考えていい。 つまり上記のようなコードは実際には以下のような処理の流れになっている (と思われる)。

UIView *aView = [self someView];
CGRect frame = [aView frame];
return frame.size.height;

aView が nil の場合、frame には nil が代入されるのだろう。 そうだとすれば当然それは CGRect の初期化としては不十分で、その結果、ある程度メモリ領域が汚れてくるとゴミ値が入ってくるので return される値がおかしくなる。 C の経験がある程度あれば当たり前の挙動ではあるが、そうでない場合にはさっぱり意味がわからないかもしれない。 やっぱり Objective-C を使う人は C も必修にした方がいいんじゃないか。


2011-05-14 [長年日記]

% [game] Condemned 2 City Museum 捜査攻略

ここだけゴールドが取れてなかったが、ようやく何とかなったのでメモ。

  • 3 Did you find any fingerprints on the paper?
  • 1 Do you think Vanhorn nursed his nephew back to health?
  • 2 What about the saw? You said it looked medieval?
  • Camera ID 1501
  • Medieval Tools 13A (カメラの映像を確認した後に展示室の階段付近に案内看板があるのでそれを調べる)
  • 1 Any evidence the metal pieces came from Rachael Mars's body?

頭の数字は選択肢の位置。 13A の看板の存在に気付かなかったせいで今まで Perfect が取れてなかった。 位置的に見逃しやすいし、無視しても話が進むのが結構罠。

ちなみに警備員を殺さないようにするミッションは、自分が殺さなければいいだけなので邪魔な場合は泥棒たちに始末してもらうと良い。


トップ 追記

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

RSS はこちら

jijixi at azito.com