web-dev-qa-db-fra.com

Comment puis-je parcourir un NSArray?

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

431
Steve McLeod

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 les objets d'une collection conforme au protocole NSFastEnumeration . Cette approche présente un avantage en termes de rapidité, car elle stocke les pointeurs de plusieurs objets (obtenus via un seul appel de méthode) dans un tampon et les itère en les faisant avancer dans le tampon en utilisant l’arithmétique de pointeur. C'est beaucoup plus rapide que d'appeler -objectAtIndex: à chaque fois par la boucle.

Il est également intéressant de noter que, bien que vous utilisiez can techniquement, une boucle for-in pour parcourir une NSEnumerator, j'ai constaté que cela annule pratiquement tout l'avantage de rapidité d'une énumération rapide. La raison en est que l'implémentation NSEnumerator par défaut de -countByEnumeratingWithState:objects:count: ne place qu'un seul objet dans la mémoire tampon à chaque appel.

Je l'ai signalé dans radar://6296108 (l'énumération rapide de NSEnumerators est lente) mais elle a été renvoyée comme étant non réparable. 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 utiliser le même énumérateur après la décomposition. de la boucle, il arrive souvent que plusieurs objets soient ignorés.

Si vous codez pour OS X 10.6/iOS 4.0 et versions ultérieures, vous avez également la possibilité d’utiliser des API par 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 NSEnumerationConcurrent et/ou NSEnumerationReverse comme argument options.


10.4 ou plus tôt

L'idiome standard pour les versions antérieures à la version 10.5 consiste à utiliser une boucle NSEnumerator et une boucle while, comme suit:

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

Je recommande de garder les choses simples. Vous attacher à un type de tableau est inflexible, et l'augmentation de vitesse supposée d'utiliser -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 réalité l'arithmétique de pointeur sur la structure de données sous-jacente et supprime l'essentiel de la surcharge des appels de méthodes.) L'optimisation prématurée n'est jamais une bonne idée. Il en résulte un code plus compliqué pour résoudre un problème qui n'est pas votre goulot d'étranglement.

Lorsque vous utilisez -objectEnumerator, vous passez très facilement à une autre collection énumérable (comme une NSSet, des clés dans une NSDictionary, etc.), ou même vous passez à -reverseObjectEnumerator pour énumérer un tableau à l'envers, le tout sans autre code changements. Si le code d'itération est dans une méthode, vous pouvez même passer toute variable NSEnumerator et le code n'a même pas à se soucier de quoi c'est itération. De plus, une 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 auto-supprimé.

Peut-être que la chose la plus importante contre laquelle une NSEnumerator (ou une énumération rapide) vous protège est qu’une collection mutable (tableau ou autre) change en-dessous de vous à votre insu alors que vous l’énumérez. Si vous accédez aux objets par index, vous pouvez rencontrer des exceptions étranges ou des erreurs inopinées (souvent longtemps après que le problème est survenu), ce qui peut être horrible à déboguer. L'énumération utilisant l'un des idiomes standard a un comportement "fail-fast", ainsi le problème (dû à un code incorrect) se manifestera immédiatement lorsque vous essayez d'accéder à l'objet suivant après la mutation. À mesure que les programmes deviennent de plus en plus complexes et multithreads, voire dépendent 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! :-)


646
Quinn Taylor

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 les versions ultérieures:

for (id myArrayElement in myArray) {
   ...do something useful with myArrayElement
}
124
diederikh

Les résultats du test et le 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 c'est exact à 2-3 chiffres significatifs et après que cela pourrait varier avec chaque exécution. Cela donne une marge d'erreur inférieure à 1%. Le test fonctionnait sur un iPhone 3G, car c'était 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 la gestion des ensembles de données (NSDictionary, NSArray, NSSet, etc.) fournissent une interface très agréable pour la gestion des informations, sans avoir à se soucier de la bureaucratie de la gestion de la mémoire, de la réaffectation, etc. Bien sûr, cela a un coût . Je pense qu'il est assez évident de dire qu'utiliser un NSArray of NSNumbers va être plus lent qu'un C Cray of float pour de simples itérations. J'ai donc décidé de faire des tests et les résultats ont été assez choquants! Je ne m'attendais pas à ce que ce soit si grave. Remarque: ces tests sont réalisés sur un iPhone 3G car c'est la plateforme cible qui m'intéressait.

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

Je crée une boucle simple pour résumer le contenu de chaque tableau et le chronométrer à l'aide de mach_absolute_time (). Le NSMutableArray prend en moyenne 400 fois plus longtemps !! (Pas 400%, seulement 400 fois plus longtemps! C'est 40 000% plus long!).

Entête:

// Array_Speed_TestViewController.h

// Test de vitesse de matrice

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

// 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 qu’une énumération rapide, mais il existe une caractéristique très intéressante: l’énumération simultanée. Si l'ordre de é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 d'é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 supposé ê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] renvoie un objet. Cet objet a une méthode nextObject que nous pouvons appeler dans une boucle jusqu'à ce qu'il renvoie nil

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

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

objectAtIndex: énumération

Utiliser une boucle for qui augmente un entier et interroger 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

15
Hitendra Hckr

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. C'est le moyen le plus rapide d'exécution et 3. avec l'auto-complétion, oubliez d'écrire l'enveloppe d'itération.

Ajoutez la méthode each dans votre NSArray category, vous en aurez beaucoup besoin

Code extrait de ObjectiveSugar

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

Voici comment vous déclarez un tableau de chaînes et vous les parcourez:

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

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

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
}
0
Nilesh R Patel