Search

Fast Enumeration, part 1

Mark Dalrymple

5 min read

Oct 31, 2012

Fast Enumeration, part 1

Fast Enumeration was introduced into Objective-C back in the 10.5 days. It’s the feature that lets you succinctly iterate through a collection:

NSArray *strings =
    [NSArray arrayWithObjects: @"greeble", @"bork", @"hoover", nil];
for (NSString *thing in strings) {
    NSLog (@"Woo! %@", thing);
}

Back in the moldy old days, we had to use NSEnumerator to do the same thing:

NSEnumerator *enumerator = [strings objectEnumerator];
NSString *thing;
while ((thing = [enumerator nextObject])) {
    NSLog (@"woo.  %@", thing);
}

So why the change? What was wrong with the NSEnumerator technique?

There really isn’t anything wrong with NSEnumerator. It’s just clumsy. You need to make an object (which requires a dynamic memory allocation). You then have to ask the enumerator nicely for each object (which requires an objective-C message send for each one). That’s a fair amount of overhead, especially if you’re iterating over small collections.

NSFastEnumeration lets classes, both Apple’s and ours, figure out what’s the optimal way to iterate its contents. You can often avoid memory allocation as well as a message send for every single object in the collection. Plus it’s fewer lines of code and fewer additional local variables.

Enumerators still have their place

NSEnumerator still has its place, though. If you’re wanting some collection of objects in the form of an array, you can ask an enumerator for -allObjects. It’ll spin a loop behind the scenes and accumulate the stuff into an array.

An enumerator object can provide additional data that’s out of band from simply returning a sequence of objects. Consider NSDirectoryEnumerator. A directory enumerator will feed you the file name in a directory, recursively:

NSFileManager *fileManager = [NSFileManager defaultManager];
NSDirectoryEnumerator *direnum = [fileManager enumeratorAtPath: @"/usr/share/dict"];
NSString *filename;
while ((filename = [direnum nextObject])) {
    NSLog (@"file!  %@", filename);
}

Which prints out

file!  connectives
file!  propernames
file!  README
file!  web2
file!  web2a
file!  words

Inside of the loop you can ask the enumerator what some of the -fileAttributes are. You can tell it to -skipDescendants (or -skipDescendents – take your pick) if you decide that you don’t want to recurse any more in the current directory. Here’s the same loop, but getting the file sizes too:

while ((filename = [direnum nextObject])) {
    NSDictionary *fileAttributes = [<strong>direnum fileAttributes</strong>];
    NSLog (@"file!  %@ : %llu", filename, [fileAttributes fileSize]);
}

This prints out the files and their sizes:

file!  connectives : 706
file!  propernames : 8546
file!  README : 1689
file!  web2 : 2493109
file!  web2a : 1012731
file!  words : 4

###

Miscibility

One nice feature of fast enumeration is that you can feed it NSEnumerators. It works just the same, but with the nicer syntax:

<strong>direnum</strong> = [fileManager enumeratorAtPath: @"/usr/share/dict"];
for (NSString *filename in <strong>direnum</strong>) {
    NSDictionary *fileAttributes = [<strong>direnum</strong> fileAttributes];
    NSLog (@"file!  %@ : %llu", filename, [fileAttributes fileSize]);
}

Ever wondered if you can fast enumerate backwards through an array? You can, by getting an array’s -reverseObjectEnumerator and fast enumerating through that:

NSArray *strings =
    [NSArray arrayWithObjects: @"greeble", @"bork", @"hoover", nil];
NSEnumerator *rotaremune = [strings reverseObjectEnumerator];
for (NSString *thing in rotaremune) {
    NSLog (@"!ooW %@", thing);
}

This runs through the list backwards:

!ooW hoover
!ooW bork
!ooW greeble

Granted, this still has the overhead penalty of NSEnumerator, but it’s convenient.

Just passing through

Fast enumeration is pretty nice. Apple has opened the playground for us to play in, too. We can have our own class participate, as first-class citizens, in the world of fast enumeration. To do so, all you have to do is adopt the NSFastEnumeration protocol, which has a single method you need to implement:

- (NSUInteger) countByEnumeratingWithState: (NSFastEnumerationState *) enumerationState
                                   objects: (id __unsafe_unretained []) stackBuffer
                                     count: (NSUInteger) length;

That’s somewhat frightening. At least it’s only one method.

Part 2 will do a look at how this method actually works. But if your class uses one of Apple’s collections as a backing store, you can pass the implementation right through. This will give your users the convenience of fast enumerating through your stuff, while letting Apple do the hard work.

Say your software models the members of an orchestra. An orchestra has musicians and trombone players, so you need a class to capture their state. (The code can be found at this gist)

@interface Musician : NSObject
+ (id) musicianWithName: (NSString *) name  instrument: (NSString *) instrument;
@property (copy, nonatomic) NSString *name;
@property (copy, nonatomic) NSString *instrument;
@end // Musician

As well as the orchestra. Here’s the start of that class:

@interface Orchestra : NSObject
- (void) addMusician: (Musician *) musician;
@end // Orchestra

@implementation Orchestra {
    NSMutableArray *_members;
}
- (void) addMusician: (Musician *) musician {
    if (!_members) _members = [NSMutableArray array];
    [_members addObject: musician];
} // addMusician

@end // Orchestra

Pretty straightforward stuff. Get a musician object, add it to the array of members. It’s pretty easy to populate too:

Orchestra *edgewoodSymphony = [[Orchestra alloc] init];
Musician *peggy = [Musician musicianWithName: @"Peggy"  instrument: @"Flaut"];
[edgewoodSymphony addMusician: peggy];
Musician *jim = [Musician musicianWithName: @"Jim"  instrument: @"Bassoon"];
[edgewoodSymphony addMusician: jim];

Now comes the time to design how users of Orchestra are going to access the members. We could add a property that returns an array, and then callers can get that array and fast enumerate through it. That’s fine. There are some complications : the orchestra has the members stored as mutable array in an instance variable. It’d be dangerous to just blindly return that mutable array, lest someone accidentally or maliciously modify it behind our backs. See About Mutability for a meditation on mutability. To address that, Orchestra could return a copy of the array.

Or, it could support fast enumeration.

Because the Orchestra is backed by a single Apple collection, NSArray, we can just pass-through the fast enumeration method.

First update the interface to declare our love and admiration of NSFastEnumeration:

@interface Orchestra : NSObject <strong><NSFastEnumeration></strong>
- (void) addMusician: (Musician *) musician;
@end // Orchestra

And add the required method, which just turns around and passes the arguments through to the array:

- (NSUInteger) countByEnumeratingWithState: (NSFastEnumerationState *) enumerationState
                                   objects: (id __unsafe_unretained []) buffer
                                     count: (NSUInteger) len {
    return [_members countByEnumeratingWithState: enumerationState
                     objects: buffer
                     count: len];
} // countByEnumeratingWithState

And now, the orchestra is fast-enumeratable:

for (Musician *member in edgewoodSymphony) {
    NSLog (@"%@ plays the %@", member.name, member.instrument);
}

Which prints out

Peggy plays the Flaut
Jim plays the Bassoon

So you can see it can be nearly trivial to support fast enumeration in your own classes.

Next week we’ll look at the guts of this call, and how you might support it with your own collection classes.

Jazzed about enumeration? This material is also covered in Advanced Mac OS X Programming : The Big Nerd Ranch Guide, along with an application of NSFastEnumeration that doesn’t actually iterate a collection of objects.

Mark Dalrymple

Author Big Nerd Ranch

MarkD is a long-time Unix and Mac developer, having worked at AOL, Google, and several start-ups over the years.  He’s the author of Advanced Mac OS X Programming: The Big Nerd Ranch Guide, over 100 blog posts for Big Nerd Ranch, and an occasional speaker at conferences. Believing in the power of community, he’s a co-founder of CocoaHeads, an international Mac and iPhone meetup, and runs the Pittsburgh PA chapter. In his spare time, he plays orchestral and swing band music.

Speak with a Nerd

Schedule a call today! Our team of Nerds are ready to help

Let's Talk

Related Posts

We are ready to discuss your needs.

Not applicable? Click here to schedule a call.

Stay in Touch WITH Big Nerd Ranch News