开发者

When to use enumerateObjectsUsingBlock vs. for

开发者 https://www.devze.com 2023-01-31 14:59 出处:网络
Besides the obvious differences: Use enumerateObjectsUsingBlock when you need both the 开发者_开发知识库index and the object

Besides the obvious differences:

  • Use enumerateObjectsUsingBlock when you need both the 开发者_开发知识库index and the object
  • Don't use enumerateObjectsUsingBlock when you need to modify local variables (I was wrong about this, see bbum's answer)

Is enumerateObjectsUsingBlock generally considered better or worse when for (id obj in myArray) would also work? What are the advantages/disadvantages (for example is it more or less performant)?


Ultimately, use whichever pattern you want to use and comes more naturally in the context.

While for(... in ...) is quite convenient and syntactically brief, enumerateObjectsUsingBlock: has a number of features that may or may not prove interesting:

  • enumerateObjectsUsingBlock: will be as fast or faster than fast enumeration (for(... in ...) uses the NSFastEnumeration support to implement enumeration). Fast enumeration requires translation from an internal representation to the representation for fast enumeration. There is overhead therein. Block-based enumeration allows the collection class to enumerate contents as quickly as the fastest traversal of the native storage format. Likely irrelevant for arrays, but it can be a huge difference for dictionaries.

  • "Don't use enumerateObjectsUsingBlock when you need to modify local variables" - not true; you can declare your locals as __block and they'll be writable in the block.

  • enumerateObjectsWithOptions:usingBlock: supports either concurrent or reverse enumeration.

  • with dictionaries, block based enumeration is the only way to retrieve the key and value simultaneously.

Personally, I use enumerateObjectsUsingBlock: more often than for (... in ...), but - again - personal choice.


For simple enumeration, simply using fast enumeration (i.e. a for…in… loop) is the more idiomatic option. The block method might be marginally faster, but that doesn't matter much in most cases — few programs are CPU-bound, and even then it's rare that the loop itself rather than the computation inside will be a bottleneck.

A simple loop also reads more clearly. Here's the boilerplate of the two versions:

for (id x in y){
}

[y enumerateObjectsUsingBlock:^(id x, NSUInteger index, BOOL *stop){
}];

Even if you add a variable to track the index, the simple loop is easier to read.

So when you should use enumerateObjectsUsingBlock:? When you're storing a block to execute later or in multiple places. It's good for when you're actually using a block as a first-class function rather than an overwrought replacement for a loop body.


Although this question is old, things have not changed, the accepted answer is incorrect.

The enumerateObjectsUsingBlock API was not meant to supersede for-in, but for a totally different use case:

  • It allows the application of arbitrary, non-local logic. i.e. you don’t need to know what the block does to use it on an array.
  • Concurrent enumeration for large collections or heavy computation (using the withOptions: parameter)

Fast Enumeration with for-in is still the idiomatic method of enumerating a collection.

Fast Enumeration benefits from brevity of code, readability and additional optimizations which make it unnaturally fast. Faster than a old C for-loop!

A quick test concludes that in the year 2014 on iOS 7, enumerateObjectsUsingBlock is consistently 700% slower than for-in (based on 1mm iterations of a 100 item array).

Is performance a real practical concern here?

Definitely not, with rare exception.

The point is to demonstrate that there is little benefit to using enumerateObjectsUsingBlock: over for-in without a really good reason. It doesn't make the code more readable... or faster... or thread-safe. (another common misconception).

The choice comes down to personal preference. For me, the idiomatic and readable option wins. In this case, that is Fast Enumeration using for-in.

Benchmark:

NSMutableArray *arr = [NSMutableArray array];
for (int i = 0; i < 100; i++) {
    arr[i] = [NSString stringWithFormat:@"%d", i];
}
int i;
__block NSUInteger length;

i = 1000 * 1000;
uint64_t a1 = mach_absolute_time();
while (--i > 0) {
    for (NSString *s in arr) {
        length = s.length;
    }
}
NSLog(@"For-in %llu", mach_absolute_time()-a1);

i = 1000 * 1000;
uint64_t b1 = mach_absolute_time();
while (--i > 0) {
    [arr enumerateObjectsUsingBlock:^(NSString *s, NSUInteger idx, BOOL *stop) {
        length = s.length;
    }];
}
NSLog(@"Enum %llu", mach_absolute_time()-b1);

Results:

2014-06-11 14:37:47.717 Test[57483:60b] For-in 1087754062
2014-06-11 14:37:55.492 Test[57483:60b] Enum   7775447746


To answer the question about performance, I made some tests using my performance test project. I wanted to know which of the three options for sending a message to all objects in an array is the fastest.

The options were:

1) makeObjectsPerformSelector

[arr makeObjectsPerformSelector:@selector(_stubMethod)];

2) fast enumeration & regular message send

for (id item in arr)
{
    [item _stubMethod];
}

3) enumerateObjectsUsingBlock & regular message send

[arr enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) 
 {
     [obj _stubMethod];
 }];

It turns out that makeObjectsPerformSelector was the slowest by far. It took twice as long as fast enumeration. And enumerateObjectsUsingBlock was the fastest, it was around 15-20% faster than fast iteration.

So if you're very concerned about the best possible performance, use enumerateObjectsUsingBlock. But keep in mind that in some cases the time it takes to enumerate a collection is dwarfed by the time it takes to run whatever code you want each object to execute.


It's fairly useful to use enumerateObjectsUsingBlock as an outer loop when you want to break nested loops.

e.g.

[array1 enumerateObjectsUsingBlock:^(id obj1, NSUInteger idx, BOOL * _Nonnull stop) {
  for(id obj2 in array2) {
    for(id obj3 in array3) {
      if(condition) {
        // break ALL the loops!
        *stop = YES;
        return;
      }
    }
  }
}];

The alternative is using goto statements.


Thanks to @bbum and @Chuck for starting comprehensive comparisons on performance. Glad to know it's trivial. I seem to have gone with:

  • for (... in ...) - as my default goto. More intuitive to me, more programming history here than any real preference - cross language reuse, less typing for most data structures due to IDE auto complete :P.

  • enumerateObject... - when access to object and index is needed. And when accessing non-array or dictionary structures (personal preference)

  • for (int i=idx; i<count; i++) - for arrays, when I need to start on a non-zero index

0

精彩评论

暂无评论...
验证码 换一张
取 消