Quando utilizzare enumerateObjectsUsingBlock vs. for

Oltre alle ovvie differenze:

  • Utilizzare enumerateObjectsUsingBlock quando è necessario sia l’indice che l’object
  • Non utilizzare enumerateObjectsUsingBlock quando è necessario modificare le variabili locali (mi sono sbagliato su questo, vedere la risposta di bbum)

enumerateObjectsUsingBlock è generalmente considerato migliore o peggiore quando for (id obj in myArray) funzionerebbe anche? Quali sono i vantaggi / svantaggi (ad esempio è più o meno performante)?

In definitiva, utilizza qualsiasi pattern che desideri utilizzare e si presenta in modo più naturale nel contesto.

Mentre for(... in ...) è abbastanza conveniente e sintatticamente breve, enumerateObjectsUsingBlock: ha un numero di funzionalità che possono o non possono rivelarsi interessanti:

  • enumerateObjectsUsingBlock: sarà veloce o più veloce dell’enumerazione veloce ( for(... in ...) utilizza il supporto NSFastEnumeration per implementare l’enumerazione). L’enumerazione rapida richiede la conversione da una rappresentazione interna alla rappresentazione per l’enumerazione rapida. C’è un sovraccarico in esso. L’enumerazione basata su blocchi consente alla class di raccolta di enumerare i contenuti tanto rapidamente quanto il più veloce traversamento del formato di archiviazione nativo. Probabilmente irrilevante per gli array, ma può essere un’enorme differenza per i dizionari.

  • “Non utilizzare enumerateObjectsUsingBlock quando è necessario modificare le variabili locali” – non vero; puoi dichiarare i tuoi locali come __block e saranno scrivibili nel blocco.

  • enumerateObjectsWithOptions:usingBlock: supporta enumerazione simultanea o inversa.

  • con i dizionari, l’enumerazione basata su blocchi è l’unico modo per recuperare la chiave e il valore simultaneamente.

Personalmente, utilizzo enumerateObjectsUsingBlock: più spesso che for (... in ...) , ma – di nuovo – scelta personale.

Per l’enumerazione semplice, l’uso dell’enumerazione veloce (cioè for…in… ciclo for…in… ) è l’opzione più idiomatica. Il metodo di blocco potrebbe essere leggermente più veloce, ma non importa molto nella maggior parte dei casi – alcuni programmi sono legati alla CPU, e anche allora è raro che il ciclo stesso piuttosto che il calcolo all’interno costituisca un collo di bottiglia.

Un ciclo semplice si legge anche più chiaramente. Ecco la scheda tecnica delle due versioni:

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

Anche se aggiungi una variabile per tracciare l’indice, il ciclo semplice è più facile da leggere.

Quindi quando dovresti usare enumerateObjectsUsingBlock: Quando stai memorizzando un blocco da eseguire in seguito o in più posizioni. È positivo quando si utilizza effettivamente un blocco come funzione di prima class piuttosto che come sostituzione eccessiva per un corpo ad anello.

Sebbene questa domanda sia vecchia, le cose non sono cambiate, la risposta accettata non è corretta.

L’API enumerateObjectsUsingBlock non era pensata per sostituire for-in , ma per un caso d’uso completamente diverso:

  • Permette l’applicazione di una logica arbitraria, non locale. cioè non è necessario sapere cosa fa il blocco per usarlo su un array.
  • Enumerazione simultanea per grandi raccolte o calcoli pesanti (utilizzando il parametro withOptions:

L’enumerazione veloce con for-in è ancora il metodo idiomatico di enumerazione di una raccolta.

Fast Enumeration trae vantaggio dalla brevità del codice, dalla leggibilità e da ulteriori ottimizzazioni che lo rendono innaturalmente veloce. Più veloce di un vecchio ciclo for C!

Un rapido test conclude che nell’anno 2014 su iOS 7, enumerateObjectsUsingBlock è costantemente più lento del 700% rispetto a for-in (basato su iterazioni di 1 mm di un array di 100 elementi).

La performance è una vera preoccupazione pratica qui?

Sicuramente no, con rare eccezioni.

Il punto è dimostrare che c’è poco vantaggio nell’usare enumerateObjectsUsingBlock: over for-in senza una vera ragione. Non rende il codice più leggibile … o più veloce … o thread-safe. (un altro malinteso comune).

La scelta dipende dalle preferenze personali. Per me, l’opzione idiomatica e leggibile vince. In questo caso, si tratta dell’enumerazione veloce che utilizza for-in .

Indice di riferimento:

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

risultati:

 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 

Per rispondere alla domanda sulle prestazioni, ho effettuato alcuni test utilizzando il mio progetto di test delle prestazioni . Volevo sapere quale delle tre opzioni per inviare un messaggio a tutti gli oggetti in un array è la più veloce.

Le opzioni erano:

1) makeObjectsPerformSelector

 [arr makeObjectsPerformSelector:@selector(_stubMethod)]; 

2) veloce enumerazione e invio regolare di messaggi

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

3) enumerateObjectsUsingBlock e invio di messaggi regolari

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

Risulta che makeObjectsPerformSelector è stato il più lento di gran lunga. Ci è voluto il doppio del tempo dell’enumerazione veloce. E enumerateObjectsUsingBlock era il più veloce, era circa il 15-20% più veloce rispetto all’iterazione veloce.

Quindi, se sei molto preoccupato delle migliori prestazioni possibili, usa enumerateObjectsUsingBlock. Tuttavia, tieni presente che in alcuni casi il tempo necessario per enumerare una raccolta è sminuito dal tempo necessario per eseguire qualsiasi codice che desideri venga eseguito da ciascun object.

È abbastanza utile usare enumerateObjectsUsingBlock come un ciclo esterno quando si desidera interrompere i cicli annidati.

per esempio

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

L’alternativa sta usando le istruzioni goto.

Grazie a @bbum e @Chuck per aver avviato confronti completi sulle prestazioni. Sono contento di sapere che è banale. Mi sembra di essere andato con:

  • for (... in ...) – come il mio goto predefinito. Più intuitivo per me, più storia di programmazione qui di qualsiasi preferenza reale – riutilizzo della lingua incrociata, meno tipizzazione per la maggior parte delle strutture dati grazie al completamento automatico di IDE: P.

  • enumerateObject... – quando è necessario l’accesso all’object e all’indice. E quando si accede a strutture non di array o di dizionario (preferenza personale)

  • for (int i=idx; i - per gli array, quando ho bisogno di iniziare su un indice diverso da zero