4

I don't entirely understand the details of how fast enumeration works, but compare the following two cases:

for(NSObject *object in self.myParent.parentsParents.granfathersMother.cousin.unclesNephew.array) {
    // do something
}

vs.

NSArray *array = self.myParent.parentsParents.granfathersMother.cousin.unclesNephew.array;
for(NSObject *object in array) {
     // do something
 }

In the first example, will it go through that entire chain every iteration to get the array? Should I be using the second way?

Snowman
  • 31,411
  • 46
  • 180
  • 303

2 Answers2

7

I was at WWDC when Apple introduced Fast Enumeration, and (I recall) we were told then that the right hand object is moved into a temp. In addition, it must be since this works:

for(id foo in [myCollection reverseObjectEnumerator])

You can see that collections that perform fast enumeration adopt the "Fast Enumeration Protocol" (NSFastEnumeration), which has one method:

– countByEnumeratingWithState:objects:count:

That method returns a C Array of objects that lets the enumeration go very quickly, again supporting the one time use of the right side.


Now, having said all that, currently Apple advises developers (at WWDC) to use the block enumeration, which they claim is both faster and generates less code:

[myCollection enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop)
{
... your code
} ];

What I am fond of doing is not using "id obj", but the actual type (to avoid a cast in the block):

[myCollection enumerateObjectsUsingBlock:^(NSDictionary *dict, NSUInteger idx, BOOL *stop)
{
... your code
} ];

Neither the compiler nor the analyzer (4.4) complains when I do this.

If you need to set a variable outside this method, then you have to make it a block variable:

__block int foo = 0;
[myCollection enumerateObjectsUsingBlock:^(NSNumber *num, NSUInteger idx, BOOL *stop)
{
   foo = MAX(foo, [num integerValue]);
} ];

EDIT: as a clarification, the direct answer to your question is 'no', the statement 'self.myParent.parentsParents.granfathersMother.cousin.unclesNephew.array' is evaluated once, and the final object stored as a temp on the stack. Also, you can use the same technique with block enumeations - the statement is evaluated once and the final returned object used for the enumeration.

__block int foo = 0;
[self.myParent.parentsParents.granfathersMother.cousin.unclesNephew.array enumerateObjectsUsingBlock:^(NSNumber *num, NSUInteger idx, BOOL *stop)
{
   foo = MAX(foo, [num integerValue]);
} ];

EDIT2: I found another thread on SO where this same topic was discussed. The one point I missed regarding block enumeration is that you can specify that they should be run concurrently (or in reverse) using the slightly more complex method:

enumerateObjectsWithOptions:usingBlock:

As iOS devices get more and more core's this could potentially be a big win depending on what you're doing.

@bbum's response to the question (and others too) are here.

Community
  • 1
  • 1
David H
  • 40,852
  • 12
  • 92
  • 138
  • *That method returns a C Array of objects that lets the enumeration go very quickly, again supporting the one time use of the right side.* +1 -- right. it would be the correct approach if 'fast' were the intention. the collection is enumerated, not an arbitrary expression. variants returned by the expression could not even be a consideration if speed or correctness were desired in this construct. – justin Sep 05 '12 at 13:31
  • David, I do not recall that statement being made. Which WWDC presentation said that? Also, I just ran a quick experiment, and for several different cases, I could never see where calling enumerateObjectsUsingBlock was faster then fast enumeration on an array. In debug builds fast-enumeration was two orders of magnitude faster. Debug means nothing though. On release builds, they were very close, but the block enumeration was never faster. – Jody Hagins Sep 05 '12 at 13:50
  • @JodyHagins wait, what is the difference between a debug build and a release build? I thought it was just a matter of certificates and provisioning and all that? – Snowman Sep 05 '12 at 14:08
  • You can determine the difference yourself by changing settings in your scheme, but generally a debug build has no optimizations, while a release build enables most optimizations. You can easily switch by selecting Scheme, then Run, and toggle it between debug/release. – Jody Hagins Sep 05 '12 at 14:13
  • @JodyHagins have you really watched _all_ the WWDC videos? ;) – Snowman Sep 05 '12 at 14:22
  • @mohabitar Ummm..., well, errr... Almost all of them... many multiple times. Actually, there are only a few that I have not watched, but they all deal with stuff like HTML5, and UIWebView... They are far more entertaining than a TV show or movie... and they don't cost anything... if only I could get my wife to count watching them as a date night... – Jody Hagins Sep 05 '12 at 14:31
3

That's probably compiler-specific (i.e. undefined). If you are that bothered then add some timing code and find out yourself:

#import <sys/time.h>

static unsigned getTickCount()
{
    struct timeval tv;
    gettimeofday(&tv, 0);
    return (unsigned)((tv.tv_sec * 1000) + (tv.tv_usec / 1000));
}

...

unsigned startTime = getTickCount();
for(NSObject *object in self.myParent.parentsParents.granfathersMother.cousin.unclesNephew.array) {
    // do something
}
unsigned endTime = getTickCount();
NSLog(@"That took %umS", endTime - startTime);

You will have to have a pretty big array however in order to register anything above 0.

trojanfoe
  • 120,358
  • 21
  • 212
  • 242
  • 1
    It would be easier (and more reliable) to set a breakpoint in the `myParent` getter and check if it is only hit once for the entire loop... – omz Sep 05 '12 at 12:41
  • @omz Yeah good point, but that won't tell you if it matters or not. Timing it will. – trojanfoe Sep 05 '12 at 12:42
  • The last point is also the reason why it doesn't matter if the getter chain is accessed on every iteration. After the first access, the caching mechanism for methods kicks in and if you don't have a really huge set of data it doesn't matter whats used. – JustSid Sep 05 '12 at 12:44
  • @JustSid How do you know what these getters do exactly? Perhaps they need to perform some sort of expensive calculation to get their results. Then it would matter a lot whether they're called 1000 times or just once. (Besides, method calls are never "free") – omz Sep 05 '12 at 12:51
  • @omz Very valid point, sorry, I worded that a bit stupid. What I meant was that it most likely doesn't matter and that the question smells highly like premature optimization. – JustSid Sep 05 '12 at 12:57