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

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

вопрос задан 14.06.2009
Steve McLeod
32167 репутация

7 ответов


  • 631 рейтинг

    Обычно предпочтительный код для 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, например:

    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! :-)


    ответ дан Quinn Taylor, с репутацией 40255, 14.06.2009
  • 121 рейтинг

    Для OS X 10. 4. х и предыдущая:

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

    Для OS X 10. 5. х (или iPhone) и далее:

    for (id myArrayElement in myArray) {
       ...do something useful with myArrayElement
    }
    
    ответ дан diederikh, с репутацией 23994, 14.06.2009
  • 14 рейтинг

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

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

    // Создано Мехметом Актеном 05.02.2009.

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

    #import 
    
    @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. м

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

    // Создано Мехметом Актеном 05.02.2009.

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

        #import "Array_Speed_TestViewController.h"
        #include 
        #include 
    
     @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

    От: памятка. телевизор

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

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

    [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]];
    }
    

    ////////////// От: темная пыль. нетто

    ответ дан Hitendra Hckr, с репутацией 3012, 11.05.2014
  • 8 рейтинг

    Три способа:

            //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. с автозаполнением забудьте о написании конверта итерации.
    ответ дан Javier Calatrava Llavería, с репутацией 5567, 31.08.2015
  • 6 рейтинг

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

    Код взят из ObjectiveSugar

    - (void)each:(void (^)(id object))block {
        [self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
            block(obj);
        }];
    }
    
    ответ дан onmyway133, с репутацией 22956, 1.01.2015
  • 1 рейтинг

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

    NSArray *langs = @[@"es", @"en", @"pt", @"it", @"fr"];
    
    for (int i = 0; i < [langs count]; i++) {
      NSString *lang = (NSString*) [langs objectAtIndex:i];
      NSLog(@"%@, ",lang);
    }
    
    ответ дан user2070775, с репутацией 5815, 20.07.2016
  • 0 рейтинг

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

    for (id object in array) 
    {
            // statement
    }
    
    ответ дан does_not_exist, с репутацией , 1.06.2017