objective c - Cuándo usar enumerateObjectsUsingBlock vs. for
objective-c multithreading (6)
Aunque esta pregunta es antigua, las cosas no han cambiado, la respuesta aceptada es incorrecta.
La API enumerateObjectsUsingBlock
no reemplazó for-in
, pero para 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álculos pesados (usando el parámetro
withOptions:
La enumeración rápida con for-in
sigue 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 antinaturalmente 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, enumerateObjectsUsingBlock
es consistentemente 700% más lento que for-in (basado en iteraciones de 1mm de una matriz de 100 elementos).
¿Es el rendimiento una verdadera preocupación práctica aquí?
Definitivamente no, con rara excepción.
El objetivo es demostrar que hay poco beneficio al usar enumerateObjectsUsingBlock:
over for-in
sin una razón realmente buena. No hace que el código sea más legible ... o más rápido ... o seguro para subprocesos. (otro error común).
La elección se reduce a las preferencias personales. Para mí, la opción idiomática y legible gana. En este caso, eso es Enumeración rápida usando 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
Además de las diferencias obvias:
- Use
enumerateObjectsUsingBlock
cuando necesite el índice y el objeto -
No use(me equivoqué al respecto, vea la respuesta de bbum)enumerateObjectsUsingBlock
cuando necesite modificar las variables locales
¿En general, el elemento enumerateObjectsUsingBlock
se considera mejor o peor cuando for (id obj in myArray)
también funciona? ¿Cuáles son las ventajas / desventajas (por ejemplo, es más o menos rendimiento)?
En última instancia, utilice el patrón que desee utilizar y sea más natural en el contexto.
Mientras que 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 soporteNSFastEnumeration
para implementar la enumeración). La enumeración rápida requiere la traducción de una representación interna a la representación para una enumeración rápida. Hay gastos generales en eso. La enumeración basada en bloques permite que la clase de recopilación enumere los 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 verdadero; puedes declarar a tus locales como
__block
y se podrán escribir en el bloque.enumerateObjectsWithOptions:usingBlock:
admite la 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 - de nuevo - elección personal.
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.
Gracias a @bbum y @Chuck para comenzar comparaciones exhaustivas sobre el rendimiento. Me alegra saber que es trivial. Parece que he ido con:
for (... in ...)
- como mi predeterminado goto. Más intuitivo para mí, más historia de programación aquí que cualquier preferencia real: reutilización de lenguaje cruzado, menos tipeo para la mayoría de las estructuras de datos debido a IDE autocompletado: P.enumerateObject...
- cuando se necesita acceso a objeto e índice. Y al acceder a estructuras que no sean de matriz o de diccionario (preferencia personal)for (int i=idx; i<count; i++)
- para matrices, cuando necesito comenzar en un índice distinto de cero
Para la enumeración simple, simplemente usar la enumeración rápida (es decir, un bucle for…in…
) es la opción más idiomática. El método de bloque 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 bucle en sí en lugar del cálculo interno sea un cuello de botella.
Un bucle simple también lee más claramente. Aquí está el modelo de las dos versiones:
for (id x in y){
}
[y enumerateObjectsUsingBlock:^(id x, NSUInteger index, BOOL *stop){
}];
Incluso si agrega una variable para seguir el índice, el bucle simple es más fácil de leer.
Entonces, cuando deberías usar enumerateObjectsUsingBlock:
:? Cuando está almacenando un bloque para ejecutarlo más tarde o en varios lugares. Es bueno para cuando realmente está usando un bloque como una función de primera clase en lugar de un reemplazo sobreexplotado para un cuerpo de bucle.
Para responder a la pregunta sobre el rendimiento, hice 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];
}];
Resultó que makeObjectsPerformSelector fue el más lento de lejos. Tomó el doble de tiempo que una enumeración rápida. Y enumerateObjectsUsingBlock fue el más rápido, fue alrededor de 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 toma enumerar una colección queda eclipsado por el tiempo que lleva ejecutar el código que desee que ejecute cada objeto.