web-dev-qa-db-ja.com

enumerateObjectsUsingBlockとforを使用する場合

明らかな違いに加えて:

  • インデックスとオブジェクトの両方が必要な場合はenumerateObjectsUsingBlockを使用します
  • ローカル変数を変更する必要がある場合は、enumerateObjectsUsingBlockを使用しないでください (私はこれについて間違っていました、bbumの答えを参照してください)

for (id obj in myArray)も機能する場合、enumerateObjectsUsingBlockは一般的に良くも悪くも考えられますか?長所/短所は何ですか(たとえば、パフォーマンスは多少異なりますか)。

150
Paul Wheeler

最終的には、使用したいパターンを使用し、コンテキストでより自然になります。

for(... in ...)は非常に便利で構文的に簡潔ですが、enumerateObjectsUsingBlock:には興味深いものであるかどうかわからない機能がいくつかあります。

  • enumerateObjectsUsingBlock:は、高速列挙より高速または高速になります(for(... in ...)は、NSFastEnumerationサポートを使用して列挙を実装します)。高速列挙には、内部表現から高速列挙の表現への変換が必要です。そこにはオーバーヘッドがあります。ブロックベースの列挙により、コレクションクラスは、ネイティブストレージ形式の最速走査と同じくらい迅速にコンテンツを列挙できます。配列には関係ないようですが、辞書にとっては大きな違いになります。

  • 「ローカル変数を変更する必要がある場合、enumerateObjectsUsingBlockを使用しないでください」-trueではありません。ローカルを__blockとして宣言すると、ブロック内で書き込み可能になります。

  • enumerateObjectsWithOptions:usingBlock:は、同時列挙または逆列挙をサポートします。

  • 辞書では、ブロックベースの列挙がキーと値を同時に取得する唯一の方法です。

個人的に、enumerateObjectsUsingBlock:for (... in ...)よりも頻繁に使用しますが、ここでも-個人的な選択です。

346
bbum

単純な列挙の場合、単純な高速列挙(つまり、for…in…ループ)を使用するのがより慣用的なオプションです。ブロック方式はやや速いかもしれませんが、ほとんどの場合それはそれほど重要ではありません。CPUにバインドされているプログラムはほとんどなく、その場合でも内部の計算ではなくループ自体がボトルネックになることはまれです。

単純なループもより明確に読み取ります。 2つのバージョンの定型文は次のとおりです。

for (id x in y){
}

[y enumerateObjectsUsingBlock:^(id x, NSUInteger index, BOOL *stop){
}];

インデックスを追跡するために変数を追加しても、単純なループは読みやすくなります。

enumerateObjectsUsingBlock:を使用する必要がある場合ブロックを保存して、後で実行したり、複数の場所で実行したりする場合。これは、ループ本体の過剰な置換ではなく、実際にブロックをファーストクラスの関数として実際に使用している場合に適しています。

81
Chuck

この質問は古いですが、状況は変わっていません、受け入れられた答えは間違っています。

enumerateObjectsUsingBlock AP​​Iはfor-inに取って代わるものではありませんでしたが、まったく異なるユースケースのために:

  • 任意の非ローカルロジックを適用できます。つまり、ブロックをアレイで使用するためにブロックが何をするかを知る必要はありません。
  • 大規模なコレクションまたは重い計算の同時列挙(withOptions:パラメーターを使用)

for-inを使用した高速列挙は、コレクションを列挙するidiomaticメソッドのままです。

高速列挙は、コードの簡潔さ、読みやすさ、および 追加の最適化 の恩恵を受けており、不自然に高速になります。古いC forループよりも高速です!

簡単なテストの結果、2014年のiOS 7では、enumerateObjectsUsingBlockは一貫してfor-inよりも700%遅くなります(100項目配列の1mmの繰り返しに基づく)。

ここでパフォーマンスは本当に実用的な懸念事項ですか?

まれな例外を除いて、間違いなくそうです。

重要なのは、enumerateObjectsUsingBlock:for-inよりも使用するメリットがほとんどないことを実証することです。コードを読みやすくしたり、高速化したり、スレッドセーフにしたりすることはありません。 (別の一般的な誤解)。

選択は個人的な好みに帰着します。私にとっては、慣用的で読みやすいオプションが勝ちです。この場合、それはfor-inを使用した高速列挙です。

基準:

NSMutableArray *arr = [NSMutableArray array];
for (int i = 0; i < 100; i++) {
    arr[i] = [NSString stringWithFormat:@"%d", i];
}
int i;
__block NSUInteger length;

i = 1000 * 1000;
uint64_t a1 = mach_absolute_time();
while (--i > 0) {
    for (NSString *s in arr) {
        length = s.length;
    }
}
NSLog(@"For-in %llu", mach_absolute_time()-a1);

i = 1000 * 1000;
uint64_t b1 = mach_absolute_time();
while (--i > 0) {
    [arr enumerateObjectsUsingBlock:^(NSString *s, NSUInteger idx, BOOL *stop) {
        length = s.length;
    }];
}
NSLog(@"Enum %llu", mach_absolute_time()-b1);

結果:

2014-06-11 14:37:47.717 Test[57483:60b] For-in 1087754062
2014-06-11 14:37:55.492 Test[57483:60b] Enum   7775447746
42
Adam Kaplan

パフォーマンスに関する質問に答えるために、 パフォーマンステストプロジェクト を使用していくつかのテストを行いました。配列内のすべてのオブジェクトにメッセージを送信するための3つのオプションのうち、どれが最も速いかを知りたかったのです。

オプションは次のとおりです。

1)makeObjectsPerformSelector

[arr makeObjectsPerformSelector:@selector(_stubMethod)];

2)高速列挙と通常のメッセージ送信

for (id item in arr)
{
    [item _stubMethod];
}

3)enumerateObjectsUsingBlockおよび通常のメッセージ送信

[arr enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) 
 {
     [obj _stubMethod];
 }];

MakeObjectsPerformSelectorが最も遅いことが判明しました。高速列挙の2倍の時間がかかりました。また、enumerateObjectsUsingBlockは最速で、高速反復よりも約15〜20%高速でした。

そのため、最高のパフォーマンスを非常に心配している場合は、enumerateObjectsUsingBlockを使用します。ただし、場合によっては、コレクションを列挙するのにかかる時間は、各オブジェクトに実行させたいコードを実行するのにかかる時間よりも短いことに注意してください。

23
LearnCocos2D

ネストされたループを解除する場合は、enumerateObjectsUsingBlockを外部ループとして使用すると非常に便利です。

例えば.

[array1 enumerateObjectsUsingBlock:^(id obj1, NSUInteger idx, BOOL * _Nonnull stop) {
  for(id obj2 in array2) {
    for(id obj3 in array3) {
      if(condition) {
        // break ALL the loops!
        *stop = YES;
        return;
      }
    }
  }
}];

別の方法は、gotoステートメントを使用することです。

3
Gabe

パフォーマンスの包括的な比較を開始してくれた@bbumと@Chuckに感謝します。些細なことだと知ってうれしい。私は行ったようです:

  • for (... in ...)-デフォルトgotoとして。私にとってより直感的で、実際の好みよりもプログラミング履歴が多い-クロス言語の再利用、IDE auto complete:Pによるほとんどのデータ構造の入力の減少.

  • enumerateObject...-オブジェクトとインデックスへのアクセスが必要な場合。また、非配列または辞書構造にアクセスする場合(個人設定)

  • for (int i=idx; i<count; i++)-配列の場合、ゼロ以外のインデックスで開始する必要がある場合

1
snowbound