问答中心分类: OBJECT-C如何迭代NSArray?
0
匿名用户 提问 3月 前

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

8 Answers
0
Quinn Taylor 回答 3月 前

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(或快速枚举)保护您不受以下可变集合(数组或其他)更改的影响在你不知情的情况下当你列举它的时候。如果按索引访问对象,可能会遇到奇怪的异常或一个错误(通常是在问题发生很久之后)导致的错误,这可能会让调试感到可怕。使用标准习惯用法之一的枚举具有“快速失败”行为,因此当您在发生突变后尝试访问下一个对象时,问题(由错误代码引起)将立即显现出来。随着程序变得更加复杂和多线程,甚至依赖于第三方代码可能修改的内容,脆弱的枚举代码变得越来越成问题。封装和抽象FTW!:-)

Adam Rosenfield 回复 3月 前

注意:大多数编译器都会发出关于“while(object=[e nextObject])”的警告。在这种情况下,实际上您的意思是使用=而不是==。要抑制警告,可以添加额外的括号:“while((object=[e nextObject])”)。

diederikh 回复 3月 前

NSEnumerator需要记住的另一件事是,它将比objectWithIndex方法使用更多的内存(创建数组的副本)。

Coderama 回复 3月 前

@奎因泰勒使用for (id object in array),tere是一种确定数组中当前索引的对象的方法,还是需要包含一个单独的计数器?

Quinn Taylor 回复 3月 前

您需要一个单独的计数器,但我建议您考虑基于块的枚举(它确实包括索引),如果您可以使用的话。

nielsbot 回复 3月 前

我很奇怪,但我用for循环如下:for(;;) { id object = [ e nextObject ] ; if ( !e ) { break ; } ... your loop operation ... }

funroll 回复 3月 前

在里面enumerateObjectsUsingBlock,您可以更改类型id为对象的实际类型。(例如。NSString.)

Ashwin G 回复 3月 前

如何使用for(NSArray*arr in dictionary)语法向后迭代?=>这意味着我要首先在最后一个对象上运行for循环

Hibbem 回复 3月 前

会投票支持这个答案,但不能把666变成667。如果某个怪物决定这么做,他会回来的。

0
diederikh 回答 3月 前

对于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
}
Quinn Taylor 回复 3月 前

在第一个示例中,您甚至不必在循环外声明int。这同样有效,并且可以很好地确定变量的作用域,以便以后需要时可以重用它:for(int i=0;i<[myArray count];i++)。。。但也要注意,每次通过数组调用-count可能会抵消使用-objectAtIndex的好处:

Quinn Taylor 回复 3月 前

实际上,如果要枚举可变集合,则每次通过循环检查计数在技术上更为正确。我澄清了我的答案,以解释使用NSEnumerator或NSFastEnumeration可以防止数组的并发突变。

diederikh 回复 3月 前

for(int i=0;…)contruct是一种C语言方言(我相信是C99),我自己也使用它,但我不确定它是XCode默认值。

Quinn Taylor 回复 3月 前

C99是通过Xcode 3.1的默认up。x-在将来的某个时候,默认值将更改为GNU99,它(除其他外)支持匿名联合和结构。那应该很好。。。

Quinn Taylor 回复 3月 前

使用for (NSUInteger i = 0, count = [myArray count]; i < count; i++)可能是这种方法最有效、最简洁的方法。

0
Hitendra Solanki 回答 3月 前

测试结果和源代码如下(您可以在应用程序中设置迭代次数)。时间以毫秒为单位,每个条目都是运行测试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\u absolute\u time()对其计时。NSMutableArray平均耗时400倍!!(不是400%,只是长了400倍!那是4万倍!)。
标题:
//Array\u Speed\u TestViewController。h类
//阵列速度测试
//由Mehmet Akten于2009年2月5日创建。
//版权所有MSA Visuals有限公司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\u Speed\u TestViewController。m级
//阵列速度测试
//由Mehmet Akten于2009年2月5日创建。
//版权所有MSA Visuals有限公司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];
}];

///////////
快速枚举背后的思想是使用快速C数组访问来优化迭代。它不仅应该比传统的NSEnumerator更快,而且Objective-C 2.0还提供了非常简洁的语法。

id object;
for (object in myArray) {
    [self doSomethingWith:object];
}

/////////////////
N分子
这是一种外部迭代形式:[myArray objectEnumerator]返回一个对象。这个对象有一个方法nextObject,我们可以在循环中调用它,直到它返回nil为止

NSEnumerator *enumerator = [myArray objectEnumerator];
id object;
while (object = [enumerator nextObject]) {
    [self doSomethingWith:object];
}

/////////////////
ObjectaIndex:枚举
使用增加整数的for循环和使用[myArray objectAtIndex:index]查询对象是枚举的最基本形式。

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

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

0
Javier Calatrava Llavería 回答 3月 前

这三种方式是:

//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。使用自动完成,忘记编写迭代信封。
0
onmyway133 回答 3月 前

添加each方法NSArray category你会非常需要的
代码取自目标糖

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