Wie iteriere ich über ein NSArray?

Antworten:

667

Der allgemein bevorzugte Code für 10.5 + / iOS.

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

Dieses Konstrukt wird verwendet, um Objekte in einer Sammlung aufzulisten, die dem NSFastEnumerationProtokoll entspricht. Dieser Ansatz hat einen Geschwindigkeitsvorteil, da er Zeiger auf mehrere Objekte (die über einen einzelnen Methodenaufruf erhalten wurden) in einem Puffer speichert und diese durchläuft, indem er mithilfe der Zeigerarithmetik durch den Puffer fährt. Dies ist viel schneller als -objectAtIndex:jedes Mal durch die Schleife aufzurufen .

Es ist auch erwähnenswert, dass Sie zwar technisch gesehen eine For-In-Schleife verwenden können, um eine zu durchlaufen NSEnumerator, ich jedoch festgestellt habe, dass dies praktisch den gesamten Geschwindigkeitsvorteil einer schnellen Aufzählung zunichte macht. Der Grund dafür ist , dass der Standard NSEnumeratorImplementierung von -countByEnumeratingWithState:objects:count:Orten nur ein Objekt in dem Puffer bei jedem Aufruf.

Ich habe dies in radar://6296108(Die schnelle Aufzählung von NSEnumerators ist träge) gemeldet, aber es wurde als Nicht zu beheben zurückgegeben. Der Grund dafür ist, dass eine schnelle Aufzählung eine Gruppe von Objekten vorab abruft und Sie nur bis zu einem bestimmten Punkt im Aufzähler aufzählen möchten (z. B. bis ein bestimmtes Objekt gefunden wurde oder die Bedingung erfüllt ist) und nach dem Ausbruch denselben Aufzähler verwenden In der Schleife würden häufig mehrere Objekte übersprungen.

Wenn Sie für OS X 10.6 / iOS 4.0 und höher codieren, haben Sie auch die Möglichkeit, blockbasierte APIs zum Auflisten von Arrays und anderen Sammlungen zu verwenden:

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

Sie können auch -enumerateObjectsWithOptions:usingBlock:und NSEnumerationConcurrentund oder oder NSEnumerationReverseals Optionsargument verwenden und übergeben .


10.4 oder früher

Die Standardsprache für Pre-10.5 ist die Verwendung einer NSEnumeratorund einer while-Schleife wie folgt :

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

Ich empfehle es einfach zu halten. Es ist unflexibel, sich an einen Array-Typ zu binden, und die angebliche Geschwindigkeitssteigerung bei der Verwendung -objectAtIndex:ist ohnehin unbedeutend für die Verbesserung bei schneller Aufzählung auf 10.5+. (Bei der schnellen Aufzählung wird tatsächlich eine Zeigerarithmetik für die zugrunde liegende Datenstruktur verwendet, und der größte Teil des Methodenaufrufaufwands entfällt.) Eine vorzeitige Optimierung ist niemals eine gute Idee - sie führt zu unübersichtlichem Code, um ein Problem zu lösen, das ohnehin nicht Ihr Engpass ist.

Bei der Verwendung -objectEnumeratorkönnen Sie ganz einfach zu einer anderen aufzählbaren Sammlung wechseln (z. B. zu NSSet, Schlüssel in einer NSDictionaryusw.) oder sogar zu wechseln, -reverseObjectEnumeratorum ein Array rückwärts aufzulisten, ohne dass andere Codeänderungen erforderlich sind. Wenn sich der Iterationscode in einer Methode befindet, können Sie sogar eine übergeben, NSEnumeratorund der Code muss sich nicht einmal darum kümmern, was er iteriert. Außerdem NSEnumeratorbehält ein (zumindest die vom Apple-Code bereitgestellten) die aufgezählte Sammlung bei, solange mehr Objekte vorhanden sind, sodass Sie sich keine Gedanken darüber machen müssen, wie lange ein automatisch freigegebenes Objekt vorhanden sein wird.

Vielleicht ist das Größte, wovor Sie eine NSEnumerator(oder schnelle Aufzählung) schützt, dass sich eine veränderbare Sammlung (Array oder andere) ohne Ihr Wissen unter Ihnen ändert, während Sie sie aufzählen. Wenn Sie über den Index auf die Objekte zugreifen, können seltsame Ausnahmen oder Fehler auftreten (häufig lange nach dem Auftreten des Problems), deren Debugging schrecklich sein kann. Die Aufzählung mit einer der Standard-Redewendungen hat ein "Fail-Fast" -Verhalten, sodass sich das Problem (verursacht durch falschen Code) sofort manifestiert, wenn Sie versuchen, auf das nächste Objekt zuzugreifen, nachdem die Mutation aufgetreten ist. Wenn Programme komplexer und multithreaded werden oder sogar von etwas abhängen, das durch Code von Drittanbietern geändert werden kann, wird fragiler Aufzählungscode zunehmend problematisch. Verkapselung und Abstraktion FTW! :-)


Quinn Taylor
quelle
28
Hinweis: Die meisten Compiler geben eine Warnung zu "while (object = [e nextObject])" aus. In diesem Fall wollen Sie tatsächlich = anstelle von == verwenden. Um die Warnungen zu unterdrücken, können Sie zusätzliche Klammern hinzufügen: "while ((object = [e nextObject]))".
Adam Rosenfield
Eine andere Sache, die Sie bei NSEnumerator beachten sollten, ist, dass es mehr Speicher benötigt (eine Kopie des Arrays erstellt) als der objectWithIndex-Ansatz.
Diederikh
@QuinnTaylor Gibt for (id object in array)es eine Möglichkeit, auch den aktuellen Index des Objekts im Array zu bestimmen, oder muss ein separater Zähler eingefügt werden?
Coderama
1
Sie benötigen einen separaten Zähler, aber ich würde vorschlagen, eine blockbasierte Aufzählung (die den Index enthält) in Betracht zu ziehen, wenn diese für Sie verfügbar ist.
Quinn Taylor
Ich bin komisch, aber ich benutze eine forSchleife wie diese:for(;;) { id object = [ e nextObject ] ; if ( !e ) { break ; } ... your loop operation ... }
Nielsbot
125

Für OS X 10.4.x und frühere Versionen:

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

Für OS X 10.5.x (oder iPhone) und darüber hinaus:

for (id myArrayElement in myArray) {
   ...do something useful with myArrayElement
}
diederikh
quelle
2
Im ersten Beispiel müssen Sie das int nicht einmal außerhalb der Schleife deklarieren. Dies funktioniert genauso gut und ordnet die Variable gut ein, damit Sie sie später bei Bedarf wiederverwenden können: for (int i = 0; i <[myArray count]; i ++) ... Beachten Sie jedoch auch, dass Sie -count jedes Mal aufrufen Durch das Array kann der Vorteil der Verwendung von -objectAtIndex aufgehoben werden:
Quinn Taylor
1
Und in Wirklichkeit ist es technisch korrekter, wenn Sie eine veränderbare Sammlung aufzählen, die Anzahl jedes Mal durch die Schleife zu überprüfen. Ich habe meine Antwort klargestellt, um zu erklären, dass die Verwendung von NSEnumerator oder NSFastEnumeration vor gleichzeitigen Mutationen des Arrays schützen kann.
Quinn Taylor
Das for (int i = 0; ...) Konstrukt ist ein C-Dialekt (C99 ist glauben), den ich selbst verwende, aber ich war mir nicht sicher, ob es der XCode-Standard war.
Diederikh
C99 ist die Standardeinstellung bis Xcode 3.1.x - zu einem späteren Zeitpunkt wird die Standardeinstellung in GNU99 geändert, das (unter anderem) anonyme Gewerkschaften und Strukturen unterstützt. Das sollte schön sein ...
Quinn Taylor
2
Die Verwendung for (NSUInteger i = 0, count = [myArray count]; i < count; i++)ist wahrscheinlich die effizienteste und prägnanteste, die Sie für diesen Ansatz erhalten.
Quinn Taylor
17

Die Ergebnisse des Tests und des Quellcodes sind unten aufgeführt (Sie können die Anzahl der Iterationen in der App festlegen). Die Zeit ist in Millisekunden angegeben, und jeder Eintrag ist ein durchschnittliches Ergebnis der fünf- bis zehnmaligen Ausführung des Tests. Ich fand heraus, dass es im Allgemeinen auf 2-3 signifikante Stellen genau ist und danach mit jedem Lauf variieren würde. Dies ergibt eine Fehlerquote von weniger als 1%. Der Test lief auf einem iPhone 3G, da dies die Zielplattform war, an der ich interessiert war.

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

Die von Cocoa bereitgestellten Klassen für die Verarbeitung von Datensätzen (NSDictionary, NSArray, NSSet usw.) bieten eine sehr schöne Schnittstelle für die Verwaltung von Informationen, ohne sich um die Bürokratie der Speicherverwaltung, Neuzuweisung usw. kümmern zu müssen. Dies ist natürlich mit Kosten verbunden . Ich denke, es ist ziemlich offensichtlich, dass die Verwendung eines NSArray von NSNumbers für einfache Iterationen langsamer sein wird als die Verwendung eines C-Arrays von Floats. Deshalb habe ich beschlossen, einige Tests durchzuführen, und die Ergebnisse waren ziemlich schockierend! Ich hatte nicht erwartet, dass es so schlimm wird. Hinweis: Diese Tests werden auf einem iPhone 3G durchgeführt, da dies die Zielplattform ist, an der ich interessiert war.

In diesem Test führe ich einen sehr einfachen Vergleich der Leistung bei wahlfreiem Zugriff zwischen einem C-Float * und einem NSArray von NSNumbers durch

Ich erstelle eine einfache Schleife, um den Inhalt jedes Arrays zusammenzufassen und mit mach_absolute_time () zu messen. Das NSMutableArray dauert durchschnittlich 400 mal länger !! (nicht 400 Prozent, nur 400 Mal länger! das sind 40.000% länger!).

Header:

// Array_Speed_TestViewController.h

// Array-Geschwindigkeitstest

// Erstellt von Mehmet Akten am 05/02/2009.

// Copyright MSA Visuals Ltd. 2009. Alle Rechte vorbehalten.

#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

Implementierung:

// Array_Speed_TestViewController.m

// Array-Geschwindigkeitstest

// Erstellt von Mehmet Akten am 05/02/2009.

// Copyright MSA Visuals Ltd. 2009. Alle Rechte vorbehalten.

    #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

Von: memo.tv.

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

Dies ist seit der Einführung von Blöcken verfügbar und ermöglicht das Iterieren eines Arrays mit Blöcken. Die Syntax ist nicht so gut wie die schnelle Aufzählung, aber es gibt eine sehr interessante Funktion: die gleichzeitige Aufzählung. Wenn die Aufzählungsreihenfolge nicht wichtig ist und die Jobs ohne Sperren parallel ausgeführt werden können, kann dies auf einem Mehrkernsystem zu einer erheblichen Beschleunigung führen. Mehr dazu im Abschnitt zur gleichzeitigen Aufzählung.

[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

Die Idee hinter der schnellen Aufzählung besteht darin, den schnellen C-Array-Zugriff zu verwenden, um die Iteration zu optimieren. Es soll nicht nur schneller als der herkömmliche NSEnumerator sein, sondern Objective-C 2.0 bietet auch eine sehr präzise Syntax.

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

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

NSEnumerator

Dies ist eine Form der externen Iteration: [myArray objectEnumerator] gibt ein Objekt zurück. Dieses Objekt hat eine Methode nextObject, die wir in einer Schleife aufrufen können, bis sie nil zurückgibt

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

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

objectAtIndex: Aufzählung

Die Verwendung einer for-Schleife, die eine Ganzzahl erhöht, und das Abfragen des Objekts mit [myArray objectAtIndex: index] ist die grundlegendste Form der Aufzählung.

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

/////////////// Von: darkdust.net

Hitendra Solanki
quelle
12

Die drei Möglichkeiten sind:

        //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. Ist der schnellste Weg in der Ausführung, und 3. vergessen Sie bei der automatischen Vervollständigung das Schreiben des Iterationsumschlags.
Javier Calatrava Llavería
quelle
7

Füge eine eachMethode hinzu NSArray category, du wirst sie sehr brauchen

Code aus ObjectiveSugar

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

So deklarieren Sie ein Array von Zeichenfolgen und iterieren darüber:

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

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

Für 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
quelle
-1

Mach das :-

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

quelle