Cocoa foreach Macro
I’ve updated the macro that I use to iterate over Cocoa collections. As before, the idea is that you write foreach( object, collection )
, where object
is the name of the loop variable and collection
is either an NSEnumerator
or an object that responds to -objectEnumerator
. collection
is only evaluated once, and IMP
-caching is used to speed up the calls to -nextObject
.
Unlike the old version, all the variables are local to the loop; it does not pollute the outer scope. This requires that you set the “C Language Dialect” in Xcode to C99. Also, there is a new macro called foreacht
that lets you specify a type for the loop variable. Then you can write foreach( NSString *, string, collection )
, and if you write something like [string count]
you’ll get a compiler warning. Here’s the code:
#define foreachGetEnumerator(c) \ ([c respondsToSelector:@selector(objectEnumerator)] ? \ [c objectEnumerator] : \ c) #define foreachGetIMP(e) \ [foreachEnum methodForSelector:@selector(nextObject)] #define foreachCallIMP(imp, e) \ ((IMP)imp)(e, @selector(nextObject)) #define foreachGetFirst(imp, e) \ (e ? foreachCallIMP(imp, e) : nil) #define foreach(object, collection) \ for ( id foreachCollection = collection, \ foreachEnum = foreachGetEnumerator(foreachCollection), \ foreachIMP = (id)foreachGetIMP(foreachEnum), \ object = foreachGetFirst(foreachIMP, foreachEnum); \ object; \ object = foreachCallIMP(foreachIMP, foreachEnum) ) #define foreacht(type, object, collection) \ for ( type foreachCollection = (id)collection, \ *foreachEnum = (id)foreachGetEnumerator((id)foreachCollection), \ *foreachIMP = (id)foreachGetIMP((id)foreachEnum), \ *object = foreachGetFirst(foreachIMP, foreachEnum); \ object; \ object = foreachCallIMP(foreachIMP, foreachEnum) )
Update: Matt Neuberg (who prompted this post by sending me his modification of my original foreach
to add the type
parameter) didn’t like the look of the casts and suggested using nested one-time for
loops instead:
#define foreachGetEnumerator(c) \ ([c respondsToSelector:@selector(objectEnumerator)] ? \ [c objectEnumerator] : \ c) #define foreacht(type, object, collection) \ for ( id foreachCollection = collection; \ foreachCollection; \ foreachCollection = nil ) \ for ( id foreachEnum = foreachGetEnumerator(foreachCollection); \ foreachEnum; \ foreachEnum = nil ) \ for ( IMP foreachNext = [foreachEnum methodForSelector:@selector(nextObject)]; \ foreachNext; \ foreachNext = NULL ) \ for ( type object = foreachNext(foreachEnum, @selector(nextObject)); \ object; \ object = foreachNext(foreachEnum, @selector(nextObject)) )
And with that foreacht
, you can now define foreach
like so:
#define foreach(object, collection) foreacht(id, object, (collection))
8 Comments RSS · Twitter
When I saw your original article I wrote some macros of my own, documented here:
http://wincent.com/a/knowledge-base/archives/2006/01/objectivec_enum.php
It's a good improvement that you've used loop-local variables. I did that too. But if you want to be able to nest enumerations you'll find that you still get namespace clashes. One way to solve this is with the concatenation operator (##). To illustrate (don't know if this will be readable or the code will get eaten but going to paste it in anyway):
#define WO_ENUMERATE(collection, object) \
for (id WOMacroEnumerator_ ## object = [collection objectEnumerator], \
WOMacroSelector_ ## object = (id)@selector(nextObject), \
WOMacroMethod_ ## object = (id)[WOMacroEnumerator_ ## object methodForSelector:(SEL)WOMacroSelector_ ## object], \
object = WOMacroEnumerator_ ## object ? \
((IMP)WOMacroMethod_ ## object)(WOMacroEnumerator_ ## object, (SEL)WOMacroSelector_ ## object) : nil; \
object != nil; \
object = ((IMP)WOMacroMethod_ ## object)(WOMacroEnumerator_ ## object, (SEL)WOMacroSelector_ ## object))
/*! Perl-like syntax for WO_ENUMERATE. */
#define foreach(object, collection) WO_ENUMERATE(collection, object)
I haven't seen any problems with namespace clashes. Nesting a foreach creates a new scope for its variables.
Yes, it will compile and run, but I think it's bad practice to rely on the behaviour in general. What do you do if you're in the debugger looking at nested enumerations?
for (int i = 0; i < 5; i++)
{
for (int i = 0; i < 5; i++)
{
// what do I do here if I want to print the value of the outer "i"?
}
}
Well, the truth is that I use the debugger only rarely. I prefer logging.
Anyway, I think your version with ## will have the same problem in the debugger. You'll also have two i's, as well as two of each of the helper _i variables. The main difference is that if you use i for one loop and j for the other, your helper variables will have unique names in the debugger and mine won't. That doesn't bother me because I'll probably be ignoring the helper variables, anyway, and because it's probably bad style to use nested loops both with i.
The main reason I like the loop-scoping (and the ## in the old version of my macro) is that it prevents collisions of the helper variable names when two foreach loops are in sequence or nested.
If you were really worried about collisions of two "_i" variables (really "_object") then you could switch to using "## collection" instead; I can't think of any reason why you'd have nested enumerators iterating over the same collection. The reason why I chose "object" to introduce some uniqueness into the names is that in my usage patterns when using nested enumerators I find that I'm always using two different types of object and so the variable names will naturally be different; things like "title" and "author", or "inner" and "outer" and so forth.
Ugh, that nested for loops solution looks pretty ghastly. What's wrong with the original casts? In all cases the types involved are pointers (id, IMP etc) so there is no danger in casting them.
Yes, I used "## object" in my original macro for the same reason. I just don't see any reason to prefer the "## object" to using loop-scope variables. In any case, both work, so I'll stop discussing this.
It think the casts are perfectly safe, but I agree with Matt that they're a bit unsightly. Though so are the for-loops in their own way, but that's why we have a macro to hide it all. :-)
Here's an interesting alternative. Personally, I think it's too verbose, though.