Как мне перебрать NSArray?

Ответы:

668

Обычно предпочтительный код для 10.5 + / iOS.

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

Эта конструкция используется для перечисления объектов в коллекции, которая соответствует NSFastEnumerationпротоколу. Этот подход имеет преимущество в скорости, потому что он хранит указатели на несколько объектов (полученных с помощью одного вызова метода) в буфере и выполняет итерацию по ним, продвигаясь через буфер с использованием арифметики указателей. Это намного быстрее, чем звонить -objectAtIndex:каждый раз через цикл.

Стоит также отметить, что, хотя технически вы можете использовать цикл for-in для пошагового выполнения NSEnumerator, я обнаружил, что это сводит на нет практически все преимущества скорости быстрого перечисления. Причина в том, что NSEnumeratorреализация по умолчанию -countByEnumeratingWithState:objects:count:помещает только один объект в буфер при каждом вызове.

Я сообщил об этом в radar://6296108(Быстрое перечисление NSEnumerators вяло), но оно было возвращено как Не исправлено. Причина в том, что быстрое перечисление предварительно выбирает группу объектов, и если вы хотите перечислить только определенную точку в перечислителе (например, до тех пор, пока не будет найден конкретный объект или не выполнено условие) и использовать тот же перечислитель после разрыва цикла, часто бывает, что несколько объектов будут пропущены.

Если вы программируете для OS X 10.6 / iOS 4.0 и выше, у вас также есть возможность использовать блочные API для перечисления массивов и других коллекций:

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

Вы также можете использовать -enumerateObjectsWithOptions:usingBlock:и передавать NSEnumerationConcurrentи / или NSEnumerationReverseв качестве аргумента параметров.


10.4 или ранее

Стандартная идиома для pre-10.5 заключается в использовании NSEnumeratorцикла while и while, например:

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

Я рекомендую держать это простым. Привязка себя к типу массива негибка, и предполагаемое увеличение скорости использования -objectAtIndex:незначительно для улучшения с быстрым перечислением на 10,5+ в любом случае. (Быстрое перечисление фактически использует арифметику указателей на базовой структуре данных и устраняет большую часть накладных расходов при вызове метода.) Преждевременная оптимизация никогда не бывает хорошей идеей - она ​​приводит к более сложному коду для решения проблемы, которая в любом случае не является вашим узким местом.

При использовании -objectEnumeratorвы очень легко переключаетесь на другую перечисляемую коллекцию (например NSSet, ключи NSDictionary, и т. Д.) Или даже переключаетесь -reverseObjectEnumeratorна перечисление массива в обратном порядке, и все это без каких-либо других изменений кода. Если код итерации находится в методе, вы можете даже передать любой, NSEnumeratorи коду не нужно даже заботиться о том, что он выполняет. Кроме того, NSEnumerator(по крайней мере, предоставляемые кодом Apple) сохраняет коллекцию, которую она перечисляет, до тех пор, пока существует больше объектов, поэтому вам не нужно беспокоиться о том, как долго будет существовать автоматически выпущенный объект.

Возможно, самая большая вещь, от которой NSEnumerator(или быстрое перечисление) защищает вас, - это изменчивая коллекция (массив или другое), изменяемая под вами без вашего ведома, пока вы ее перечисляете. Если вы обращаетесь к объектам по индексу, вы можете столкнуться со странными исключениями или ошибками «один за другим» (часто спустя много времени после возникновения проблемы), которые могут быть ужасающими для отладки. Перечисление с использованием одной из стандартных идиом имеет поведение «fast-fast», поэтому проблема (вызванная неправильным кодом) проявится сразу же, когда вы попытаетесь получить доступ к следующему объекту после того, как произошла мутация. Поскольку программы становятся более сложными и многопоточными или даже зависят от того, что может изменить сторонний код, хрупкий код перечисления становится все более проблематичным. Инкапсуляция и абстракция FTW! :-)


Куинн Тейлор
источник
28
Примечание: большинство компиляторов выдают предупреждение о "while (object = [e nextObject])". В этом случае вы действительно хотите использовать = вместо ==. Чтобы подавить предупреждения, вы можете добавить дополнительные скобки: "while ((object = [e nextObject]))".
Адам Розенфилд
Еще одна вещь, которую нужно помнить с NSEnumerator, это то, что он будет использовать больше памяти (создает копию массива), чем подход objectWithIndex.
diederikh
@QuinnTaylor Использование for (id object in array), есть ли способ также определить текущий индекс объекта в массиве, или нужно включить отдельный счетчик?
Coderama
1
Вам понадобится отдельный счетчик, но я бы посоветовал рассмотреть перечисление на основе блоков (которое включает в себя индекс), если оно доступно для вас.
Куинн Тейлор
Я странный, но я использую такой forцикл:for(;;) { id object = [ e nextObject ] ; if ( !e ) { break ; } ... your loop operation ... }
nielsbot
125

Для OS X 10.4.x и предыдущих версий:

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

Для OS X 10.5.x (или iPhone) и выше:

for (id myArrayElement in myArray) {
   ...do something useful with myArrayElement
}
diederikh
источник
2
В первом примере вам даже не нужно объявлять int вне цикла. Это работает так же хорошо и хорошо определяет переменную, так что вы можете использовать ее позже, если это необходимо: for (int i = 0; i <[myArray count]; i ++) ... Но также следует помнить, что каждый раз вызывая -count через массив можно отменить преимущество использования -objectAtIndex:
Куинн Тейлор
1
И на самом деле, если вы перечисляете изменяемую коллекцию, технически правильнее проверять счетчик каждый раз в цикле. Я разъяснил свой ответ, объяснив, что использование NSEnumerator или NSFastEnumeration может защитить от одновременных мутаций массива.
Куинн Тейлор
Конструкция for (int i = 0; ...) - это диалект языка Си (верят C99), которым я сам пользуюсь, но я не был уверен, что это XCode по умолчанию.
diederikh
C99 - это значение по умолчанию через Xcode 3.1.x - в какой-то момент в будущем значение по умолчанию изменится на GNU99, который (среди прочего) поддерживает анонимные объединения и структуры. Это должно быть хорошо ...
Куинн Тейлор
2
Использование for (NSUInteger i = 0, count = [myArray count]; i < count; i++), вероятно, является наиболее эффективным и лаконичным для такого подхода.
Куинн Тейлор
17

Результаты теста и исходный код приведены ниже (вы можете установить количество итераций в приложении). Время указывается в миллисекундах, и каждая запись является средним результатом выполнения теста 5-10 раз. Я обнаружил, что, как правило, с точностью до 2-3 значащих цифр, и после этого он будет меняться с каждым прогоном. Это дает погрешность менее 1%. Тест проводился на iPhone 3G, так как это целевая платформа, которая меня интересовала.

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

Классы, предоставляемые Cocoa для обработки наборов данных (NSDictionary, NSArray, NSSet и т. Д.), Предоставляют очень удобный интерфейс для управления информацией, не беспокоясь о бюрократии управления памятью, перераспределения и т. Д. Конечно, это обходится дорого, хотя , Я думаю, совершенно очевидно, что использование NSArray из NSNumbers будет медленнее, чем использование массива с плавающей точкой C для простых итераций, поэтому я решил провести несколько тестов, и результаты были довольно шокирующими! Я не ожидал, что это будет так плохо. Примечание: эти тесты проводятся на iPhone 3G, поскольку это целевая платформа, которая меня заинтересовала.

В этом тесте я делаю очень простое сравнение производительности произвольного доступа между C float * и NSArray из NSNumbers

Я создаю простой цикл, чтобы суммировать содержимое каждого массива и рассчитывать время, используя mach_absolute_time (). NSMutableArray занимает в среднем в 400 раз больше времени !! (не на 400 процентов, а в 400 раз дольше! это на 40000% дольше!).

Заголовок:

// Array_Speed_TestViewController.h

// Тест скорости массива

// Создано Мехмет Актен 05/02/2009.

// Copyright MSA Visuals Ltd. 2009. Все права защищены.

#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

Реализация:

// Array_Speed_TestViewController.m

// Тест скорости массива

// Создано Мехмет Актен 05/02/2009.

// Copyright MSA Visuals Ltd. 2009. Все права защищены.

    #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

От: memo.tv

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

Доступно с момента появления блоков, это позволяет перебирать массив с блоками. Его синтаксис не так хорош, как быстрое перечисление, но есть одна очень интересная особенность: параллельное перечисление. Если порядок перечисления не важен и задания могут выполняться параллельно без блокировки, это может обеспечить значительное ускорение в многоядерной системе. Подробнее об этом в разделе одновременного перечисления.

[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

Идея быстрого перечисления заключается в использовании быстрого доступа к массиву C для оптимизации итерации. Мало того, что он должен быть быстрее, чем традиционный NSEnumerator, но Objective-C 2.0 также обеспечивает очень краткий синтаксис.

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

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

NSEnumerator

Это форма внешней итерации: [myArray objectEnumerator] возвращает объект. Этот объект имеет метод nextObject, который мы можем вызывать в цикле, пока он не вернет ноль

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

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

objectAtIndex: перечисление

Использование цикла for, который увеличивает целое число, и запрос объекта с помощью [myArray objectAtIndex: index] является наиболее простой формой перечисления.

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

////////////// От: darkdust.net

Хитендра Соланки
источник
12

Три способа:

        //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. Это самый быстрый способ выполнения, и 3. с автозаполнением забудьте о написании конверта итерации.
Хавьер Калатрава Ллаверия
источник
7

Добавьте eachметод в ваш NSArray category, вам это понадобится много

Код взят из ObjectiveSugar

- (void)each:(void (^)(id object))block {
    [self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        block(obj);
    }];
}
onmyway133
источник
5

Вот как вы объявляете массив строк и перебираете их:

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

for (int i = 0; i < [langs count]; i++) {
  NSString *lang = (NSString*) [langs objectAtIndex:i];
  NSLog(@"%@, ",lang);
}
oabarca
источник
0

Для Свифта

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
источник
-1

Сделай это :-

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

источник