views:

10925

answers:

3

I'm looking for the standard idiom to iterate over an NSArray. My code needs to be suitable for OS X 10.4+.

+22  A: 

For OS X 10.4.x and previous:

 int i;
 for (i = 0; i < [myArray count]; i++) {
   id myArrayElement = [myArray objectAtIndex:i];
   ...do something useful with myArrayElement
 }

For OS X 10.5.x (or iPhone) and beyond:

for (id myArrayElement in myArray) {
   ...do something useful with myArrayElement
}
Diederik Hoogenboom
In the first example, you don't even have to declare the int outside the loop. This works just as well, and scopes the variable nicely so you can reuse it later if needed: for (int i = 0; i < [myArray count]; i++) ... But also, be aware that calling -count each time through the array can cancel out the benefit of using -objectAtIndex:
Quinn Taylor
And in reality, if you're enumerating a mutable collection, it is technically more correct to check the count every time through the loop. I clarified my answer to explain that using NSEnumerator or NSFastEnumeration can protect from concurrent mutations of the array.
Quinn Taylor
This answer looked great, but I could pick only one...
Steve McLeod
The for(int i = 0; ...) contruct is a C language dialect (C99 is believe), which I myself do use but I wasn't sure it was the XCode default.
Diederik Hoogenboom
C99 is the default up through Xcode 3.1.x — at some future point the default will change to GNU99, which (among other things) supports anonymous unions and structs. That should be nice...
Quinn Taylor
Using `for (NSUInteger i = 0, count = [myArray count]; i < count; i++)` is probably the most efficient and concise you'll get for this approach.
Quinn Taylor
+25  A: 

The standard idiom for pre-10.5 is to use an NSEnumerator and a while loop, like so:

NSEnumerator *e = [array objectEnumerator];
id object;
while (object = [e nextObject]) {
  // do something with object
}

I recommend keeping it simple. Tying yourself to an array type is inflexible, and the purported speed increase of using -objectAtIndex: is insignificant to the improvement with fast enumeration on 10.5+ anyway. (Fast enumeration actually uses pointer arithmetic on the underlying data structure, and removes most of the method call overhead.) Premature optimization is never a good idea — it results in messier code to solve a problem that isn't your bottleneck anyway.

When using -objectEnumerator, you very easily change to another enumerable collection (like an NSSet, keys in an NSDictionary, etc.), or even switch to -reverseObjectEnumerator to enumerate an array backwards, all with no other code changes. If the iteration code is in a method, you could even pass in any NSEnumerator and the code doesn't even have to care about what it's iterating. Further, an NSEnumerator (at least those provided by Apple code) retains the collection it's enumerating as long as there are more objects, so you don't have to worry about how long an autoreleased object will exist.

Perhaps the biggest thing an NSEnumerator (or fast enumeration) protects you from is having a mutable collection (array or otherwise) change underneath you without your knowledge while you're enumerating it. If you access the objects by index, you can run into strange exceptions or off-by-one errors (often long after the problem has occurred) that can be horrific to debug. Enumeration using one of the standard idioms has a "fail-fast" behavior, so the problem (caused by incorrect code) will manifest itself immediately when you try to access the next object after the mutation has occurred. As programs get more complex and multi-threaded, or even depend on something that third-party code may modify, fragile enumeration code becomes increasingly problematic. Encapsulation and abstraction FTW! :-)

Quinn Taylor
Note: most compilers will give a warning about "while (object = [e nextObject])". In this case, you actually DO mean to use = instead of ==. To suppress the warnings, you can add extra parentheses: "while ((object = [e nextObject]))".
Adam Rosenfield
Interesting, I don't believe I've ever had gcc/Xcode warn be about this. In C (and, by extention, Objective-C) it's perfectly acceptable to use a non-boolean value as a conditional test; anything non-zero is interpreted as true. Still, I can see your point. One could argue that the clearest usage in Objective-C would be while ((object = [e nextObject]) != nil) ...
Quinn Taylor
Another thing to remember with NSEnumerator is that it will use more memory (makes a copy of the array) than the objectWithIndex approach.
Diederik Hoogenboom
That's incorrect, it uses the same NSArray you obtained it from. Why else would there be danger of mutation during enumeration? See the Special Considerations section at this link: http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSArray_Class/Reference/Reference.html#//apple_ref/occ/instm/NSArray/objectEnumerator
Quinn Taylor
A: 

concerning performance you also might want to have a look at this and this

didito
Those are mildly interesting micro-benchmarks, but performance wasn't part of the question here, and prematurely optimizing an action that is usually a miniscule part of the work being done makes the code more complex without much benefit. Applying Amdahl's Law (from a sophomore computer science course) to a sequential program demonstrates that focusing on optimizing what is provably slow gets the most bang for your buck, and at some point optimizing something inconsequential becomes near pointless. http://en.wikipedia.org/wiki/Amdahl%27s_law#Speedup_in_a_sequential_program
Quinn Taylor
well, i consider this a standard answer of a software engineer (usually followed by a flamewar). this topic has been discussed alot, everybody knows about it because it comes up regularly.i never advocated for premature optimization or making code ugly in order to save some ticks. but i often come across this situations where you want to quickly iterate over pixels, sound samples, <some_data_array> and work on each of them. and this is where performance quickly starts to matter.this applies not only to prototypes, sometimes you just don't have the resources to optimize other ways.
didito
right, performance was not a question here. nevertheless i thought i can contribute with this (imho) interesting links because performance and ease of understanding of loop-through-array code was slightly touched. and for me stackoverflow is also about sharing, not question -> only one right answer.receiving a negative rating for my contribution shows me just how narrowminded people sometimes are.have a good day!
didito
(sigh) I'm not saying it wasn't interesting and that it's not good to consider performance, but this is one of those questions that certainly has a straightforward answer, and not even mentioning the standard idiom makes this answer not so helpful. If the question had asked about speeding up array iteration it would be much more apropos. Since he's dealing with an NSArray, he may not have the choice of using a C array. In addition, the benchmarks wrap float values in NSNumber, which of course adds lots of overhead. If they used an id* array and objects only, the results would be different.
Quinn Taylor
Using an NSArray is fundamentally a choice in favor of convenience over performance, but being aware of the tradeoff can also be useful. It wasn't my intent to offend or belittle the links you shared, although I can see how it would appear that I was, and I apologize for that. I was writing in an analytical tone and evaluating as objectively as I could. To me, critically examining all responses is part of finding a solution. Please don't take the criticism personally, we're all trying to help the OP find the answers that are most helpful given the question. I'll be more careful in my wording.
Quinn Taylor