web-dev-qa-db-fra.com

Quand utiliser enumerateObjectsUsingBlock vs. for

Outre les différences évidentes:

  • Utilisez enumerateObjectsUsingBlock lorsque vous avez besoin à la fois de l'index et de l'objet
  • N'utilisez pas enumerateObjectsUsingBlock lorsque vous devez modifier des variables locales (Je me suis trompé à ce sujet, voir la réponse de bbum)

Est-ce que enumerateObjectsUsingBlock est généralement considéré comme meilleur ou pire lorsque for (id obj in myArray) fonctionne également? Quels sont les avantages/inconvénients (par exemple, est-il plus ou moins performant)?

150
Paul Wheeler

En fin de compte, utilisez le modèle que vous souhaitez utiliser et vient plus naturellement dans le contexte.

Bien que for(... in ...) soit assez pratique et syntaxiquement bref, enumerateObjectsUsingBlock: Possède un certain nombre de fonctionnalités qui peuvent ou non s'avérer intéressantes:

  • enumerateObjectsUsingBlock: Sera aussi rapide ou plus rapide qu'une énumération rapide (for(... in ...) utilise le support NSFastEnumeration pour implémenter l'énumération). L'énumération rapide nécessite la traduction d'une représentation interne vers la représentation pour une énumération rapide. Il y a des frais généraux là-dedans. L'énumération par blocs permet à la classe collection d'énumérer le contenu aussi rapidement que la traversée la plus rapide du format de stockage natif. Probablement non pertinent pour les tableaux, mais cela peut être une énorme différence pour les dictionnaires.

  • "N'utilisez pas enumerateObjectsUsingBlock lorsque vous devez modifier des variables locales" - pas vrai; vous pouvez déclarer vos sections locales comme __block et elles seront inscriptibles dans le bloc.

  • enumerateObjectsWithOptions:usingBlock: Prend en charge l'énumération simultanée ou inversée.

  • avec les dictionnaires, l’énumération par blocs est le seul moyen de récupérer simultanément la clé et la valeur.

Personnellement, j'utilise plus souvent enumerateObjectsUsingBlock: Que for (... in ...), mais, encore une fois, c'est un choix personnel.

346
bbum

Pour une énumération simple, utilisez simplement une énumération rapide (c.-à-d. Un for…in… boucle) est l’option la plus idiomatique. La méthode du bloc peut être légèrement plus rapide, mais cela n'a pas beaucoup d'importance dans la plupart des cas - peu de programmes sont liés au processeur, et même dans ce cas, il est rare que la boucle elle-même plutôt que le calcul à l'intérieur constituent un goulot d'étranglement.

Une simple boucle se lit aussi plus clairement. Voici le passe-partout des deux versions:

for (id x in y){
}

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

Même si vous ajoutez une variable pour suivre l'index, la boucle simple est plus facile à lire.

Alors, quand devriez-vous utiliser enumerateObjectsUsingBlock:? Lorsque vous stockez un bloc à exécuter ultérieurement ou à plusieurs endroits. C'est bien lorsque vous utilisez un bloc en tant que fonction de premier ordre plutôt qu'un remplacement trop important pour un corps de boucle.

81
Chuck

Bien que cette question soit ancienne, les choses n'ont pas changé, la réponse acceptée est incorrecte.

L'API enumerateObjectsUsingBlock ne devait pas remplacer for-in, mais pour un cas d'utilisation totalement différent:

  • Il permet l'application d'une logique arbitraire et non locale. c’est-à-dire que vous n’avez pas besoin de savoir ce que fait le bloc pour l’utiliser sur un tableau.
  • Enumération simultanée pour les grandes collections ou les calculs lourds (en utilisant le withOptions: paramètre)

Énumération rapide avec for-in est toujours la méthode idiomatique d'énumération d'une collection.

Fast Enumeration bénéficie de la brièveté du code, de la lisibilité et de optimisations supplémentaires , ce qui le rend anormalement rapide. Plus rapide qu'un vieux C pour la boucle!

Un test rapide conclut que, en 2014 sur iOS 7, enumerateObjectsUsingBlock est toujours 700% plus lent que for-in (basé sur des itérations de 1 mm sur un tableau de 100 éléments).

La performance est-elle un réel problème pratique ici?

Certainement pas, à de rares exceptions près.

Le but est de démontrer que l’utilisation de enumerateObjectsUsingBlock: plus de for-in sans une très bonne raison. Cela ne rend pas le code plus lisible, ni plus rapide, ni thread-safe. (une autre idée fausse commune).

Le choix dépend des préférences personnelles. Pour moi, l'option idiomatique et lisible gagne. Dans ce cas, il s’agit d’une énumération rapide utilisant for-in.

Référence:

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

Résultats:

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

Pour répondre à la question sur les performances, j’ai fait quelques tests en utilisant mon projet de test de performances . Je voulais savoir laquelle des trois options d'envoi d'un message à tous les objets d'un tableau est la plus rapide.

Les options étaient:

1) makeObjectsPerformSelector

[arr makeObjectsPerformSelector:@selector(_stubMethod)];

2) énumération rapide et envoi régulier de messages

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

3) enumerateObjectsUsingBlock & normal message send

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

Il s'avère que makeObjectsPerformSelector était de loin le plus lent. Il a fallu deux fois plus de temps qu'un dénombrement rapide. Et énumerateObjectsUsingBlock était le plus rapide, il était environ 15 à 20% plus rapide qu'une itération rapide.

Donc, si vous êtes très préoccupé par les meilleures performances possibles, utilisez enumerateObjectsUsingBlock. Mais gardez à l'esprit que dans certains cas, le temps nécessaire pour énumérer une collection est réduit au minimum par le temps nécessaire pour exécuter le code que vous voulez que chaque objet exécute.

23
LearnCocos2D

Il est assez utile d'utiliser enumerateObjectsUsingBlock en tant que boucle externe pour casser des boucles imbriquées.

par exemple.

[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'alternative consiste à utiliser les instructions goto.

3
Gabe

Merci à @bbum et @Chuck pour le lancement de comparaisons complètes sur les performances. Content de savoir que c'est trivial. Je semble être allé avec:

  • for (... in ...) - comme mon goto par défaut. Plus intuitif pour moi, plus d’historique de programmation ici que de préférence réelle - réutilisation de plusieurs langues, moins de saisie pour la plupart des structures de données en raison de IDE auto complete: P.

  • enumerateObject... - lorsque l'accès à l'objet et à l'index est nécessaire. Et lors de l'accès à des structures autres que des tableaux ou des dictionnaires (préférence personnelle)

  • for (int i=idx; i<count; i++) - pour les tableaux, lorsque j'ai besoin de commencer avec un index non nul

1
snowbound