Comment puis-je itérer sur un NSArray?

Réponses:

667

Le code généralement préféré pour 10.5 + / iOS.

for (id object in array) {
    // do something with object
}

Cette construction est utilisée pour énumérer des objets dans une collection conforme au NSFastEnumerationprotocole. Cette approche présente un avantage de vitesse car elle stocke des pointeurs vers plusieurs objets (obtenus via un seul appel de méthode) dans un tampon et les parcourt en avançant dans le tampon à l'aide de l'arithmétique des pointeurs. C'est beaucoup plus rapide que d'appeler à -objectAtIndex:chaque fois dans la boucle.

Il convient également de noter que, même si vous pouvez techniquement utiliser une boucle for-in pour parcourir une NSEnumerator, j'ai constaté que cela annule pratiquement tous les avantages de vitesse d'une énumération rapide. La raison en est que l' NSEnumeratorimplémentation par défaut de -countByEnumeratingWithState:objects:count:place un seul objet dans le tampon à chaque appel.

Je l'ai signalé dans radar://6296108 (L'énumération rapide des NSEnumerators est lente) mais elle a été renvoyée comme étant non à corriger. La raison en est que l'énumération rapide prélève un groupe d'objets, et si vous souhaitez énumérer uniquement à un point donné de l'énumérateur (par exemple jusqu'à ce qu'un objet particulier soit trouvé ou que la condition soit remplie) et utilisez le même énumérateur après avoir éclaté de la boucle, il arrive souvent que plusieurs objets soient sautés.

Si vous codez pour OS X 10.6 / iOS 4.0 et supérieur, vous avez également la possibilité d'utiliser des API basées sur des blocs pour énumérer les tableaux et autres collections:

[array enumerateObjectsUsingBlock:^(id object, NSUInteger idx, BOOL *stop) {
    // do something with object
}];

Vous pouvez également utiliser -enumerateObjectsWithOptions:usingBlock:et passer NSEnumerationConcurrentet / ou NSEnumerationReversecomme argument d'options.


10.4 ou version antérieure

L'idiome standard pour la version antérieure à 10.5 est d'utiliser une NSEnumeratorboucle while et while, comme ceci:

NSEnumerator *e = [array objectEnumerator];
id object;
while (object = [e nextObject]) {
  // do something with object
}

Je recommande de rester simple. Vous attacher à un type de tableau est inflexible, et l'augmentation supposée de la vitesse d'utilisation-objectAtIndex: est insignifiante pour l'amélioration avec une énumération rapide sur 10.5+ de toute façon. (L'énumération rapide utilise en fait l'arithmétique des pointeurs sur la structure de données sous-jacente et supprime la majeure partie de la surcharge des appels de méthode.) L'optimisation prématurée n'est jamais une bonne idée - elle se traduit par un code plus compliqué pour résoudre un problème qui n'est pas votre goulot d'étranglement de toute façon.

Lors de l'utilisation -objectEnumerator, vous passez très facilement à une autre collection énumérable (comme un NSSet, les clés dans un NSDictionary, etc.), ou même passez à -reverseObjectEnumeratorpour énumérer un tableau en arrière, le tout sans autre changement de code. Si le code d'itération est dans une méthode, vous pouvez même en passer une NSEnumeratoret le code n'a même pas à se soucier de ce qu'il itère. De plus, un NSEnumerator(au moins ceux fournis par le code Apple) conserve la collection qu'il énumère tant qu'il y a plus d'objets, vous n'avez donc pas à vous soucier de la durée de vie d'un objet libéré automatiquement.

Peut-être que la plus grande chose qu'une NSEnumerator(ou une énumération rapide) vous protège est d'avoir une collection mutable (tableau ou autre) sous vous à votre insu pendant que vous l'énumérez. Si vous accédez aux objets par index, vous pouvez rencontrer des exceptions étranges ou des erreurs ponctuelles (souvent longtemps après le problème) qui peuvent être horribles à déboguer. L'énumération à l'aide de l'un des idiomes standard a un comportement "rapide", de sorte que le problème (provoqué par un code incorrect) se manifestera immédiatement lorsque vous tenterez d'accéder à l'objet suivant après la mutation. À mesure que les programmes deviennent plus complexes et multithreads, ou dépendent même de quelque chose que le code tiers peut modifier, le code d'énumération fragile devient de plus en plus problématique. Encapsulation et abstraction FTW! :-)


Quinn Taylor
la source
28
Remarque: la plupart des compilateurs émettront un avertissement concernant "while (object = [e nextObject])". Dans ce cas, vous voulez réellement utiliser = au lieu de ==. Pour supprimer les avertissements, vous pouvez ajouter des parenthèses supplémentaires: "while ((object = [e nextObject]))".
Adam Rosenfield
Une autre chose à retenir avec NSEnumerator est qu'il utilisera plus de mémoire (fait une copie du tableau) que l'approche objectWithIndex.
deaderikh
@QuinnTaylor En utilisant for (id object in array), est-ce un moyen de déterminer également l'index actuel des objets dans le tableau, ou faut-il inclure un compteur séparé?
Coderama
1
Vous auriez besoin d'un compteur séparé, mais je suggérerais d'envisager une énumération basée sur des blocs (qui inclut l'index) si elle est disponible pour vous.
Quinn Taylor
Je suis bizarre, mais j'utilise une forboucle comme celle-ci:for(;;) { id object = [ e nextObject ] ; if ( !e ) { break ; } ... your loop operation ... }
nielsbot
125

Pour OS X 10.4.x et versions antérieures:

 int i;
 for (i = 0; i < [myArray count]; i++) {
   id myArrayElement = [myArray objectAtIndex:i];
   ...do something useful with myArrayElement
 }

Pour OS X 10.5.x (ou iPhone) et au-delà:

for (id myArrayElement in myArray) {
   ...do something useful with myArrayElement
}
deaderikh
la source
2
Dans le premier exemple, vous n'avez même pas besoin de déclarer l'int en dehors de la boucle. Cela fonctionne tout aussi bien et étend bien la variable afin que vous puissiez la réutiliser plus tard si nécessaire: for (int i = 0; i <[myArray count]; i ++) ... Mais aussi, sachez que l'appel de -count à chaque fois à travers le tableau peut annuler l'avantage d'utiliser -objectAtIndex:
Quinn Taylor
1
Et en réalité, si vous énumérez une collection modifiable, il est techniquement plus correct de vérifier le nombre à chaque fois dans la boucle. J'ai clarifié ma réponse pour expliquer que l'utilisation de NSEnumerator ou NSFastEnumeration peut protéger contre les mutations simultanées du tableau.
Quinn Taylor
Le for (int i = 0; ...) contruct est un dialecte en langage C (C99 est croire), que j'utilise moi-même mais je n'étais pas sûr que ce soit le XCode par défaut.
deaderikh
C99 est la valeur par défaut via Xcode 3.1.x - à un moment futur, la valeur par défaut passera à GNU99, qui (entre autres) prend en charge les unions et structures anonymes. Ça devrait être sympa ...
Quinn Taylor
2
L'utilisation for (NSUInteger i = 0, count = [myArray count]; i < count; i++)est probablement la plus efficace et la plus concise que vous obtiendrez pour cette approche.
Quinn Taylor
17

Les résultats du test et du code source sont ci-dessous (vous pouvez définir le nombre d'itérations dans l'application). Le temps est en millisecondes et chaque entrée est un résultat moyen de l'exécution du test 5 à 10 fois. J'ai trouvé qu'en général, il est précis à 2-3 chiffres significatifs et après cela, il varie à chaque exécution. Cela donne une marge d'erreur inférieure à 1%. Le test fonctionnait sur un iPhone 3G car c'est la plate-forme cible qui m'intéressait.

numberOfItems   NSArray (ms)    C Array (ms)    Ratio
100             0.39            0.0025          156
191             0.61            0.0028          218
3,256           12.5            0.026           481
4,789           16              0.037           432
6,794           21              0.050           420
10,919          36              0.081           444
19,731          64              0.15            427
22,030          75              0.162           463
32,758          109             0.24            454
77,969          258             0.57            453
100,000         390             0.73            534

Les classes fournies par Cocoa pour gérer les ensembles de données (NSDictionary, NSArray, NSSet etc.) fournissent une très belle interface pour gérer les informations, sans avoir à se soucier de la bureaucratie de la gestion de la mémoire, de la réallocation, etc. Bien sûr, cela a un coût cependant . Je pense qu'il est assez évident que dire que l'utilisation d'un NSArray de NSNumbers va être plus lent qu'un C Array de flottants pour des itérations simples, j'ai donc décidé de faire quelques tests, et les résultats ont été assez choquants! Je ne m'attendais pas à ce que ce soit si mauvais. Remarque: ces tests sont effectués sur un iPhone 3G car c'est la plateforme cible qui m'intéressait.

Dans ce test, je fais une comparaison de performances d'accès aléatoire très simple entre un flotteur C * et NSArray de NSNumbers

Je crée une boucle simple pour résumer le contenu de chaque tableau et les chronométrer en utilisant mach_absolute_time (). Le NSMutableArray prend en moyenne 400 fois plus de temps !! (pas 400 pour cent, juste 400 fois plus longtemps! c'est 40000% plus long!).

Entête:

// Array_Speed_TestViewController.h

// Test de vitesse de la baie

// Créé par Mehmet Akten le 05/02/2009.

// Copyright MSA Visuals Ltd. 2009. Tous droits réservés.

#import <UIKit/UIKit.h>

@interface Array_Speed_TestViewController : UIViewController {

    int                     numberOfItems;          // number of items in array

    float                   *cArray;                // normal c array

    NSMutableArray          *nsArray;               // ns array

    double                  machTimerMillisMult;    // multiplier to convert mach_absolute_time() to milliseconds



    IBOutlet    UISlider    *sliderCount;

    IBOutlet    UILabel     *labelCount;


    IBOutlet    UILabel     *labelResults;

}


-(IBAction) doNSArray:(id)sender;

-(IBAction) doCArray:(id)sender;

-(IBAction) sliderChanged:(id)sender;


@end

La mise en oeuvre:

// Array_Speed_TestViewController.m

// Test de vitesse de la baie

// Créé par Mehmet Akten le 05/02/2009.

// Copyright MSA Visuals Ltd. 2009. Tous droits réservés.

    #import "Array_Speed_TestViewController.h"
    #include <mach/mach.h>
    #include <mach/mach_time.h>

 @implementation Array_Speed_TestViewController



 // Implement viewDidLoad to do additional setup after loading the view, typically from a nib.

- (void)viewDidLoad {

    NSLog(@"viewDidLoad");


    [super viewDidLoad];


    cArray      = NULL;

    nsArray     = NULL;


    // read initial slider value setup accordingly

    [self sliderChanged:sliderCount];


    // get mach timer unit size and calculater millisecond factor

    mach_timebase_info_data_t info;

    mach_timebase_info(&info);

    machTimerMillisMult = (double)info.numer / ((double)info.denom * 1000000.0);

    NSLog(@"machTimerMillisMult = %f", machTimerMillisMult);

}



// pass in results of mach_absolute_time()

// this converts to milliseconds and outputs to the label

-(void)displayResult:(uint64_t)duration {

    double millis = duration * machTimerMillisMult;


    NSLog(@"displayResult: %f milliseconds", millis);


    NSString *str = [[NSString alloc] initWithFormat:@"%f milliseconds", millis];

    [labelResults setText:str];

    [str release];

}




// process using NSArray

-(IBAction) doNSArray:(id)sender {

    NSLog(@"doNSArray: %@", sender);


    uint64_t startTime = mach_absolute_time();

    float total = 0;

    for(int i=0; i<numberOfItems; i++) {

        total += [[nsArray objectAtIndex:i] floatValue];

    }

    [self displayResult:mach_absolute_time() - startTime];

}




// process using C Array

-(IBAction) doCArray:(id)sender {

    NSLog(@"doCArray: %@", sender);


    uint64_t start = mach_absolute_time();

    float total = 0;

    for(int i=0; i<numberOfItems; i++) {

        total += cArray[i];

    }

    [self displayResult:mach_absolute_time() - start];

}



// allocate NSArray and C Array 

-(void) allocateArrays {

    NSLog(@"allocateArrays");


    // allocate c array

    if(cArray) delete cArray;

    cArray = new float[numberOfItems];


    // allocate NSArray

    [nsArray release];

    nsArray = [[NSMutableArray alloc] initWithCapacity:numberOfItems];



    // fill with random values

    for(int i=0; i<numberOfItems; i++) {

        // add number to c array

        cArray[i] = random() * 1.0f/(RAND_MAX+1);


        // add number to NSArray

        NSNumber *number = [[NSNumber alloc] initWithFloat:cArray[i]];

        [nsArray addObject:number];

        [number release];

    }


}



// callback for when slider is changed

-(IBAction) sliderChanged:(id)sender {

    numberOfItems = sliderCount.value;

    NSLog(@"sliderChanged: %@, %i", sender, numberOfItems);


    NSString *str = [[NSString alloc] initWithFormat:@"%i items", numberOfItems];

    [labelCount setText:str];

    [str release];


    [self allocateArrays];

}



//cleanup

- (void)dealloc {

    [nsArray release];

    if(cArray) delete cArray;


    [super dealloc];

}


@end

De: memo.tv

//////////////////////

Disponible depuis l'introduction des blocs, cela permet d'itérer un tableau avec des blocs. Sa syntaxe n'est pas aussi agréable que l'énumération rapide, mais il y a une caractéristique très intéressante: l'énumération simultanée. Si l'ordre d'énumération n'est pas important et que les travaux peuvent être effectués en parallèle sans verrouillage, cela peut fournir une accélération considérable sur un système multicœur. Plus d'informations à ce sujet dans la section énumération simultanée.

[myArray enumerateObjectsUsingBlock:^(id object, NSUInteger index, BOOL *stop) {
    [self doSomethingWith:object];
}];
[myArray enumerateObjectsWithOptions:NSEnumerationConcurrent usingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    [self doSomethingWith:object];
}];

/////////// NSFastEnumerator

L'idée derrière l'énumération rapide est d'utiliser un accès rapide au tableau C pour optimiser l'itération. Non seulement il est censé être plus rapide que NSEnumerator traditionnel, mais Objective-C 2.0 fournit également une syntaxe très concise.

id object;
for (object in myArray) {
    [self doSomethingWith:object];
}

///////////////////

NSEnumerator

Il s'agit d'une forme d'itération externe: [myArray objectEnumerator] renvoie un objet. Cet objet a une méthode nextObject que nous pouvons appeler dans une boucle jusqu'à ce qu'elle renvoie nil

NSEnumerator *enumerator = [myArray objectEnumerator];
id object;
while (object = [enumerator nextObject]) {
    [self doSomethingWith:object];
}

///////////////////

objectAtIndex: énumération

L'utilisation d'une boucle for qui augmente un entier et l'interrogation de l'objet à l'aide de [myArray objectAtIndex: index] est la forme d'énumération la plus élémentaire.

NSUInteger count = [myArray count];
for (NSUInteger index = 0; index < count ; index++) {
    [self doSomethingWith:[myArray objectAtIndex:index]];
}

////////////// De: darkdust.net

Hitendra Solanki
la source
12

Les trois manières sont:

        //NSArray
    NSArray *arrData = @[@1,@2,@3,@4];

    // 1.Classical
    for (int i=0; i< [arrData count]; i++){
        NSLog(@"[%d]:%@",i,arrData[i]);
    }

    // 2.Fast iteration
    for (id element in arrData){
        NSLog(@"%@",element);
    }

    // 3.Blocks
    [arrData enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
         NSLog(@"[%lu]:%@",idx,obj);
         // Set stop to YES in case you want to break the iteration
    }];
  1. Est le moyen le plus rapide dans l'exécution, et avec l'autocomplétion, oubliez d'écrire l'enveloppe d'itération.
Javier Calatrava Llavería
la source
7

Ajoutez une eachméthode dans votre NSArray category, vous en aurez besoin

Code extrait d' ObjectiveSugar

- (void)each:(void (^)(id object))block {
    [self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        block(obj);
    }];
}
onmyway133
la source
5

Voici comment déclarer un tableau de chaînes et les parcourir:

NSArray *langs = @[@"es", @"en", @"pt", @"it", @"fr"];

for (int i = 0; i < [langs count]; i++) {
  NSString *lang = (NSString*) [langs objectAtIndex:i];
  NSLog(@"%@, ",lang);
}
oabarca
la source
0

Pour Swift

let arrayNumbers = [11, 12, 13, 14, 15, 16, 17, 18, 19, 20]

// 1
for (index, value) in arrayNumbers.enumerated() {
    print(index, value)
    //... do somthing with array value and index
}


//2
for value in arrayNumbers {
    print(value)
    //... do somthing with array value
}
Nilesh R Patel
la source
-1

Faites ceci: -

for (id object in array) 
{
        // statement
}

la source