Cuándo usar enumerateObjectsUsingBlock vs. para

150

Además de las diferencias obvias:

  • Utilizar enumerateObjectsUsingBlock cuando necesite tanto el índice como el objeto
  • No lo use enumerateObjectsUsingBlockcuando necesite modificar variables locales (estaba equivocado sobre esto, vea la respuesta de bbum)

¿ enumerateObjectsUsingBlockGeneralmente se considera mejor o peor cuando for (id obj in myArray)también funcionaría? ¿Cuáles son las ventajas / desventajas (por ejemplo, es más o menos eficaz)?

Paul Wheeler
fuente
1
Me gusta usarlo si necesito el índice actual.
Besi
Ver también: stackoverflow.com/questions/8509662/…
Simon Whitaker

Respuestas:

350

En última instancia, use el patrón que desee usar y que sea más natural en el contexto.

Si bien for(... in ...)es bastante conveniente y sintácticamente breve, enumerateObjectsUsingBlock:tiene una serie de características que pueden resultar interesantes o no:

  • enumerateObjectsUsingBlock:será tan rápido o más rápido que la enumeración rápida ( for(... in ...)utiliza el NSFastEnumerationsoporte para implementar la enumeración). La enumeración rápida requiere la traducción de una representación interna a la representación para la enumeración rápida. Hay gastos generales en el mismo. La enumeración basada en bloques permite a la clase de recopilación enumerar contenidos tan rápido como el recorrido más rápido del formato de almacenamiento nativo. Probablemente irrelevante para las matrices, pero puede ser una gran diferencia para los diccionarios.

  • "No use enumerateObjectsUsingBlock cuando necesite modificar variables locales" - no es cierto; puedes declarar a tus locales como __blocky se podrán escribir en el bloque.

  • enumerateObjectsWithOptions:usingBlock: admite enumeración simultánea o inversa.

  • Con los diccionarios, la enumeración basada en bloques es la única forma de recuperar la clave y el valor simultáneamente.

Personalmente, uso enumerateObjectsUsingBlock:más a menudo que for (... in ...), pero, una vez más, elección personal.

bbum
fuente
16
Guau, muy informativo. Desearía poder aceptar ambas respuestas, pero voy con Chuck porque me resuena un poco más. Además, encontré tu blog ( viernes.com/bbum/2009/08/29/blocks-tips-tricks ) mientras buscaba __block y aprendí aún más. Gracias.
Paul Wheeler
8
Para el registro, la enumeración basada en bloques no siempre es "tan rápido o más rápido" mikeabdullah.net/slow-block-based-dictionary-enumeration.html
Mike Abdullah
2
Los bloques @VanDuTran solo se ejecutan en un hilo separado si les dice que se ejecuten en un hilo separado. A menos que utilice la opción de enumeración de concurrencia, se ejecutará en el mismo hilo en el que se realizó la llamada
bbum
2
Nick Lockwood hizo un artículo realmente bueno sobre esto, y parece enumerateObjectsUsingBlockque todavía es significativamente más lento que la enumeración rápida para matrices y conjuntos. ¿Me pregunto porque? iosdevelopertips.com/objective-c/…
Bob Spryn
2
Aunque es una especie de detalle de implementación, debe mencionarse en esta respuesta entre las diferencias entre los dos enfoques que enumerateObjectsUsingBlockenvuelven cada invocación del bloque con un grupo de liberación automática (a partir de OS X 10.10 al menos). Esto explica la diferencia de rendimiento en comparación con for inlo que no hace eso.
Pol
83

Para una enumeración simple, simplemente usando la enumeración rápida (es decir, un for…in… bucle) es la opción más idiomática. El método de bloqueo puede ser marginalmente más rápido, pero eso no importa mucho en la mayoría de los casos: pocos programas están vinculados a la CPU, e incluso entonces es raro que el ciclo en sí en lugar del cálculo interno sea un cuello de botella.

Un bucle simple también se lee más claramente. Aquí está el repetitivo de las dos versiones:

for (id x in y){
}

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

Incluso si agrega una variable para rastrear el índice, el bucle simple es más fácil de leer.

Entonces, ¿cuándo deberías usar enumerateObjectsUsingBlock:? Cuando está almacenando un bloque para ejecutarlo más tarde o en varios lugares. Es bueno para cuando estás usando un bloque como una función de primera clase en lugar de un reemplazo forzado para un cuerpo de bucle.

Arrojar
fuente
55
enumerateObjectsUsingBlock:será la misma velocidad o más rápido que la enumeración rápida en todos los casos. for(... in ...)utiliza una enumeración rápida que requiere que la recopilación proporcione alguna representación provisional de las estructuras de datos internas. Como notas, probablemente irrelevante.
bbum
44
+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
77
@bbum Mis propias pruebas muestran que en enumerateObjects...realidad puede ser más lento que la enumeración rápida con un bucle. Hice esta prueba varios miles de veces; el cuerpo del bloque y bucle eran los mismos de una sola línea de código: [(NSOperation *)obj cancel];. Los promedios: bucle enum rápido - -[JHStatusBar dequeueStatusMessage:] [Line: 147] Fast enumeration time (for..in..loop): 0.000009y para el bloque - -[JHStatusBar dequeueStatusMessage:] [Line: 147] Enumeration time using block: 0.000043. Es extraño que la diferencia horaria sea tan grande y consistente, pero, obviamente, este es un caso de prueba muy específico.
vestido el
42

Aunque esta pregunta es antigua, las cosas no han cambiado, la respuesta aceptada es incorrecta.

La enumerateObjectsUsingBlockAPI no estaba destinada a reemplazar for-in, sino a un caso de uso totalmente diferente:

  • Permite la aplicación de lógica arbitraria no local. es decir, no necesita saber qué hace el bloque para usarlo en una matriz.
  • Enumeración concurrente para grandes colecciones o cálculo pesado (usando el withOptions:parámetro)

La enumeración rápida for-insigue siendo el método idiomático de enumerar una colección.

La enumeración rápida se beneficia de la brevedad del código, la legibilidad y las optimizaciones adicionales que lo hacen anormalmente rápido. ¡Más rápido que un viejo C for-loop!

Una prueba rápida concluye que en el año 2014 en iOS 7, enumerateObjectsUsingBlockes consistentemente un 700% más lento que for-in (basado en iteraciones de 1 mm de una matriz de 100 elementos).

¿Es el rendimiento una preocupación práctica real aquí?

Definitivamente no, con una rara excepción.

El punto es demostrar que hay poco beneficio al usar enumerateObjectsUsingBlock:más defor-in sin una buena razón. No hace que el código sea más legible ... o más rápido ... o seguro para subprocesos. (Otro concepto erróneo común).

La elección se reduce a la preferencia personal. Para mí, la opción idiomática y legible gana. En este caso, se trata de una enumeración rápida for-in.

Punto de referencia:

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

Resultados:

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
Adam Kaplan
fuente
44
Puedo confirmar que ejecutar la misma prueba en un MacBook Pro Retina 2014, en enumerateObjectsUsingBlockrealidad es 5 veces más lento. Aparentemente, esto se debe a un grupo de liberación automática que envuelve cada invocación del bloque, lo que no sucede en el for incaso.
Pol
2
Confirmo que enumerateObjectsUsingBlock:sigue siendo 4 veces más lento en el iPhone 6 iOS9 real, usando Xcode 7.x para construir.
Cœur
1
¡Gracias por confirmar! Desearía que esta respuesta no se enterrara tanto ... a algunas personas les gusta la enumeratesintaxis porque se siente como FP y no quieren escuchar el análisis de rendimiento.
Adam Kaplan
1
Por cierto, no se debe solo al grupo de liberación automática. Hay mucho más empuje y estallido con la pilaenumerate:
Adam Kaplan
24

Para responder a la pregunta sobre el rendimiento, realicé algunas pruebas con mi proyecto de prueba de rendimiento . Quería saber cuál de las tres opciones para enviar un mensaje a todos los objetos en una matriz es la más rápida.

Las opciones fueron:

1) makeObjectsPerformSelector

[arr makeObjectsPerformSelector:@selector(_stubMethod)];

2) enumeración rápida y envío regular de mensajes

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

3) enumerateObjectsUsingBlock y envío regular de mensajes

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

Resulta que makeObjectsPerformSelector fue el más lento con diferencia. Tomó el doble de tiempo que la enumeración rápida. Y enumerateObjectsUsingBlock fue el más rápido, fue alrededor del 15-20% más rápido que la iteración rápida.

Entonces, si está muy preocupado por el mejor rendimiento posible, use enumerateObjectsUsingBlock. Pero tenga en cuenta que, en algunos casos, el tiempo que lleva enumerar una colección se ve reducido por el tiempo que lleva ejecutar el código que desea que ejecute cada objeto.

LearnCocos2D
fuente
¿Puedes señalar tu prueba específica? Parece que has dado una respuesta incorrecta.
Cœur
3

Es bastante útil usar enumerateObjectsUsingBlock como un bucle externo cuando desea romper bucles anidados.

p.ej

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

La alternativa es usar declaraciones goto.

Gabe
fuente
1
O simplemente podría regresar del método tal como lo ha hecho aquí :-D
Adam Kaplan
1

Gracias a @bbum y @Chuck por comenzar comparaciones exhaustivas sobre el rendimiento. Me alegra saber que es trivial. Parece que me he ido con:

  • for (... in ...)- Como mi goto predeterminado. Más intuitivo para mí, más historial de programación aquí que cualquier preferencia real: reutilización de idiomas cruzados, menos tipeo para la mayoría de las estructuras de datos debido a IDE autocompletar: P.

  • enumerateObject...- cuando se necesita acceso al objeto y al índice. Y al acceder a estructuras que no son matrices o de diccionario (preferencia personal)

  • for (int i=idx; i<count; i++) - para matrices, cuando necesito comenzar en un índice distinto de cero

aprisionado por la nieve
fuente