Jak mam to zrobić nad NSArray?

Szukam standardowego idiomu do iteracji nad NSArray. Mój kod musi być odpowiedni dla OS X 10.4+.

Author: Quinn Taylor, 2009-06-14

7 answers

Ogólnie preferowany kod dla 10.5+ / iOS.

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

Ten konstrukt służy do wyliczania obiektów w zbiorze, który odpowiada NSFastEnumeration protokół. To podejście ma przewagę szybkości, ponieważ przechowuje wskaźniki do kilku obiektów (uzyskiwanych za pomocą jednego wywołania metody) w buforze i iteruje przez nie, przechodząc przez bufor za pomocą arytmetyki wskaźników. To jest dużo szybciej niż wywołanie -objectAtIndex: za każdym razem przez pętlę.

To także warto zauważyć, że chociaż technicznie można użyć pętli for-in, aby przejść przez NSEnumerator, odkryłem, że to unieważnia praktycznie całą przewagę prędkości szybkiego wyliczania. Powodem jest to, że domyślna implementacja NSEnumerator -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 naprawy. Powodem jest to, że szybkie wyliczenie wstępnie pobiera grupę obiektów, a jeśli chcesz wyliczyć tylko do określonego punktu w wyliczeniu (np. do momentu znalezienia konkretnego obiektu lub spełnienia warunku) i użyć tego samego wyliczenia po wyrwaniu się z pętli, często zdarza się, że kilka obiektów zostanie pominiętych.

Jeśli kodujesz dla OS X 10.6 / iOS 4.0 i nowszych, możesz również użyć interfejsów API opartych na blokach do wyliczania tablic i innych kolekcji:]}
[array enumerateObjectsUsingBlock:^(id object, NSUInteger idx, BOOL *stop) {
    // do something with object
}];

Możesz również użyć -enumerateObjectsWithOptions:usingBlock: i przekazać NSEnumerationConcurrent i / lub NSEnumerationReverse jako argument options.


10.4 lub wcześniej

Standardowym idiomem dla pre-10.5 jest użycie NSEnumerator i pętli while, W ten sposób:
NSEnumerator *e = [array objectEnumerator];
id object;
while (object = [e nextObject]) {
  // do something with object
}

Zalecam utrzymanie tego w prostocie. Przywiązanie się do typu tablicy jest nieelastyczne, a rzekomy wzrost prędkości używania -objectAtIndex: jest nieistotny dla poprawy z szybkim wyliczaniem na 10.5+ w każdym razie. (Fast enumeration faktycznie używa arytmetyki wskaźników na podstawowej strukturze danych i usuwa większość narzutów wywołania metody.) Przedwczesna optymalizacja nigdy nie jest dobrym pomysłem - powoduje, że kod Messiera rozwiązuje problem, który i tak nie jest Twoim wąskim gardłem.

Podczas stosowania -objectEnumerator, bardzo łatwo można zmienić na inny zbiór liczbowy (np. NSSet, Klucze w NSDictionary itp.), a nawet przełączyć się na -reverseObjectEnumerator aby wyliczyć tablicę wstecz, wszystko bez innych zmian kodu. Jeśli kod iteracji jest w metodzie, możesz nawet przekazać dowolną NSEnumerator, A kod nie musi się nawet przejmować Co jest iteracyjne. Co więcej, NSEnumerator (przynajmniej te dostarczone przez kod Apple) zachowuje kolekcję, którą wylicza, tak długo, jak jest więcej obiektów, więc nie musisz się martwić o to, jak długo będzie istniał obiekt z autorelementem.

Być może największą rzeczą NSEnumerator (lub szybkie wyliczenie) chroni Cię przed jest posiadanie mutowalnego zbioru (tablicy lub w inny sposób) zmieniającego się pod tobą bez Twojej wiedzy podczas wyliczania go. Jeśli uzyskasz dostęp do obiektów przez indeks, możesz napotkać dziwne wyjątki lub błędy off-by-one (często długo po wystąpieniu problemu), które mogą być przerażające do debugowania. Wyliczanie przy użyciu jednego ze standardowych idiomów ma zachowanie "Fail-fast" , więc problem (spowodowany nieprawidłowym kodem) pojawi 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 innych firm, kruchy kod wyliczania staje się coraz bardziej problematyczne. Enkapsulacja I abstrakcja FTW! :-)


 632
Author: Quinn Taylor,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-09-20 21:48:04

Dla OS X 10.4.x i poprzedni:

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

Dla OS X 10.5.x (lub iPhone) i dalej:

for (id myArrayElement in myArray) {
   ...do something useful with myArrayElement
}
 121
Author: diederikh,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-03-02 15:17:46

Wyniki testu i Kod źródłowy są poniżej (możesz ustawić liczbę iteracji w aplikacji). Czas jest w milisekundach, a każdy wpis jest średnim wynikiem uruchomienia testu 5-10 razy. Odkryłem, że ogólnie jest dokładny do 2-3 znaczących cyfr, a potem będzie się różnić z każdym uruchomieniem. Daje to margines błędu poniżej 1%. Test działał na iPhone 3G, ponieważ jest 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 przewidziane przez Cocoa dla obsługa zestawów danych (NSDictionary, NSArray, NSSet itp.) zapewniają bardzo ładny interfejs do zarządzania informacjami, bez konieczności martwienia się o biurokrację zarządzania pamięcią, realokacji itp. Oczywiście jest to jednak kosztowne. Myślę, że to dość oczywiste, że powiedzmy używanie NSArray z NSNumbers będzie wolniejsze niż tablica C pływaków 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: te testy są prowadzone na iPhone 3G, ponieważ jest to platforma docelowa, którą byłem zainteresowany.

W tym teście robię bardzo proste porównanie wydajności dostępu losowego między C float* i NSArray NSNumbers

Tworzę prostą pętlę do podsumowania zawartości każdej tablicy i zmierzenia jej za pomocą mach_absolute_time (). Nsmutablearray trwa ś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 Tablicy

// Created by Mehmet Akten on 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 Tablicy

// Created by Mehmet Akten on 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 to na iterację tablicy z blokami. Jego składnia nie jest tak miła jak szybkie wyliczanie, ale jest jedna bardzo interesująca cecha: równoczesne wyliczanie. Jeśli kolejność wyliczeń nie jest ważna, a zadania mogą być wykonywane równolegle bez blokowania, może to zapewnić znaczne przyspieszenie w systemie wielordzeniowym. Więcej o tym w podsumowaniu sekcja.

[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 optymalizacji 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 iteracji zewnętrznej: [myarray objectEnumerator] zwraca obiekt. Obiekt ten posiada metodę nextObject, którą możemy wywołać w pętli dopóki nie zwróci nil

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

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

ObjectAtIndex: enumeration

Używanie pętli for, która zwiększa liczbę całkowitą i odpytywanie obiektu za pomocą [myarray objectAtIndex: index] jest najbardziej podstawową formą wyliczenia.

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

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

 14
Author: Hitendra Hckr,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-05-11 15:30:38

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 najszybszą drogą w wykonaniu, a 3. z autocompletion zapomnij o pisaniu koperty iteracyjnej.
 8
Author: Javier Calatrava Llavería,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-31 07:11:20

Dodaj each metodę w swoim NSArray category, będziesz jej bardzo potrzebował

Kod zaczerpnięty z obiektów

- (void)each:(void (^)(id object))block {
    [self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        block(obj);
    }];
}
 6
Author: onmyway133,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-01-01 17:14:21

Oto jak deklarujesz tablicę łańcuchów i powtarzasz 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);
}
 1
Author: user2070775,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-20 16:54:58

Zrób to:-

for (id object in array) 
{
        // statement
}
 0
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-06-01 07:17:59