Як я повторюю NSArray?


Відповіді:


667

Загальновигідний код для 10.5 + / iOS.

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

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

Варто також зазначити, що, хоча ви технічно можете використовувати цикл для входу для переходу через NSEnumerator, я виявив, що це зводить нанівець практично всі переваги швидкості швидкого перерахування. Причина полягає в тому, що NSEnumeratorреалізація за замовчуванням -countByEnumeratingWithState:objects:count:розміщує лише один об'єкт у буфері під час кожного виклику.

Я повідомив про це в radar://6296108(Швидке перерахування NSEnumerators є млявим), але це було повернуто як Not To Be Fixed. Причина полягає в тому, що швидке перерахування попередньо отримує групу об'єктів, і якщо ви хочете перерахувати лише до заданої точки в перерахунку (наприклад, до тих пір, поки певний об'єкт не буде знайдений або не буде виконано умову), і використовуйте той же перелік після виривання циклу, часто буває так, що кілька об'єктів будуть пропущені.

Якщо ви кодуєте для 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 або раніше

Стандартна ідіома для до-10.5 полягає у використанні NSEnumeratorциклу та час, як-от так:

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

Я рекомендую зберігати його просто. Прив’язання себе до типу масиву не є гнучким, і передбачуване збільшення швидкості використання не -objectAtIndex:має значного значення для покращення з швидким перерахуванням на 10.5+. (Швидке перерахування насправді використовує арифметику вказівника на базовій структурі даних і видаляє більшу частину накладних викликів методу.) Передчасна оптимізація ніколи не є хорошою ідеєю - це призводить до того, що код Messier вирішить проблему, яка все одно не є вашим вузьким місцем.

Під час використання -objectEnumeratorви дуже легко переходите на іншу численну колекцію (наприклад, anNSSet , клавіші, клавіші NSDictionaryтощо), або навіть переходите -reverseObjectEnumeratorна перерахування масиву назад, і все це не змінюється. Якщо код ітерації є методом, ви можете навіть передати будь-який, NSEnumeratorі код навіть не повинен дбати про те, що це ітерація. Крім того, NSEnumerator(принаймні ті, що надаються кодом Apple) зберігається колекція, яку він перераховує, поки існує більше об'єктів, тож вам не доведеться турбуватися про те, як довго буде існувати автоматично випущений об’єкт.

Мабуть, найбільше, що вас NSEnumerator(або швидке перерахування) захищає від того, щоб змінити під собою змінну колекцію (масив чи інше) без вашого відома під час її перерахування. Якщо ви звертаєтесь до об'єктів за індексом, ви можете зіткнутися з дивними винятками або однозначними помилками (часто довгими після виникнення проблеми), які можуть бути жахливими для налагодження. Перерахування з використанням однієї зі стандартних ідіом має поведінку "невдало", тому проблема (викликана неправильним кодом) проявиться негайно при спробі отримати доступ до наступного об'єкта після мутації. Оскільки програми стають більш складними та багатопотоковими або навіть залежать від чогось, що сторонній код може змінити, неміцний код перерахування стає все більш проблематичним. Інкапсуляція та абстракція FTW! :-)



28
Примітка: більшість компіляторів подадуть попередження про "while (object = [e nextObject])". У цьому випадку ви насправді НЕ маєте на увазі використовувати = замість ==. Щоб придушити попередження, ви можете додати додаткові дужки: "while ((object = [e nextObject]))".
Адам Розенфілд

Інша річ, яку слід пам’ятати з NSEnumerator, це те, що він буде використовувати більше пам'яті (робить копію масиву), ніж підхід objectWithIndex.
dieerikh

@QuinnTaylor Використовуючи for (id object in array), це тере спосіб також визначити поточний індекс об'єктів у масиві, чи потрібно включити окремий лічильник?
Coderama

1
Вам знадобиться окремий лічильник, але я б запропонував розглянути можливість перерахування на основі блоку (який включає індекс), якщо він доступний для вас.
Квінн Тейлор

Я дивний, але я використовую forцикл, як це:for(;;) { id object = [ e nextObject ] ; if ( !e ) { break ; } ... your loop operation ... }
nielsbot

125

Для ОС X 10.4.x та попередніх версій:

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

Для ОС X 10.5.x (або iPhone) та вище:

for (id myArrayElement in myArray) {
   ...do something useful with myArrayElement
}

2
У першому прикладі вам навіть не потрібно оголошувати int поза циклом. Це працює так само добре, і міняє зміну чудово, тому ви можете використовувати її пізніше за потреби: for (int i = 0; i <[myArray count]; i ++) ... Але також майте на увазі, що виклик-рахунок щоразу через масив можна скасувати перевагу використання -objectAtIndex:
Квінн Тейлор

1
І насправді, якщо ви перераховуєте колекцію, що змінюється, технічно правильніше перевіряти кількість кожного разу через цикл. Я пояснив свою відповідь, щоб пояснити, що використання NSEnumerator або NSFastEnumeration може захистити від паралельних мутацій масиву.
Квінн Тейлор

Форма for (int i = 0; ...) - це діалект мови мови C (вважається C99), який я сам використовую, але не був впевнений, що це за замовчуванням XCode.
dieerikh

С99 за замовчуванням перетворюється на 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, оскільки це була цільова платформа, яка мене зацікавила.

У цьому тесті я роблю дуже просте порівняння продуктивності з випадковим доступом між поплавком С * та NSArray з NSNumbers

Я створюю простий цикл, щоб підсумовувати вміст кожного масиву та обробляти їх час, використовуючи mach_absolute_time (). NSMutableArray займає в середньому в 400 разів довше !! (не 400 відсотків, лише 400 разів довше! ось на 40 000% довше!).

Заголовок:

// Array_Speed_TestViewController.h

// Тест швидкості масиву

// Створено Мехметом Актен 02.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

// Тест швидкості масиву

// Створено Мехметом Актен 02.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

Від: пам’ятко

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

Доступний з моменту введення блоків, це дозволяє повторити масив з блоками. Його синтаксис не такий приємний, як швидке перерахування, але є одна дуже цікава особливість: паралельне перерахування. Якщо порядок перерахування не важливий і завдання можна виконувати паралельно без блокування, це може забезпечити значне прискорення роботи в багатоядерній системі. Детальніше про це у розділі паралельного перерахування.

[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

Ідея швидкого перерахування полягає у використанні швидкого доступу до масиву С для оптимізації ітерації. Мало того, що він повинен бути швидшим, ніж традиційний 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);
    }];
}

5

Ось як ви оголошуєте масив рядків і повторюєте їх:

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

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

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
}

-1

Зробити це :-

for (id object in array) 
{
        // statement
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.