wake-up-neo.com

Verwendung von enumerateObjectsUsingBlock vs.

Neben den offensichtlichen Unterschieden:

  • Verwenden Sie enumerateObjectsUsingBlock, wenn Sie sowohl den Index als auch das Objekt benötigen
  • Verwenden Sie enumerateObjectsUsingBlock nicht, wenn Sie lokale Variablen ändern müssen (Ich habe mich geirrt, siehe bbums Antwort)

Wird enumerateObjectsUsingBlock im Allgemeinen als besser oder schlechter angesehen, wenn for (id obj in myArray) auch funktionieren würde? Was sind die Vor-/Nachteile (zum Beispiel ist es mehr oder weniger performant)?

150
Paul Wheeler

Verwenden Sie letztendlich das Muster, das Sie verwenden möchten und das im Kontext natürlicher ist.

Während for(... in ...) recht praktisch und syntaktisch kurz ist, hat enumerateObjectsUsingBlock: Eine Reihe von Funktionen, die sich als interessant erweisen können oder auch nicht:

  • enumerateObjectsUsingBlock: Ist genauso schnell oder schneller als die schnelle Aufzählung (for(... in ...) verwendet die Unterstützung von NSFastEnumeration, um die Aufzählung zu implementieren). Die schnelle Aufzählung erfordert die Übersetzung von einer internen Darstellung in die Darstellung für die schnelle Aufzählung. Da ist Overhead drin. Die blockbasierte Aufzählung ermöglicht es der Auflistungsklasse, Inhalte so schnell aufzulisten, wie das native Speicherformat am schnellsten durchlaufen wird. Wahrscheinlich irrelevant für Arrays, aber es kann ein großer Unterschied für Wörterbücher sein.

  • "Verwenden Sie nicht enumerateObjectsUsingBlock, wenn Sie lokale Variablen ändern müssen" - nicht wahr; Sie können Ihre Einheimischen als __block deklarieren, und sie sind im Block beschreibbar.

  • enumerateObjectsWithOptions:usingBlock: Unterstützt sowohl die gleichzeitige als auch die umgekehrte Aufzählung.

  • bei Wörterbüchern ist die blockbasierte Aufzählung die einzige Möglichkeit, den Schlüssel und den Wert gleichzeitig abzurufen.

Persönlich verwende ich enumerateObjectsUsingBlock: Häufiger als for (... in ...), aber auch hier meine persönliche Wahl.

346
bbum

Für eine einfache Aufzählung ist die einfache Verwendung einer schnellen Aufzählung (d. H. Einer for…in… - Schleife) die idiomatischere Option. Die Block-Methode ist zwar etwas schneller, aber in den meisten Fällen spielt das keine Rolle - nur wenige Programme sind an die CPU gebunden, und selbst dann ist die Schleife selbst und nicht die darin enthaltene Berechnung selten ein Engpass.

Eine einfache Schleife liest sich auch deutlicher. Hier ist das Boilerplate der beiden Versionen:

for (id x in y){
}

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

Selbst wenn Sie eine Variable hinzufügen, um den Index zu verfolgen, ist die einfache Schleife leichter zu lesen.

Wann sollten Sie also enumerateObjectsUsingBlock: Verwenden? Wenn Sie einen Block speichern, um ihn später oder an mehreren Stellen auszuführen. Es ist gut für den Fall, dass Sie einen Block als erstklassige Funktion verwenden und nicht als überarbeiteten Ersatz für einen Schleifenkörper.

81
Chuck

Obwohl diese Frage alt ist, haben sich die Dinge nicht geändert, die akzeptierte Antwort ist falsch.

Die enumerateObjectsUsingBlock API sollte nicht for-in Ersetzen, sondern für einen völlig anderen Anwendungsfall:

  • Es ermöglicht die Anwendung beliebiger, nicht lokaler Logik. Sie müssen nicht wissen, wie der Block ihn in einem Array verwendet.
  • Gleichzeitige Aufzählung für große Sammlungen oder umfangreiche Berechnungen (mit dem Parameter withOptions:)

Die schnelle Aufzählung mit for-in Ist immer noch die idiomatische Methode zum Aufzählen einer Sammlung.

Fast Enumeration profitiert von der Kürze des Codes, der Lesbarkeit und von zusätzlichen Optimierungen , die es unnatürlich schnell machen. Schneller als eine alte C for-Schleife!

Ein schneller Test kommt zu dem Schluss, dass enumerateObjectsUsingBlock im Jahr 2014 unter iOS 7 durchweg 700% langsamer ist als beim For-In (basierend auf 1-mm-Iterationen eines Arrays mit 100 Elementen).

Ist Leistung hier ein echtes praktisches Anliegen?

Auf keinen Fall, mit seltenen Ausnahmen.

Damit soll demonstriert werden, dass die Verwendung von enumerateObjectsUsingBlock: Gegenüber for-in Ohne einen wirklich guten Grund nur einen geringen Vorteil hat. Es macht den Code nicht lesbarer ... oder schneller ... oder threadsicher. (Ein weiteres häufiges Missverständnis).

Die Wahl hängt von den persönlichen Vorlieben ab. Für mich gewinnt die idiomatische und lesbare Option. In diesem Fall ist das eine schnelle Aufzählung mit for-in.

Benchmark:

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);

Ergebnisse:

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

Um die Frage zur Leistung zu beantworten, habe ich einige Tests mit meinem Leistungstestprojekt durchgeführt. Ich wollte wissen, welche der drei Optionen zum Senden einer Nachricht an alle Objekte in einem Array am schnellsten ist.

Die Optionen waren:

1) makeObjectsPerformSelector

[arr makeObjectsPerformSelector:@selector(_stubMethod)];

2) schnelle Aufzählung und regelmäßiges Senden von Nachrichten

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

3) enumerateObjectsUsingBlock & regular message send

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

Es stellte sich heraus, dass makeObjectsPerformSelector mit Abstand am langsamsten war. Es dauerte doppelt so lange wie die schnelle Aufzählung. Und enumerateObjectsUsingBlock war am schnellsten, es war ungefähr 15-20% schneller als eine schnelle Iteration.

Wenn Sie also sehr an der bestmöglichen Leistung interessiert sind, verwenden Sie enumerateObjectsUsingBlock. Beachten Sie jedoch, dass in einigen Fällen die Zeit, die zum Auflisten einer Auflistung erforderlich ist, um die Zeit verkürzt ist, die zum Ausführen des Codes erforderlich ist, den jedes Objekt ausführen soll.

23
LearnCocos2D

Es ist ziemlich nützlich, enumerateObjectsUsingBlock als äußere Schleife zu verwenden, wenn Sie verschachtelte Schleifen unterbrechen möchten.

z.B.

[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;
      }
    }
  }
}];

Die Alternative ist die Verwendung von goto-Anweisungen.

3
Gabe

Vielen Dank an @bbum und @Chuck für die umfassenden Leistungsvergleiche. Ich bin froh zu wissen, dass es trivial ist. Ich scheine gegangen zu sein mit:

  • for (... in ...) - als meine Standardeinstellung gehe zu. Für mich intuitiver, hier mehr Programmiergeschichte als jede echte Vorliebe - sprachübergreifende Wiederverwendung, weniger Typisierung für die meisten Datenstrukturen aufgrund von IDE auto complete: P.

  • enumerateObject... - wenn Zugriff auf Objekt und Index benötigt wird. Und beim Zugriff auf Nicht-Array- oder Dictionary-Strukturen (persönliche Präferenz)

  • for (int i=idx; i<count; i++) - für Arrays, wenn ich mit einem Index ungleich Null beginnen muss

1
snowbound