如何在NSArray上进行迭代?

467
我正在寻找在NSArray上进行迭代的标准习语。我的代码需要适用于OS X 10.4+。
8个回答

689

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 或之前

在 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(至少是由苹果代码提供的那些)在枚举更多对象的情况下保留它正在枚举的集合,因此您不必担心自动释放的对象将存在多长时间。
也许一个NSEnumerator(或者快速枚举)最大的好处就是保护你免受可变集合(array或其他)在你枚举时在没有你知道的情况下发生改变。如果你通过索引访问对象,你可能会遇到奇怪的异常或者off-by-one错误(通常是在问题发生之后很长时间),这些错误非常难以调试。使用标准惯用法进行枚举具有“快速失败”的行为,因此(由不正确的代码引起的)问题将在修改发生后尝试访问下一个对象时立即显示出来。随着程序变得越来越复杂和多线程化,甚至依赖于第三方代码可能进行修改的东西,脆弱的枚举代码变得越来越棘手。封装和抽象 FTW! :-)

29
注意:大多数编译器会对 "while (object = [e nextObject])" 提出警告。在这种情况下,您实际上是要使用 = 而不是 ==。为了消除警告,您可以添加额外的括号:"while ((object = [e nextObject]))"。 注意:很多编译器会对 "while (object = [e nextObject])" 发出警告。但事实上,在这种情况下,您确实需要使用等于号(=)而不是相等于号(==)。如果想消除这些警告,可以加上额外的括号:"while ((object = [e nextObject]))"。 - Adam Rosenfield
1
你需要一个单独的计数器,但如果可以使用块枚举(其中包括索引),我建议考虑使用它。 - Quinn Taylor
我很奇怪,但我使用类似这样的for循环:for(;;) { id object = [ e nextObject ] ; if ( !e ) { break ; } ... 你的循环操作 ... } - nielsbot
如何使用for (NSArray *arr in dictionary)语法向后迭代?意思是我想首先在最后一个对象上运行for循环。 - Ashwin G
想点赞这个回答,但不想把666变成667。如果有人愿意修改的话,我会再回来点赞的。 - Hibbem
显示剩余3条评论

129

针对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
}

2
在第一个示例中,您甚至不必在循环外声明int。这同样有效,并且很好地限定了变量的范围,因此如果需要,稍后可以重复使用它:for(int i = 0; i <[myArray count]; i ++)...但是,请注意,每次通过数组调用-count可能会取消使用-objectAtIndex的好处。 - Quinn Taylor
1
实际上,如果您正在枚举可变集合,则在每次循环中检查计数是技术上更正确的。我澄清了我的答案,以解释使用NSEnumerator或NSFastEnumeration可以保护数组免受并发突变的影响。 - Quinn Taylor
for(int i = 0; ...) 结构是 C 语言方言(据信是C99),我自己用过,但我不确定它是否是XCode的默认设置。 - diederikh
C99是默认的Xcode 3.1.x版本,但在未来的某个时候,它将更改为GNU99,其中(除其他外)支持匿名联合和结构。那应该很不错... - Quinn Taylor
2
使用 for (NSUInteger i = 0, count = [myArray count]; i < count; i++) 可能是这种方法中最高效和简洁的。 - Quinn Taylor

17
三种方法如下:
        //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. 借助自动完成功能,无需编写迭代包装。

16

测试结果和源代码如下(您可以在应用程序中设置迭代次数)。时间以毫秒为单位,每个条目都是运行测试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 Float数组进行简单迭代慢,因此我决定进行一些测试,结果令人震惊!我没有预料到会这么糟糕。注意:这些测试是在iPhone 3G上进行的,因为这是我感兴趣的目标平台。

在这个测试中,我对比了使用C float*和NSArray of NSNumbers进行非常简单的随机访问性能。

我创建了一个简单的循环来计算每个数组的内容,并使用mach_absolute_time()来计时。NSMutableArray平均需要花费400倍的时间!!(不是400%,而是400倍!相当于增加了40,000%)。

头文件:

// Array_Speed_TestViewController.h

// Array Speed Test

// Created by Mehmet Akten on 05/02/2009.

// Copyright MSA Visuals Ltd. 2009. All rights reserved.

#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年05月02日。

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

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

使用for循环递增整数并使用[myArray objectAtIndex:index]查询对象是最基本的枚举方式。

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

注:枚举是指按照一定顺序,逐个访问集合中的每个元素的过程。

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

////////////// 来源:darkdust.net


8
这是如何声明一个字符串数组并对其进行迭代的方法:

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

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

只是一点提示:类似上面的 C 风格 for 语句已在 Swift 3 及更高版本中被移除。 - Ram Patra

7
在您的NSArray分类中添加each方法,因为您将需要它很多次。
代码取自ObjectiveSugar
- (void)each:(void (^)(id object))block {
    [self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        block(obj);
    }];
}

0

请执行以下操作:

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

0

对于Swift

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
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接