Jak iterować po NSArray?

Odpowiedzi:

667

Generalnie preferowany kod dla wersji 10.5 + / iOS.

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

Ta konstrukcja służy do wyliczania obiektów w kolekcji zgodnej z NSFastEnumerationprotokołem. Takie podejście ma przewagę prędkości, ponieważ przechowuje wskaźniki do kilku obiektów (uzyskane za pomocą pojedynczego wywołania metody) w buforze i iteruje je, przechodząc przez bufor za pomocą arytmetyki wskaźnika. Jest to o wiele szybsze niż wywoływanie za -objectAtIndex:każdym razem za pośrednictwem pętli.

Warto również zauważyć, że chociaż technicznie możesz użyć pętli for-in, aby przejść przez NSEnumerator, odkryłem, że to niweluje praktycznie całą przewagę szybkości szybkiego liczenia. Powodem jest to, że domyślna NSEnumeratorimplementacja -countByEnumeratingWithState:objects:count:umieszcza tylko jeden obiekt w buforze przy każdym wywołaniu.

Zgłosiłem to w radar://6296108(Szybkie wyliczanie NSEnumeratorów jest powolne), ale zostało zwrócone jako Nie do naprawienia. Powodem jest to, że szybkie wyliczanie wstępnie pobiera grupę obiektów, a jeśli chcesz wyliczyć tylko do określonego punktu w wyliczaczu (np. Do momentu znalezienia określonego obiektu lub spełnienia warunku) i użyj tego samego wyliczacza po wyłamaniu pętli często zdarza się, że kilka obiektów zostanie pominiętych.

Jeśli kodujesz w systemie OS X 10.6 / iOS 4.0 i nowszym, możesz również użyć interfejsów API opartych na blokach do wyliczenia tablic i innych kolekcji:

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

Możesz także użyć -enumerateObjectsWithOptions:usingBlock:i przekazać NSEnumerationConcurrenti / lub NSEnumerationReversejako argument opcji.


10.4 lub wcześniejszy

Standardowym idiomem dla wersji wcześniejszych niż 10.5 jest użycie NSEnumeratorpętli a, a więc:

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

Polecam zachować prostotę. Powiązanie się z typem tablicy jest nieelastyczne, a rzekomy wzrost prędkości używania -objectAtIndex:jest nieznaczny dla poprawy z szybkim wyliczaniem w wersji 10.5+. (Szybkie wyliczanie faktycznie używa arytmetyki wskaźnika na podstawowej strukturze danych i usuwa większość narzutu wywołania metody). Przedwczesna optymalizacja nigdy nie jest dobrym pomysłem - skutkuje to bałaganem w kodzie, który rozwiązuje problem, który nie jest twoim wąskim gardłem.

Podczas używania -objectEnumeratorbardzo łatwo przechodzisz do innej kolekcji zliczalnej (takiej jak NSSet, klucze w NSDictionaryitd.), A nawet przełączasz się, -reverseObjectEnumeratoraby wyliczyć tablicę wstecz, bez żadnych innych zmian kodu. Jeśli kod iteracji jest w metodzie, możesz nawet podać dowolny, NSEnumeratora kod nie musi nawet dbać o to , co iteruje. Ponadto NSEnumerator(przynajmniej te dostarczone przez kod Apple) zachowuje kolekcję, którą wylicza, o ile jest więcej obiektów, więc nie musisz się martwić o to, jak długo będzie istniał automatycznie wydany obiekt.

Być może największą rzeczą, NSEnumeratorprzed którą chroni cię (lub szybkie wyliczenie), jest modyfikowanie kolekcji (tablica lub inna) pod tobą bez twojej wiedzy podczas wyliczania. Jeśli uzyskujesz dostęp do obiektów według indeksu, możesz napotkać dziwne wyjątki lub błędy indywidualne (często długo po wystąpieniu problemu), których debugowanie może być przerażające. Wyliczenie za pomocą jednego ze standardowych idiomów ma działanie „szybkie w razie awarii”, więc problem (spowodowany niepoprawnym kodem) ujawni się natychmiast, gdy spróbujesz uzyskać dostęp do następnego obiektu po wystąpieniu mutacji. Ponieważ programy stają się bardziej złożone i wielowątkowe, a nawet zależą od czegoś, co może modyfikować kod innej firmy, kruchy kod wyliczania staje się coraz bardziej problematyczny. Kapsułkowanie i abstrakcja FTW! :-)


Quinn Taylor
źródło
28
Uwaga: większość kompilatorów ostrzega o „while (object = [e nextObject])”. W tym przypadku faktycznie masz zamiar użyć = zamiast ==. Aby ukryć ostrzeżenia, możesz dodać dodatkowe nawiasy: „while ((object = [e nextObject]))”.
Adam Rosenfield
Inną rzeczą, o której należy pamiętać w NSEnumerator jest to, że zużyje więcej pamięci (tworzy kopię tablicy) niż metoda objectWithIndex.
diederikh
@QuinnTaylor Za pomocą for (id object in array), czy istnieje sposób na określenie również bieżącego indeksu obiektów w tablicy, czy też należy uwzględnić osobny licznik?
Coderama,
1
Potrzebujesz osobnego licznika, ale sugeruję rozważenie wyliczenia blokowego (który obejmuje indeks), jeśli jest on dostępny.
Quinn Taylor,
Jestem dziwny, ale używam takiej forpętli:for(;;) { id object = [ e nextObject ] ; if ( !e ) { break ; } ... your loop operation ... }
nielsbot 25.01.2013
125

W systemie OS X 10.4.xi wcześniejszych:

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

W systemie OS X 10.5.x (lub iPhone) i nowszych wersjach:

for (id myArrayElement in myArray) {
   ...do something useful with myArrayElement
}
Dieterikh
źródło
2
W pierwszym przykładzie nie musisz nawet deklarować int poza pętlą. Działa to równie dobrze i ładnie mierzy zmienną, aby w razie potrzeby można było z niej później skorzystać: for (int i = 0; i <[myArray count]; i ++) ... Ale należy również pamiętać, że wywoływanie -count za każdym razem przez tablicę może anulować korzyść z użycia -objectAtIndex:
Quinn Taylor
1
W rzeczywistości, jeśli wyliczasz kolekcję podlegającą modyfikacji, technicznie bardziej poprawne jest sprawdzanie liczby za każdym razem przez pętlę. Wyjaśniłem moją odpowiedź, aby wyjaśnić, że użycie NSEnumerator lub NSFastEnumeration może chronić przed równoczesnymi mutacjami tablicy.
Quinn Taylor
Konstrukt for (int i = 0; ...) jest dialektem w języku C (wydaje się, że C99), którego sam używam, ale nie byłem pewien, czy to domyślny XCode.
diederikh
C99 jest domyślną wersją Xcode 3.1.x - w pewnym momencie domyślna zmieni się na GNU99, która (między innymi) obsługuje anonimowe związki i struktury. To powinno być miłe ...
Quinn Taylor
2
Korzystanie for (NSUInteger i = 0, count = [myArray count]; i < count; i++)jest prawdopodobnie najbardziej wydajnym i zwięzłym rozwiązaniem dla tego podejścia.
Quinn Taylor
17

Wyniki testu i kodu źródłowego są poniżej (możesz ustawić liczbę iteracji w aplikacji). Czas jest wyrażony w milisekundach, a każdy wpis to średni wynik uruchomienia testu 5–10 razy. Stwierdziłem, że generalnie jest on dokładny do 2-3 cyfr znaczących, a potem zmienia się z każdym biegiem. Daje to margines błędu mniejszy niż 1%. Test był przeprowadzany na telefonie iPhone 3G, ponieważ była to platforma docelowa, którą byłem zainteresowany.

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

Klasy dostarczone przez Cocoa do obsługi zestawów danych (NSDictionary, NSArray, NSSet itp.) Zapewniają bardzo przyjemny interfejs do zarządzania informacjami, bez konieczności martwienia się o biurokrację w zarządzaniu pamięcią, realokacji itp. Oczywiście to jednak kosztuje . Myślę, że to całkiem oczywiste, że użycie NSArray of NSNumbers będzie wolniejsze niż C Array of float dla prostych iteracji, więc postanowiłem zrobić kilka testów, a wyniki były dość szokujące! Nie spodziewałem się, że będzie tak źle. Uwaga: testy te są przeprowadzane na telefonie iPhone 3G, ponieważ była to platforma docelowa, którą byłem zainteresowany.

W tym teście dokonuję bardzo prostego porównania wydajności losowego dostępu pomiędzy zmiennoprzecinkową C * i NSArray NSNumbers

Tworzę prostą pętlę, aby podsumować zawartość każdej tablicy i zmierzyć czas za pomocą mach_absolute_time (). NSMutableArray zajmuje średnio 400 razy dłużej !! (nie 400 procent, tylko 400 razy dłużej! to 40 000% dłużej!).

Nagłówek:

// Array_Speed_TestViewController.h

// Test prędkości macierzy

// Utworzony przez Mehmet Akten w dniu 05.02.2009.

// Copyright MSA Visuals Ltd. 2009. Wszelkie prawa zastrzeżone.

#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

Realizacja:

// Array_Speed_TestViewController.m

// Test prędkości macierzy

// Utworzony przez Mehmet Akten w dniu 05.02.2009.

// Copyright MSA Visuals Ltd. 2009. Wszelkie prawa zastrzeżone.

    #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

Od: memo.tv

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

Dostępne od wprowadzenia bloków, pozwala iterować tablicę z blokami. Jego składnia nie jest tak ładna jak szybkie wyliczanie, ale jest jedna bardzo interesująca funkcja: równoczesne wyliczanie. Jeśli kolejność wyliczania nie jest ważna, a zadania można wykonywać równolegle bez blokowania, może to zapewnić znaczne przyspieszenie w systemie wielordzeniowym. Więcej informacji na ten temat w sekcji o równoczesnym wyliczaniu.

[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

Ideą szybkiego wyliczania jest użycie szybkiego dostępu do tablicy C w celu zoptymalizowania iteracji. Nie tylko ma być szybszy niż tradycyjny NSEnumerator, ale Objective-C 2.0 zapewnia również bardzo zwięzłą składnię.

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

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

NSEnumerator

Jest to forma zewnętrznej iteracji: [myArray objectEnumerator] zwraca obiekt. Ten obiekt ma metodę nextObject, którą możemy wywoływać w pętli, aż zwróci zero

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

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

objectAtIndex: wyliczenie

Użycie pętli for, która zwiększa liczbę całkowitą i zapytanie obiektu za pomocą [myArray objectAtIndex: index] jest najbardziej podstawową formą wyliczania.

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

////////////// Od: darkdust.net

Hitendra Solanki
źródło
12

Trzy sposoby to:

        //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. Jest najszybszym sposobem na wykonanie, a 3. z autouzupełnianiem zapomnij o napisaniu koperty iteracyjnej.
Javier Calatrava Llavería
źródło
7

Dodaj eachmetodę w swoim NSArray category, będziesz jej bardzo potrzebować

Kod pobrany z ObjectiveSugar

- (void)each:(void (^)(id object))block {
    [self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        block(obj);
    }];
}
onmyway133
źródło
5

Oto jak zadeklarować tablicę ciągów i iterować nad nimi:

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

for (int i = 0; i < [langs count]; i++) {
  NSString *lang = (NSString*) [langs objectAtIndex:i];
  NSLog(@"%@, ",lang);
}
oabarca
źródło
0

Dla 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
źródło
-1

Zrób to :-

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

źródło