我如何迭代一个NSArray?

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

364988 次浏览

适用于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
}

10.5+/iOS通常首选的代码。

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

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

同样值得注意的是,虽然从技术上讲可以使用for-in循环遍历NSEnumerator,但我发现这实际上抵消了快速枚举的所有速度优势。原因是-countByEnumeratingWithState:objects:count:的默认NSEnumerator实现在每次调用时只在缓冲区中放置一个对象。

我在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时,你很容易更改为另一个可枚举集合(如NSSetNSDictionary中的键等),甚至切换到-reverseObjectEnumerator来向后枚举数组,所有这些都不需要其他代码更改。如果迭代代码在一个方法中,你甚至可以传入任何NSEnumerator,代码甚至不必关心它正在迭代的什么。此外,NSEnumerator(至少由Apple代码提供的那些)只要有更多的对象,就会保留它所枚举的集合,所以你不必担心自动释放的对象将存在多长时间。

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


测试结果和源代码如下(您可以在应用程序中设置迭代次数)。时间以毫秒为单位,每个条目是运行测试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*和NSNumbers的NSArray

我创建了一个简单的循环来总结每个数组的内容,并使用mach_absolute_time()计时。NSMutableArray平均需要400倍的时间!!(不是400%,只是400倍!长了40000% !)

标题:

/ / Array_Speed_TestViewController.h

//阵列速度测试

//由Mehmet Akten于05/02/2009创作。

//版权所有MSA视觉有限公司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视觉有限公司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];
}];
< p >/////////// NSFastEnumerator < / p >

快速枚举背后的思想是使用快速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]];
}
< p >////////////// 从:darkdust.net

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

来自ObjectiveSugar的代码

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

这三种方法是:

        //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. 三是最快的执行方式。有了自动补全功能,就不用写迭代信封了。

下面是如何声明一个字符串数组并遍历它们:

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


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

这样做:

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

为迅速

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
}