462

我正在寻找迭代 NSArray 的标准习惯用法。我的代码需要适合 OS X 10.4+。

4

8 回答 8

680

10.5+/iOS 的普遍首选代码。

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

此构造用于枚举符合NSFastEnumeration协议的集合中的对象。这种方法具有速度优势,因为它将指向多个对象(通过单个方法调用获得)的指针存储在缓冲区中,并通过使用指针算法在缓冲区中前进来迭代它们。这比每次通过循环调用快得多。-objectAtIndex:

还值得注意的是,虽然您在技术上可以使用 for-in 循环来单步执行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和一个 while 循环,如下所示:

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

我建议保持简单。将自己绑定到数组类型是不灵活的,并且据称使用的速度增加-objectAtIndex:对于 10.5+ 上的快速枚举的改进是微不足道的。(快速枚举实际上在底层数据结构上使用了指针算法,并消除了大部分方法调用开销。)过早的优化从来都不是一个好主意——它会导致解决问题的代码更加混乱,而这无论如何都不是你的瓶颈。

使用 时-objectEnumerator,您很容易更改为另一个可枚举集合(如 an NSSet、 an 中的键NSDictionary等),甚至切换到-reverseObjectEnumerator向后枚举数组,所有这些都无需更改其他代码。如果迭代代码在一个方法中,您甚至可以传入任何方法,NSEnumerator代码甚至不必关心它正在迭代什么。此外,NSEnumerator只要有更多对象,一个(至少由 Apple 代码提供的那些)保留它正在枚举的集合,因此您不必担心自动释放的对象将存在多长时间。

也许一个(或快速枚举)保护您免受最大的事情是在您枚举它时在您不知情的情况NSEnumerator下在您下面更改一个可变集合(数组或其他) 。如果您按索引访问对象,您可能会遇到奇怪的异常或一个错误(通常在问题发生很久之后),这对于调试来说可能是可怕的。使用标准惯用语之一进行枚举具有“快速失败”行为,因此当您在发生突变后尝试访问下一个对象时,问题(由不正确的代码引起)将立即显现出来。随着程序变得越来越复杂和多线程,甚至依赖于第三方代码可能修改的东西,脆弱的枚举代码变得越来越成问题。封装和抽象 FTW!:-)


于 2009-06-14T14:27:45.640 回答
128

对于 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
}
于 2009-06-14T14:25:37.890 回答
16

测试结果和源码如下(可以在app中设置迭代次数)。时间以毫秒为单位,每个条目是运行测试 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 等)为管理信息提供了一个非常好的接口,而不必担心内存管理、重新分配等官僚作风。当然,这确实是有代价的. 我认为很明显,使用 NSNumbers 的 NSArray 将比用于简单迭代的 C 浮点数组慢,所以我决定做一些测试,结果非常令人震惊!我没想到会这么糟糕。注意:这些测试是在 iPhone 3G 上进行的,因为这是我感兴趣的目标平台。

在这个测试中,我在 C float* 和 NSNumber 的 NSArray 之间进行了非常简单的随机访问性能比较

我创建了一个简单的循环来总结每个数组的内容并使用 mach_absolute_time() 对它们进行计时。NSMutableArray 平均耗时 400 倍!(不是 400%,只是长了 400 倍!那是长了 40,000%!)。

标题:

// Array_Speed_TestViewController.h

// 阵列速度测试

// 由 Mehmet Akten 于 2009 年 5 月 2 日创建。

// 版权所有 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 于 2009 年 5 月 2 日创建。

// 版权所有 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];
}

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

NSE 分子

这是外部迭代的一种形式:[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

于 2014-05-11T15:30:38.133 回答
16

三种方式是:

        //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. 使用自动完成忘记编写迭代信封。
于 2015-08-31T07:11:20.393 回答
7

在你的中添加each方法NSArray category,你会非常需要它

代码取自ObjectiveSugar

- (void)each:(void (^)(id object))block {
    [self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        block(obj);
    }];
}
于 2015-01-01T17:14:21.037 回答
7

以下是您如何声明一个字符串数组并对其进行迭代:

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

for (int i = 0; i < [langs count]; i++) {
  NSString *lang = (NSString*) [langs objectAtIndex:i];
  NSLog(@"%@, ",lang);
}
于 2016-07-20T16:54:58.323 回答
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
}
于 2019-02-08T07:23:23.390 回答
-1

做这个 :-

for (id object in array) 
{
        // statement
}
于 2017-06-01T07:17:59.823 回答