Как перебирать NSArray?

Я ищу стандартную идиому для итерации по NSArray. Мой код должен быть подходящим для OS X 10.4 +.

Ответ 1

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

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

Эта конструкция используется для перечисления объектов в коллекции, которая соответствует [NSFastEnumeration protocol] (Cocoa Reference). Этот подход имеет преимущество в скорости, поскольку он хранит указатели на несколько объектов (полученных посредством одного вызова метода) в буфере и выполняет итерацию через них, продвигаясь через буфер, используя арифметику указателя. Это намного быстрее, чем вызов -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 в качестве аргумента options.


10.4 или ранее

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

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

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

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

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


Ответ 2

Для 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
}

Ответ 3

Ниже приведены результаты теста и исходного кода (вы можете установить количество итераций в приложении). Время в миллисекундах, и каждая запись является средним результатом запуска теста 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 раз больше!), что на 40 000% больше!).

Заголовок:

//Array_Speed_TestViewController.h

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

//Создано Mehmet Akten 05/02/2009.

//Авторское право 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

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

//Создано Mehmet Akten 05/02/2009.

//Авторское право 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, который мы можем вызвать в цикле, пока он не вернет nil

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

Ответ 4

Три способа:

        //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. с автозапуском забыть о записи итерационного конверта.

Ответ 5

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

Код, взятый из ObjectiveSugar

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

Ответ 6

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

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

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

Ответ 7

Сделайте это: -

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