Szukam standardowego idiomu do iteracji po tablicy NSArray. Mój kod musi być odpowiedni dla systemu OS X 10.4+.
Szukam standardowego idiomu do iteracji po tablicy NSArray. Mój kod musi być odpowiedni dla systemu OS X 10.4+.
Odpowiedzi:
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 NSFastEnumeration
protokoł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 NSEnumerator
implementacja -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ć NSEnumerationConcurrent
i / lub NSEnumerationReverse
jako argument opcji.
Standardowym idiomem dla wersji wcześniejszych niż 10.5 jest użycie NSEnumerator
pę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 -objectEnumerator
bardzo łatwo przechodzisz do innej kolekcji zliczalnej (takiej jak NSSet
, klucze w NSDictionary
itd.), A nawet przełączasz się, -reverseObjectEnumerator
aby wyliczyć tablicę wstecz, bez żadnych innych zmian kodu. Jeśli kod iteracji jest w metodzie, możesz nawet podać dowolny, NSEnumerator
a 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ą, NSEnumerator
przed 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! :-)
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?
for
pętli:for(;;) { id object = [ e nextObject ] ; if ( !e ) { break ; } ... your loop operation ... }
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
}
for (NSUInteger i = 0, count = [myArray count]; i < count; i++)
jest prawdopodobnie najbardziej wydajnym i zwięzłym rozwiązaniem dla tego podejścia.
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
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
}];
Dodaj each
metodę 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);
}];
}
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
}
Zrób to :-
for (id object in array)
{
// statement
}