加载中

A common task in Cocoa programming is to loop through a collection of objects (e.g. an NSArray, NSSet or NSDictionary). This seemingly simple problem has a wide variety of solutions, many of them with subtle performance considerations.

The Need for Speed

First, a disclaimer: The raw speed of one Objective-C method versus another is one of the last things you should worry about when programming – the difference is unlikely to be dramatic enough to matter compared with other, more important considerations, such as code clarity and readability.

But not prioritising speed is no excuse for not understanding it. You should always learn the performance implications of the code you are writing so that on the rare occasions when it does matter, you’ll know what to do.

Also, in the case of looping, much of the time it doesn’t matter which technique you choose from a readability or clarity perspective, so you might as well choose the fastest. There’s no point writing code that is slower than it needs to be.

Cocoa编程的一个通常的任务是要去循环遍历一个对象的集合  (例如,一个 NSArray, NSSet 或者是 NSDictionary). 这个看似简单的问题有广泛数量的解决方案,它们中的许多不乏有对性能方面问题的细微考虑.

对于速度的追求

首先,是一个免责声明: 相比其它问题而言,一个 Objective-C 方法原始的速度是你在编程时最后才需要考虑的问题之一 – 区别就在于这个问题够不上去同其它更加需要重点考虑的问题进行比较,比如说代码的清晰度和可读性.

但速度的次要性并不妨碍我们去理解它. 你应该经常去了解一下性能方面的考虑将如何对你正在编写的代码产生影响,一边在极少数发生问题的情况下,你会知道如何下手.

还有,在循环的场景中,大多数时候不管是从可读性或者是清晰度考虑,你选择哪种技术都没什么关系的, 所以你还不如选择速度最快的那一种. 没有必要选择编码速度比要求更慢的。

With that in mind, here are the options:

The Classic For Loop

for (NSUInteger i = 0; i < [array count]; i++){
  id object = array[i];
  …}

This is a simple and familiar way to loop through an array; it’s also pretty poor from a performance perspective. The biggest problem with this code is that we’re calling the count method each time round the loop. The count doesn’t change, so this is redundant. Normally the C compiler would optimise away something like this, but the dynamic nature of Objective-C means that method calls cannot be optimised away automatically. So, to improve performance, it’s worth storing the count in a variable before beginning the loop, like this:

NSUInteger count = [array count];for (NSUInteger i = 0; i < count; i++){
  id object = array[i];
  …}

考虑到这一点,就有了如下的选择:

经典的循环方式

for (NSUInteger i = 0; i < [array count]; i++){
  id object = array[i];
  …}

这是循环遍历一个数组的一个简单熟悉的方式; 从性能方面考虑它也相当的差劲. 这段代码最大的问题就是循环每进行一次我们都会调用数组的计数方法. 数组的总数是不会改变的,因此每次都去调用一下这种做法是多余的. 像这种代码一般C编译器一般都会优化掉, 但是 Objective-C 的动态语言特性意味着对这个方法的调用不会被自动优化掉. 因此,为了提升性能,值得我们在循环开始之前,将这个总数存到一个变量中,像这样:

NSUInteger count = [array count];for (NSUInteger i = 0; i < count; i++){
  id object = array[i];
  …}

NSEnumerator

NSEnumerator is an alternative way of looping through collections. All collections have one or more enumerator methods that return a new NSEnumerator instance each time they are called. A given NSEnumerator contains an internal pointer to the first object in the collection, and has a nextObject method that returns the current object and increments the pointer. You call it repeatedly until it returns nil, indicating the end of the collection has been reached:

id obj = nil;NSEnumerator *enumerator = [array objectEnumerator];while ((obj = [enumerator nextObject]));{
  …          
}

The performance of NSEnumerator is comparable to an ordinary for loop, but it’s useful because it abstracts the concept of indexing, meaning it can be used with structures such as linked lists, or even infinite sequences or data streams where the item count is unknown or undefined.

NSEnumerator

NSEnumerator 是循环遍历集合的一种可选方式. 所有的集合都已一个或者更多个枚举方法,每次它们被调用的时候都会返回一个NSEnumerator实体. 一个给定的 NSEnumerator 会包含一个指向集合中第一个对象的指针, 并且会有一个 nextObject 方法返回当前的对象并对指针进行增长. 你可以重复调用它直到它返回nil,这表明已经到了集合的末尾了:

id obj = nil;NSEnumerator *enumerator = [array objectEnumerator];while ((obj = [enumerator nextObject]));{
  …          
}

NSEnumerator 的性能可以媲美原生的for循环, 但它更加实用,因为它对索引的概念进行了抽象,这意味着它应用在结构化数据上,比如链表,或者甚至是无穷序列和数据流,这些结构中的数据条数未知或者并没有被定义.

Fast Enumeration

Fast enumeration was introduced in Objective-C 2.0 as a more convenient (and faster, obviously) replacement for the traditional NSEnumerator. It doesn’t make the enumerator class obsolete because it is still used for situations such as reverse enumeration, or when you want to mutate the collection (more on this later).

Fast enumeration adds a new enumeration method that looks like this:

- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state 
   objects:(id *)stackbuf count:(NSUInteger)len;

If you’re thinking “that doesn’t look very convenient!”, I don’t blame you. But the new method came coupled with a new loop syntax, the for…in loop. This uses the new enumeration method behind the scenes, and significantly reduces both the syntax and performance overheads of using the traditional for loop or NSEnumerator approaches:

for (id object in array){
  …}

快速枚举

快速枚举是在 Objective-C 2.0 中作为传统的NSEnumerator的更便利(并且明显更快速) 的替代方法而引入的. 它并没有使得枚举类过时因为其仍然被应用于注入反向枚举, 或者是当你需要对集合进行变更操作 (之后会更多地提到) 这些场景中.

快速枚举添加了一个看起来像下面这样子的新的枚举方法:

- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state 
   objects:(id *)stackbuf count:(NSUInteger)len;

如果你正在想着“那看起来并不怎么舒服啊!”, 我不会怪你的. 但是新的方法顺便带来了一种新的循环语法, for…in 循环. 这是在幕后使用了新的枚举方法, 并且重要的是在语法和性能上都比使用传统的for循环或者 NSEnumerator 方法都更省心了:

for (id object in array){
  …}

Enumeration blocks

With the advent of blocks, Apples added a fourth enumeration mechanism based on the block syntax. This is arguably more unwieldy than fast enumeration, but does have the benefit of returning both the object and index, whereas the other enumeration methods just return the object.

Another key feature of enumeration blocks is the option of concurrent enumeration (enumerating the objects on several threads in parallel). This isn’t always useful, depending on exactly what you are doing in your loop, but for cases where you are doing a lot of work per item, and where you don’t care about the enumeration order, this might yield a significant performance boost on multicore processors (which all modern Macs and iOS devices have).

枚举块

随着块的诞生,Apple加入第四个基于块语法的枚举机制. 这无疑比快速枚举更加的少见, 但是有一个优势就是对象和索引都会返回, 而其他的枚举方法只会返回对象.

枚举块的另外一个关键特性就是可选择型的并发枚举 (在几个并发的线程中枚举对象). 这不是经常有用,取决于你在自己的循环中具体要做些什么, 但是在你正有许多工作要做,并且你并不怎么关心枚举顺序的场景下,它在多核处理器上可能会产生显著的性能提高 (现在所有的 Mac和iOS设备都已经有了多核处理器).

Benchmarks

So how do these methods stack up, performance-wise? Here is a simple benchmark command-line app that compares performance of enumerating an array using the various different methods. We’ve run it with ARC disabled to prevent any behind-the-scenes retain or release from distorting the results. Because this is running on a fast Mac, all these methods are so fast that we’ve actually had to use an array of 10,000,000 (ten million) objects to get measurable results. If you decide to run this test on an iPhone, it would be wise to use a much smaller count!

To compile the code:

  • Save the code in a file with the name benchmark.m

  • From terminal, compile the application:
    clang -framework Foundation benchmark.m -o benchmark

  • Run the app: ./benchmark

#import <Foundation/Foundation.h> int main(int argc, const char * argv[]){
  @autoreleasepool  {
    static const NSUInteger arrayItems = 10000000;
     NSMutableArray *array = [NSMutableArray arrayWithCapacity:arrayItems];    for (int i = 0; i < arrayItems; i++) [array addObject:@(i)];
    array = [array copy];
 
    CFTimeInterval start = CFAbsoluteTimeGetCurrent();
     // Naive for loop
    for (NSUInteger i = 0; i < [array count]; i++)
    {
      id object = array[i];    } 
    CFTimeInterval forLoop = CFAbsoluteTimeGetCurrent();
    NSLog(@"For loop: %g", forLoop - start);
     // Optimized for loop
    NSUInteger count = [array count];    for (NSUInteger i = 0; i <  count; i++)
    {
      id object = array[i];    } 
    CFTimeInterval forLoopWithCountVar = CFAbsoluteTimeGetCurrent();
    NSLog(@"Optimized for loop: %g", forLoopWithCountVar - forLoop);
     // NSEnumerator
    id obj = nil;    NSEnumerator *enumerator = [array objectEnumerator];    while ((obj = [enumerator nextObject]))
    {     } 
    CFTimeInterval enumeratorLoop = CFAbsoluteTimeGetCurrent();
    NSLog(@"Enumerator: %g", enumeratorLoop - forLoopWithCountVar);
     // Fast enumeration
    for (id object in array)
    {     } 
    CFTimeInterval forInLoop = CFAbsoluteTimeGetCurrent();
    NSLog(@"For…in loop: %g", forInLoop - enumeratorLoop);
     // Block enumeration
    [array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {     }];
 
    CFTimeInterval enumerationBlock = CFAbsoluteTimeGetCurrent();
    NSLog(@"Enumeration block: %g", enumerationBlock - forInLoop);
     // Concurrent enumeration
    [array enumerateObjectsWithOptions:NSEnumerationConcurrent 
      usingBlock:^(id obj, NSUInteger idx, BOOL *stop) {     }];
 
    CFTimeInterval concurrentEnumerationBlock = CFAbsoluteTimeGetCurrent();
    NSLog(@"Concurrent enumeration block: %g", 
      concurrentEnumerationBlock - enumerationBlock);  }
  return 0;}

The results are shown below:

$ For loop: 0.119066
$ Optimized for loop: 0.092441
$ Enumerator: 0.123687
$ For…in loop: 0.049296
$ Enumeration block: 0.295039
$ Concurrent enumeration block: 0.199684

基准测试

那么这些方法叠加起来会如何呢, 性能会更加的好么? 这里有一个简单的基准测试命令行应用,比较了使用多种不同方法枚举一个数据的性能. 我们已经在 ARC 关闭的情况下运行了它,以排除任何干扰最终结果的隐藏在幕后的保留或者排除处理. 由于是运行在一个很快的 Mac 机上面, 所有这些方法运行极快以至于我们实际上不得不使用一个存有10,000,000 (一千万) 对象的数组来测量结果. 如果你决定在一个 iPhone 进行测试, 最明智的做法是使用一个小得多的数量!

为了编译这段代码:

  • 把代码保存在一个文件中,命名为 benchmark.m

  • 在终端中编译应用程序:
    clang -framework Foundation benchmark.m -o benchmark

  • 运行程序: ./benchmark

#import <Foundation/Foundation.h> int main(int argc, const char * argv[]){
  @autoreleasepool  {
    static const NSUInteger arrayItems = 10000000;
     NSMutableArray *array = [NSMutableArray arrayWithCapacity:arrayItems];    for (int i = 0; i < arrayItems; i++) [array addObject:@(i)];
    array = [array copy];
 
    CFTimeInterval start = CFAbsoluteTimeGetCurrent();
     // Naive for loop
    for (NSUInteger i = 0; i < [array count]; i++)
    {
      id object = array[i];    } 
    CFTimeInterval forLoop = CFAbsoluteTimeGetCurrent();
    NSLog(@"For loop: %g", forLoop - start);
     // Optimized for loop
    NSUInteger count = [array count];    for (NSUInteger i = 0; i <  count; i++)
    {
      id object = array[i];    } 
    CFTimeInterval forLoopWithCountVar = CFAbsoluteTimeGetCurrent();
    NSLog(@"Optimized for loop: %g", forLoopWithCountVar - forLoop);
     // NSEnumerator
    id obj = nil;    NSEnumerator *enumerator = [array objectEnumerator];    while ((obj = [enumerator nextObject]))
    {     } 
    CFTimeInterval enumeratorLoop = CFAbsoluteTimeGetCurrent();
    NSLog(@"Enumerator: %g", enumeratorLoop - forLoopWithCountVar);
     // Fast enumeration
    for (id object in array)
    {     } 
    CFTimeInterval forInLoop = CFAbsoluteTimeGetCurrent();
    NSLog(@"For…in loop: %g", forInLoop - enumeratorLoop);
     // Block enumeration
    [array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {     }];
 
    CFTimeInterval enumerationBlock = CFAbsoluteTimeGetCurrent();
    NSLog(@"Enumeration block: %g", enumerationBlock - forInLoop);
     // Concurrent enumeration
    [array enumerateObjectsWithOptions:NSEnumerationConcurrent 
      usingBlock:^(id obj, NSUInteger idx, BOOL *stop) {     }];
 
    CFTimeInterval concurrentEnumerationBlock = CFAbsoluteTimeGetCurrent();
    NSLog(@"Concurrent enumeration block: %g", 
      concurrentEnumerationBlock - enumerationBlock);  }
  return 0;}

下面展示出了结果:

$ For loop: 0.119066
$ Optimized for loop: 0.092441
$ Enumerator: 0.123687
$ For…in loop: 0.049296
$ Enumeration block: 0.295039
$ Concurrent enumeration block: 0.199684

Pay no attention to the magnitudes of these timings. What we are interested in is their relative size compared with one another. If we place them in order, fastest first, we get:

  1. For…in loop – the fastest.

  2. Optimized for loop – 2 times slower than for…in.

  3. Unoptimized for loop – 2.5 times slower than for…in.

  4. Enumerator – about the same as unoptimised for loop.

  5. Concurrent enumeration block – about 6 times slower than for…in.

  6. Enumeration block – almost 6 times slower than for…in.

For…in is the standout winner. Clearly they call it fast enumeration for a reason! Concurrent enumeration seems a little faster than single-threaded, but you shouldn’t read too much into that: We’re enumerating a very large number of objects here, but for a smaller array the overhead of concurrent execution outweighs the benefits.

The primary advantage of concurrent execution comes into play when the code inside your loop takes a significant time to execute. If you do a lot of work inside your loop, consider trying parallel enumeration if you don’t care about the order (but measure to see if it’s faster, don’t assume).

忽略掉时间的具体长短. 我们感兴趣的是它们同其它方法比较的相对大小. 如果我们按顺序排列它们,快的放前面,我会得到了下面的结果:

  1. For…in循环 – 最快.

  2. 对for循环的优化 – 比 for…in 慢两倍.

  3. 没有优化的for循环 – 比 for…in 慢2.5倍.

  4. Enumerator – 大约同没有优化的循环相同.

  5. 并发的枚举块 – 比 for…in 大约慢6倍.

  6. 枚举块 – 比 for…in 几乎慢6倍.

For…in 是胜出者. 显然他们将其称为快速枚举是有原因的! 并发枚举看起来是比单线程的快一点点, 但是你没必要对其做更多的解读: 我们这里是在枚举一个非常非常大型的对象数组,而对于小一些的数据并发执行的开销远多于其带来的好处.

并发执行的主要是在当你的循环需要大量的执行时间时有优势. 如果你在自己的循环中有许多东西要运行,那就考虑试下并行枚举,在你不关心枚举顺序的前提下 (但是请用行动的去权衡一下它是否变得更快乐,不要空手去揣度).

Other Collection Types

So what about other collection types, such as NSSet and NSDictionary? NSSet is unordered, so has no concept of fetching an object by index. We can benchmark enumeration though:

$ Enumerator: 0.421863
$ For…in loop: 0.095401
$ Enumeration block: 0.302784
$ Concurrent enumeration block: 0.390825

The results are similar to NSArray; again, for…in is the clear winner. What about NSDictionary? NSDictionary is a bit different because we have both keys and objects to iterate over. It’s possibly to just iterate over either keys or values in a dictionary, but we typically need both. Here is an adapted version of our array benchmark to handle NSDictionary:

#import <Foundation/Foundation.h> int main(int argc, const char * argv[]){
  @autoreleasepool  {
    static const NSUInteger dictItems = 10000;
     NSMutableDictionary *dictionary = 
      [NSMutableDictionary dictionaryWithCapacity:dictItems];    for (int i = 0; i < dictItems; i++) dictionary[@(i)] = @(i);
    dictionary = [dictionary copy];
 
    CFTimeInterval start = CFAbsoluteTimeGetCurrent();
     // Naive for loop
    for (NSUInteger i = 0; i < [dictionary count]; i++)
    {
      id key = [dictionary allKeys][i];      id object = dictionary[key];    } 
    CFTimeInterval forLoop = CFAbsoluteTimeGetCurrent();
    NSLog(@"For loop: %g", forLoop - start);
     // Optimized for loop
    NSUInteger count = [dictionary count];    NSArray *keys = [dictionary allKeys];    for (NSUInteger i = 0; i <  count; i++)
    {
      id key = keys[i];      id object = dictionary[key];    } 
    CFTimeInterval forLoopWithCountVar = CFAbsoluteTimeGetCurrent();
    NSLog(@"Optimized for loop: %g", forLoopWithCountVar - forLoop);
     // NSEnumerator
    id key = nil;    NSEnumerator *enumerator = [dictionary keyEnumerator];    while ((key = [enumerator nextObject]))
    {
      id object = dictionary[key];    } 
    CFTimeInterval enumeratorLoop = CFAbsoluteTimeGetCurrent();
    NSLog(@"Enumerator: %g", enumeratorLoop - forLoopWithCountVar);
     // Fast enumeration
    for (id key in dictionary)
    {
      id object = dictionary[key];    } 
    CFTimeInterval forInLoop = CFAbsoluteTimeGetCurrent();
    NSLog(@"For…in loop: %g", forInLoop - enumeratorLoop);
     // Block enumeration
    [dictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {     }];
 
    CFTimeInterval enumerationBlock = CFAbsoluteTimeGetCurrent();
    NSLog(@"Enumeration block: %g", enumerationBlock - forInLoop);
     // Concurrent enumeration
    [dictionary enumerateKeysAndObjectsWithOptions:NSEnumerationConcurrent 
      usingBlock:^(id key, id obj, BOOL *stop) {     }];
 
    CFTimeInterval concurrentEnumerationBlock = CFAbsoluteTimeGetCurrent();
    NSLog(@"Concurrent enumeration block: %g", 
      concurrentEnumerationBlock - enumerationBlock);  }
  return 0;}

其它集合类型Other Collection Types

那么其它的结合类型怎么样呢, 比如 NSSet 和 NSDictionary? NSSet 是无序的, 因此没有按索引去取对象的概念.我们也可以进行一下基准测试:

$ Enumerator: 0.421863
$ For…in loop: 0.095401
$ Enumeration block: 0.302784
$ Concurrent enumeration block: 0.390825

结果同 NSArray 一致; for…in 再一次胜出了.  NSDictionary怎么样了? NSDictionary 有一点不同因为我们同时又一个键和值对象需要迭代. 在一个字典中单独迭代键或者值是可以的, 但典型的情况下我们两者都需要. 这里我们有一段适配于操作NSDictionary的基准测试代码:

#import <Foundation/Foundation.h> int main(int argc, const char * argv[]){
  @autoreleasepool  {
    static const NSUInteger dictItems = 10000;
     NSMutableDictionary *dictionary = 
      [NSMutableDictionary dictionaryWithCapacity:dictItems];    for (int i = 0; i < dictItems; i++) dictionary[@(i)] = @(i);
    dictionary = [dictionary copy];
 
    CFTimeInterval start = CFAbsoluteTimeGetCurrent();
     // Naive for loop
    for (NSUInteger i = 0; i < [dictionary count]; i++)
    {
      id key = [dictionary allKeys][i];      id object = dictionary[key];    } 
    CFTimeInterval forLoop = CFAbsoluteTimeGetCurrent();
    NSLog(@"For loop: %g", forLoop - start);
     // Optimized for loop
    NSUInteger count = [dictionary count];    NSArray *keys = [dictionary allKeys];    for (NSUInteger i = 0; i <  count; i++)
    {
      id key = keys[i];      id object = dictionary[key];    } 
    CFTimeInterval forLoopWithCountVar = CFAbsoluteTimeGetCurrent();
    NSLog(@"Optimized for loop: %g", forLoopWithCountVar - forLoop);
     // NSEnumerator
    id key = nil;    NSEnumerator *enumerator = [dictionary keyEnumerator];    while ((key = [enumerator nextObject]))
    {
      id object = dictionary[key];    } 
    CFTimeInterval enumeratorLoop = CFAbsoluteTimeGetCurrent();
    NSLog(@"Enumerator: %g", enumeratorLoop - forLoopWithCountVar);
     // Fast enumeration
    for (id key in dictionary)
    {
      id object = dictionary[key];    } 
    CFTimeInterval forInLoop = CFAbsoluteTimeGetCurrent();
    NSLog(@"For…in loop: %g", forInLoop - enumeratorLoop);
     // Block enumeration
    [dictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {     }];
 
    CFTimeInterval enumerationBlock = CFAbsoluteTimeGetCurrent();
    NSLog(@"Enumeration block: %g", enumerationBlock - forInLoop);
     // Concurrent enumeration
    [dictionary enumerateKeysAndObjectsWithOptions:NSEnumerationConcurrent 
      usingBlock:^(id key, id obj, BOOL *stop) {     }];
 
    CFTimeInterval concurrentEnumerationBlock = CFAbsoluteTimeGetCurrent();
    NSLog(@"Concurrent enumeration block: %g", 
      concurrentEnumerationBlock - enumerationBlock);  }
  return 0;}

NSDictionary is much slower to populate than NSArray or NSSet, so we’ve reduced the item count to 10,000 (ten thousand) to avoid locking up the machine. You should therefore ignore how the magnitudes of the results below compare with those of NSArray because we are using 1000 times fewer objects:

$ For loop: 2.25899
$ Optimized for loop: 0.00273103
$ Enumerator: 0.00496799
$ For…in loop: 0.001041
$ Enumeration block: 0.000607967
$ Concurrent enumeration block: 0.000748038

The unoptimized for loop here is spectacularly slow because we are copying the key array each time. By storing both the keys array and count in variables we get much better speeds. The cost of object lookup now dominates the other factors, so there is little difference between using a for loop, NSEnumerator or for…in. But the enumeration block method, which returns both key and value in a single function, is now the fastest option.

NSDictionary 填充起来比 NSArray 或者 NSSet 慢得多, 因此我们把数据条数减少到了10,000 (一万) 以避免机器锁住. 因而你应该忽略结果怎么会比那些 NSArray 低那么多,因为我们使用的是更少对象的 1000 次循环:

$ For loop: 2.25899
$ Optimized for loop: 0.00273103
$ Enumerator: 0.00496799
$ For…in loop: 0.001041
$ Enumeration block: 0.000607967
$ Concurrent enumeration block: 0.000748038

没有优化过的循环再这里慢得很壮观,因为每一次我们都复制了键数组. 通过把键数组和总数存到变量中,我们获得了更快的速度. 查找对象的消耗现在主宰了其它的因素,因此使用一个for循环, NSEnumerator 或者for…in 差别很小. 但是对于枚举块方法而言,它在一个方法中把键和值都返回了,所以现在变成了最快的选择。

Reverse Gear

Base on what we’ve seen, if all other factors are equal, you should try to use for…in (aka, fast enumeration) when looping over arrays, and block enumeration when looping over dictionaries. There are some scenarios where this isn’t possible though, such as when we need to enumerate backwards, or when we want to mutate the collection as we’re going through it.

To enumerate an array backwards, we can call the reverseObjectEnumerator method to get an NSEnumerator that steps through the array from the last to the first item. NSEnumerator, like NSArray itself, supports the fast enumeration protocol. That means we can still use for…in with this approach, with no loss of speed or concision:

  for (id object in [array reverseObjectEnumerator]) 
  {
    …  }

(In case you are wondering, there’s no equivalent method for NSSet or NSDictionary, but enumerating an NSSet or NSDictionary backwards is meaningless anyway, since the keys are unordered.)

If you want to use block enumeration, there’s an NSEnumerationReverse option you can use, like this:

  [array enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    …  }];

反转齿轮

基于我们所见,如果所有其它的因素都一样的话,在循环遍历数组时你应该尝试去使用for...in循环, 而遍历字典时,则应该选择枚举块. 也有一些场景下这样的做法并不可能行得通,比如我们需要回头来进行枚举,或者当我们在遍历时想要变更集合的情况.

为了回过头来枚举一个数据,我们可以调用reverseObjectEnumerator方法来获得一个NSEnumerator 以从尾至头遍历数组. NSEnumerator, 就像是 NSArray 它自己, 支持快速的枚举协议. 那就意味着我们仍然可以在这种方式下使用 for…in, 而无速度和简洁方面的损失:

  for (id object in [array reverseObjectEnumerator]) 
  {
    …  }

(除非你异想天开, NSSet 或者 NSDictionary 是没有等效的方法的, 而反向枚举一个 NSSet 或者NSDictionary无论如何都没啥意义, 因为键是无序的.)

如果你想使用枚举块的话, NSEnumerationReverse你可以试试, 像这样:

  [array enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    …  }];
返回顶部
顶部