Question Comment effectuer une itération sur un NSArray?


Je cherche l'idiome standard pour parcourir un NSArray. Mon code doit convenir à OS X 10.4+.


415
2018-06-14 14:21


origine


Réponses:


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 qui est conforme à la [NSFastEnumeration protocol](Référence cacao). Cette approche présente un avantage en termes de vitesse car elle stocke des pointeurs sur plusieurs objets (obtenus via un seul appel de méthode) dans un tampon et les parcourt en progressant dans le tampon à l'aide de l'arithmétique du pointeur. C'est beaucoup plus rapide que d'appeler -objectAtIndex: chaque fois à travers la boucle.

Il est également intéressant de noter que pendant que vous techniquement pouvez utiliser une boucle for-in pour parcourir un NSEnumerator, J'ai constaté que cela annule pratiquement tout l'avantage de rapidité de l'énumération rapide. La raison en est que le défaut NSEnumerator implémentation 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 sous la forme Not To Be Fixed. La raison en est que l'énumération rapide pré-récupère 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 que vous utilisiez le même énumérateur après une rupture de la boucle, il arrive souvent que plusieurs objets soient ignoré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 aussi utiliser -enumerateObjectsWithOptions:usingBlock: et passe NSEnumerationConcurrent et / ou NSEnumerationReverse comme l'argument des options.


10.4 ou plus tôt

L'idiome standard pour pré-10.5 est d'utiliser un NSEnumerator et une boucle while, comme ça:

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

Je recommande de le garder simple. Vous attacher à un type de tableau est inflexible, et la prétendue augmentation de la vitesse d'utilisation -objectAtIndex: est insignifiant 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 de pointeur sur la structure de données sous-jacente et supprime la plus grande partie de l'appel de méthode.) L'optimisation prématurée n'est jamais une bonne idée.

En utilisant -objectEnumerator, vous passez très facilement à une autre collection énumérable (comme un NSSet, les clés dans un NSDictionary, etc.), ou même passer à -reverseObjectEnumerator é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 passer NSEnumerator et le code ne doit même pas se soucier de quelle c'est itératif. En outre, un NSEnumerator (du moins celles fournies par le code Apple) conserve la collection énumérée tant qu'il y a plus d'objets, vous n'avez donc pas à vous soucier de la durée de validité d'un objet à relecture automatique.

Peut-être la plus grande chose NSEnumerator (ou énumération rapide) vous protège d'avoir une collection modifiable (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 que le problème est survenu) qui peuvent être horribles à déboguer. L'énumération utilisant un des idiomes standard a un comportement "fail-fast", donc le problème (causé par un code incorrect) se manifestera immédiatement lorsque vous essayerez d'accéder à l'objet suivant après que la mutation se soit produite. Les programmes devenant de plus en plus complexes et multithread, ou même dépendant 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! :-)



629
2018-06-14 14:27



Pour OS X 10.4.x et les versions précédentes:

 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
}

121
2018-06-14 14:25



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é que généralement, il est exact à 2-3 chiffres significatifs et après cela, il varierait avec chaque exécution. Cela donne une marge d'erreur inférieure à 1%. Le test était exécuté 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 interface très agréable pour gérer les informations sans avoir à se soucier de la bureaucratie liée à la gestion de la mémoire, à la réallocation, etc. . Je pense qu'il est assez évident que l'utilisation d'un NSArray de NSNumbers sera plus lente qu'un tableau C de flottants pour des itérations simples, alors j'ai décidé de faire des tests, et les résultats étaient assez choquants! Je ne m'attendais pas à ce que ce soit si mauvais. Note: ces tests sont effectués sur un iPhone 3G car c'est la plateforme cible qui m'intéresse.

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

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

Entête:

// Array_Speed_TestViewController.h

// Test de vitesse de tableau

// 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 tableau

// 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 bonne qu'une énumération rapide, mais il y a une caractéristique très intéressante: l'énumération concurrente. Si l'ordre d'énumération n'est pas important et que les tâches peuvent être effectuées en parallèle sans verrouillage, cela peut accélérer considérablement le fonctionnement d'un système multicœur. Plus à ce sujet dans la section de l'é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 l'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

C'est une forme d'itération externe: [myArray objectEnumerator] retourne un objet. Cet objet a une méthode nextObject que l'on peut appeler dans une boucle jusqu'à ce qu'elle revienne nulle

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 nombre entier et interroge l'objet à l'aide de [myArray objectAtIndex: index] est la forme la plus élémentaire d'énumération.

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

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


14
2018-05-11 15:30



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 3. avec l'auto-complétion oublie d'écrire l'enveloppe d'itération.

8
2017-08-31 07:11



Ajouter each méthode dans votre NSArray category, tu vas en avoir besoin beaucoup

Code pris de ObjectiveSugar

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

6
2018-01-01 17:14



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

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

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

1
2017-07-20 16:54



Faites ceci: -

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

0
2018-06-01 07:17