7

私は完全に困惑する問題にぶつかりました。コードサンプルで説明します:

@interface Crasher ()
@property (nonatomic, strong) NSArray *array;
@end

@implementation Crasher

- (void)crash;
{
  NSMutableArray *mutable = [NSMutableArray array];
  NSArray *items = @[@0, @1, @2, @3];

  if ([@YES boolValue])
  {
    [items enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
      [mutable addObject:obj];
    }];
  }
  else
  {
    [items enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
      [mutable addObject:obj];
    }];
  }

  [self setArray:mutable];
}

@end

上記のコード[self setArray:mutable]は、ARC が有効になっており、デバイス上で実行されると、回線上でクラッシュします。コードがシミュレーターでクラッシュしたり、ARC が無効になっているデバイスでクラッシュしたりすることはありません。UsingNSZombieEnabledは、セッターが既に割り当て解除された配列を保持しようとしていることを示します。

2 番目の呼び出しがコメント アウトされていてもクラッシュしません[mutable addObject:obj](ただし、このコードが最初に実行されることはありません)。

このクラッシュを示すプロジェクトを Github にアップロードしましたaidansteele/arc-crash。Xcode 4.5.2 を使用しています。Xcode 4.6 では発生しないようですが、まだ開発者プレビュー段階です。私は何を間違っていますか?


この回答に対処して(質問でもう少しスペースを空けてください)、-[NSArray enumerateObjectsUsingBlock:]そのメソッド呼び出しを次の呼び出しを使用するように変更しても問題が解決しないため、問題は内部にあるとは思いません-[NSArray(Functional) each:]

@interface NSArray (Functional)
- (void)each:(void (^)(id obj))action;
@end

@implementation NSArray (Functional)

- (void)each:(void (^)(id))action;
{
  for (NSUInteger idx = 0; idx < [self count]; idx++)
  {
    action([self objectAtIndex:idx]);
  }
}

@end
4

3 に答える 3

3

この問題はデバイス (ARM コード) とリリース ビルド (最適化されたコード) でのみ発生するため、ARC とブロックと自動解放に関する Clang コンパイラのオプティマイザにバグが見つかったのではないかと疑っています。サンプル プロジェクトを添付して、Radar でバグを報告します。

enumerateObjectsUsingBlock を

for (id n in items)
{
   [mutable addObject:n];
}

あなたのクラッシュはなくなります。

問題を修正するコードへのその他の変更:

交換:

[NSMutableArray array];

[NSMutableArray new];

また

[[NSMutableArray alloc] init];

また、余談ですが、NSArray プロパティに NSMutableArray を格納するべきではありません。プロパティに割り当てる前に、NSMutableArray を NSArray に変換する必要があります。例えば:

self.array = [NSArray arrayWithArray:mutable];

これはクラッシュを修正しないことに注意してください。それはちょうど良いコードです。

お役に立てれば。

于 2012-12-19T06:58:13.103 に答える
2

答えは、自動解放される変数と、この自動解放された変数を使用するブロックにあると思います。

__autoreleasing オブジェクトの保存期間に関する Clang ドキュメントから:

非自動保存期間の __autoreleasing オブジェクトを宣言する場合、プログラムは不正な形式です。プログラムが __autoreleasing オブジェクトをブロック内、または参照による場合を除き C++11 ラムダ内でキャプチャする場合、そのプログラムは不適切な形式です。

では、これが問題であるかどうかをテストする方法は?

最初に、可変配列をキャプチャするブロックが本当に問題の原因であるかどうかを確認します。最初のブロック (呼び出される唯一のブロック) で可変配列の使用をコメントアウトし、代わりに列挙によって見つかった値に NSLog を使用します。

[items enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        //[mutable addObject:obj];
        NSLog(@"item is %@",obj);
    }];

これでクラッシュが修正されます。(突然変異が問題にならないようにするために) 突然変異を起こさない方法で可変配列を参照するとどうなるでしょうか?

[items enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        //[mutable addObject:obj];
        NSLog(@"Mutable array is %@",mutable);
    }];

それでもクラッシュするため、ブロック内の自動解放された可変配列を参照するだけで問題が発生していることがわかります。余談ですが、すべての値を保持するために正しいサイズの arrayWithCapacity を使用すると、クラッシュも発生します。

では、問題が自動解放されたオブジェクトをキャプチャするブロックである場合、この問題をどのように修正できますか?

ARC が変数を解放しなければならないように、代わりに変数を strong にすることができます。

   NSMutableArray *mutable = [NSMutableArray array];

これによりクラッシュが修正され、ARC はメソッドが終了したときに変数を適切に解放します。

ただし、それが完全な話であるかどうかは完全にはわかりません。この単純なブロックをそのメソッドのどこかに導入するだけでも、クラッシュが修正されます。

  void (^useMute)();

    useMute = ^() {
        NSLog(@"Mutable is %@", mutable);
    };

使用されていない場合でも、変更可能な配列が保持され、早期リリースも妨げられます。したがって、真のエラーは enumerateUsingBlock と自動解放プールの相互作用のどこかにあるようです。

補足として、問題を解決するのは、ブロック列挙の代わりに通常の列挙を使用することです。

   for (id obj in items )
      {
          [mutable addObject:obj];
      }

より手の込んだ方法を使用する大きな理由がない限り、より単純なメカニズムを使用して物事を行う方が良い場合があります。単純な同期コード実行を目的とした配列要素のループの場合、ブロックが渡す他のパラメーターにアクセスする必要がないのに、なぜブロックを使用するのでしょうか? C コンストラクトの continue と stop を使用すると、列挙を完全に停止することしかできないブロック ループよりもさらに細かく制御できます。

于 2012-12-19T07:10:09.517 に答える
0

I think there's an issue in the enumerateObjectsUsingBlock method for your version of SDK. Maybe you should read the release notes of the new SDK or known issues of the old one to find out more. So, what happens is, your retain count is just fine up to the point when you call enumerateObjectsUsingBlock. After that method exits, your pointer points to some garbage.

One way to fix it would be to take responsibility for your collection and promise you will not dispose of it before enumerateObjectsUsingBlock exits. Although a fix, it doesn't tackle the core problem, which as I said above, I think it resides in enumerateObjectsUsingBlock. Here is a code that works(around).

#import "Crasher.h"

@interface Crasher ()
@property (nonatomic, strong) NSArray *array;
@end

@implementation Crasher

- (void)crash;
{
  __block NSMutableArray *mutable = [NSMutableArray array];
  NSArray *items = @[@0, @1, @2, @3];

  if ([@YES boolValue])
  {
    [items enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
      [mutable addObject:obj];
    }];
  }
  else
  {
    [items enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
      [mutable addObject:obj];
    }];
  }

    NSLog(@"%@", mutable);

  [self setArray:mutable];
}

@end
于 2012-12-19T00:46:43.957 に答える