151

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)?

bbum
  • 162,346
  • 23
  • 271
  • 359
Paul Wheeler
  • 18,988
  • 3
  • 28
  • 41

6 Answers6

357

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.

BJ Homer
  • 48,806
  • 11
  • 116
  • 129
bbum
  • 162,346
  • 23
  • 271
  • 359
  • 17
    Wow, very informative. I wish I could accept both of these answers, but I'm going with Chuck's because it resonates a bit more with me. Also, I found your blog (http://www.friday.com/bbum/2009/08/29/blocks-tips-tricks/) while searching for __block and learned even more. Thank you. – Paul Wheeler Dec 20 '10 at 05:19
  • Just for sheer informative value I'm giving this a vote. Nice answer. – imnk Jun 16 '11 at 15:06
  • 9
    For the record, block-based enumeration is not always "as fast or faster" http://www.mikeabdullah.net/slow-block-based-dictionary-enumeration.html – Mike Abdullah Nov 06 '12 at 17:27
  • Nice find. That is seemingly broken. Please file a bug (and post the # here). Note that it may be broken *now* for compatibility reasons, as an oversight, or as an implementation details and it will be fixed in some later release. – bbum Nov 06 '12 at 17:30
  • 1
    Someone posted it to the Objective-C mailing list at some point, prompting this reply: http://lists.apple.com/archives/objc-language/2012/Sep/msg00012.html – Mike Abdullah Nov 08 '12 at 09:13
  • But aren't blocks executed in a secondary thread? which means the line of code after the enumeration will execute BEFORE the enuneration blocks ends? – Van Du Tran Jun 17 '13 at 14:47
  • 2
    @VanDuTran Blocks are only executed on a separate thread if you tell them to be executed on a separate thread. Unless you use the concurrency option of enumeration, then it'll be executed on the same thread as the call was made – bbum Jun 17 '13 at 15:52
  • Why do you need __block for this? You won't get retain loop since you don't store block. Any else reason block shouldn't retain? – desudesudesu Jan 12 '14 at 12:18
  • @desudesudesu If you need to modify a variable declared locally to the method's scope within a block contained in that method, you'll need to use `__block`. Otherwise, the assignment will toss a compiler error. Nothing to do with retain cycles. – bbum Jan 12 '14 at 18:58
  • 2
    Nick Lockwood did a really nice article on this, and it seems `enumerateObjectsUsingBlock` is still significantly slower than fast enumeration for arrays and sets. I wonder why? http://iosdevelopertips.com/objective-c/high-performance-collection-looping-objective-c.html – Bob Spryn Feb 23 '14 at 02:20
  • 2
    Although it's kind of an implementation detail, it should be mentioned in this answer among the differences between the two approaches that `enumerateObjectsUsingBlock` wraps each invocation of the block with an autorelease pool (as of OS X 10.10 at least). This explains the performance difference compared to `for in` which doesn't do that. – Pol Dec 05 '14 at 19:31
  • One thing to add: block based enumeration gives you the (correct) object's index for free, while in fast enumeration it would require an rather expensive `indexOfObject:` call that isn't even returning the correct index for sure. – vikingosegundo Nov 25 '15 at 13:05
  • 1
    The claims of enumerateObjectsUsingBlock being "just as fast as" for-in are provably false. See my answer below... It's actually REALLY SLOW in comparison. If performance matters, don't use enumerateObjectsUsingBlock! In fact, don't use it _unless you have to_. – Adam Kaplan Apr 08 '16 at 23:59
83

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.

Chuck
  • 234,037
  • 30
  • 302
  • 389
  • 5
    `enumerateObjectsUsingBlock:` will either be the same speed or faster than fast enumeration in all cases. `for(... in ...)` uses fast enumeration which requires the collection to provide some interim representation of the internal data structures. As you note, likely irrelevant. – bbum Dec 20 '10 at 05:04
  • 5
    +1 `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.` – Steve Jul 04 '11 at 16:05
  • 7
    @bbum My own tests show that `enumerateObjects...` can actually be slower then fast enumeration with a loop. I ran this test several thousand times; the body of the block and loop were the same single line of code: `[(NSOperation *)obj cancel];`. The averages: fast enum loop - `-[JHStatusBar dequeueStatusMessage:] [Line: 147] Fast enumeration time (for..in..loop): 0.000009` and for the block - `-[JHStatusBar dequeueStatusMessage:] [Line: 147] Enumeration time using block: 0.000043`. Weird that the time difference is so large and consistent but, obviously, this is a very specific test case. – chown Oct 30 '12 at 17:37
44

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
Adam Kaplan
  • 1,954
  • 17
  • 16
  • 4
    I can confirm that running the same test on a MacBook Pro Retina 2014, `enumerateObjectsUsingBlock` is actually 5X slower. That's apparently due to an autorelease pool wrapping each invocation of the block, which doesn't happen for the `for in` case. – Pol Dec 05 '14 at 19:29
  • 2
    I confirm `enumerateObjectsUsingBlock:` is still 4X slower on real iPhone 6 iOS9, using Xcode 7.x to build. – Cœur Nov 25 '15 at 12:56
  • 1
    Thanks for confirming! I wish this answer didn't get so buried... some people just like the `enumerate` syntax because it feels FP-like and don't want to listen to performance analysis. – Adam Kaplan Apr 09 '16 at 00:00
  • 1
    By the way, it’s not just due to the autorelease pool. There is a lot more stack pushing & popping with `enumerate:` – Adam Kaplan Jan 07 '17 at 22:15
24

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.

CodeSmile
  • 64,284
  • 20
  • 132
  • 217
3

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.

Gabe
  • 2,279
  • 1
  • 23
  • 22
1

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

snowbound
  • 1,692
  • 2
  • 21
  • 29