enumerateObjectsUsingBlockとforを使用する場合


150

明らかな違いに加えて:

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

enumerateObjectsUsingBlock一般に、うまくいく場合は、より良いか悪いかと考えられますかfor (id obj in myArray)?長所/短所は何ですか(たとえば、多かれ少なかれパフォーマンスが良いなど)?


1
現在のインデックスが必要な場合に使用します。
Besi

回答:


350

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

一方でfor(... in ...)、非常に便利で、文法的に簡単である、enumerateObjectsUsingBlock:あるいは面白いことを証明しない場合があり多くの特徴があります。

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

  • 「ローカル変数を変更する必要がある場合はenumerateObjectsUsingBlockを使用しないでください」-trueではありません。あなたはあなたの地元住民を次のように宣言することができ__block、彼らはブロックで書き込み可能になります。

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

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

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


16
うわー、非常に有益です。私はこれらの両方の答えを受け入れることができればいいのにと思いますが、チャックと一緒に行くことにします。また、__ blockを検索しているときにあなたのブログ(friday.com/bbum/2009/08/29/blocks-tips-tricks)を見つけ、さらに学習しました。ありがとうございました。
Paul Wheeler、

8
記録のために、ブロックベースの列挙は常に「同じくらい速い」とは限りませんmikeabdullah.net/slow-block-based-dictionary-enumeration.html
Mike Abdullah

2
@VanDuTranブロックは、別のスレッドで実行するように指定した場合にのみ、別のスレッドで実行されます。列挙の同時実行オプションを使用しない限り、呼び出しが行われたのと同じスレッドで実行されます
bbum

2
Nick Lockwoodはこれに関して本当に素晴らしい記事を書いており、enumerateObjectsUsingBlock配列とセットの高速列挙よりもまだかなり遅いようです。なんでかしら?iosdevelopertips.com/objective-c/...
ボブSpryn

2
これは実装の詳細の一種ですenumerateObjectsUsingBlockが、ブロックの各呼び出しを自動解放プールでラップする2つのアプローチの違いのうち、この回答では言及する必要があります(少なくともOS X 10.10以降)。これは、for inそれを行わない場合と比較したパフォーマンスの違いを説明しています。
Pol

83

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

単純なループもより明確に読みます。2つのバージョンのボイラープレートは次のとおりです。

for (id x in y){
}

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

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

だからあなたはいつ使うべきenumerateObjectsUsingBlock:ですか?後で実行するために、または複数の場所でブロックを格納する場合。ループ本体の過度の置換ではなく、実際にブロックをファーストクラスの関数として使用している場合に適しています。


5
enumerateObjectsUsingBlock:すべての場合において、同じ速度または高速列挙よりも高速になります。 for(... in ...)内部データ構造の中間表現を提供するためにコレクションを必要とする高速列挙を使用します。お気づきのように、おそらく無関係です。
bbum

4
+1When you're storing a block to execute later or in multiple places. It's good for when you're actually using a block as a first-class function rather than an overwrought replacement for a loop body.
Steve

7
@bbum私自身のテストenumerateObjects...では、ループを使用して列挙を行うと、実際に列挙が遅くなり、速くなる可能性があることが示されています。私はこのテストを数千回実行しました。ブロックとループの本体は同じ1行のコード[(NSOperation *)obj cancel];でした。平均:高速列挙ループ- -[JHStatusBar dequeueStatusMessage:] [Line: 147] Fast enumeration time (for..in..loop): 0.000009およびブロック- -[JHStatusBar dequeueStatusMessage:] [Line: 147] Enumeration time using block: 0.000043。時間差が非常に大きく、一貫しているのは奇妙ですが、明らかに、これは非常に特殊なテストケースです。
chown

42

この質問は古く、物事は変わっていませんが、受け入れられた答えは正しくありません。

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

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

Fast Enumeration with for-inは、コレクションを列挙する慣用的な方法です。

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

クイックテストの結果、2014年のiOS 7は、enumerateObjectsUsingBlockfor-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

4
MacBook Pro Retina 2014で同じテストを実行すると、enumerateObjectsUsingBlock実際には5倍遅いことが確認できます。これは、自動解放プールがブロックの各呼び出しをラップするためであると思われますが、このfor in場合は発生しません。
Pol

2
enumerateObjectsUsingBlock:Xcode 7.xを使用してビルドすると、実際のiPhone 6 iOS9では依然として4倍遅くなることを確認します。
・クール

1
確認ありがとうございます!この回答があまり埋もれenumerateないようにしたいのですが、FPのように感じられ、パフォーマンス分析を聞きたくないという理由で構文が好きな人もいます。
Adam Kaplan

1
ちなみに、これは自動解放プールだけが原因ではありません。より多くのスタックのプッシュとポップがありますenumerate:
Adam Kaplan

24

パフォーマンスに関する質問に答えるために、パフォーマンステストプロジェクトを使用していくつかのテストを行いました。配列内のすべてのオブジェクトにメッセージを送信するための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を使用してください。ただし、場合によっては、コレクションを列挙するのにかかる時間は、各オブジェクトに実行させたいコードを実行するのにかかる時間よりも短いことを覚えておいてください。


特定のテストを指摘できますか?あなたは間違った答えを出したようです。
・クール

3

ネストされたループを解除する場合は、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ステートメントを使用することです。


1
または、ここで行ったように、メソッドから戻ることができます:-D
Adam Kaplan

1

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

  • for (... in ...)-私のデフォルトのgotoとして。私にとってはより直感的で、実際の設定よりも多くのプログラミング履歴があります-言語間の再利用、IDEのオートコンプリートによるほとんどのデータ構造のタイピングの減少:P。

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.