Question

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?

Was it helpful?

Solution

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.

OTHER TIPS

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.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top