
Swift Regex Deep Dive
iOS MacOur introductory guide to Swift Regex. Learn regular expressions in Swift including RegexBuilder examples and strongly-typed captures.
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.
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
###
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.
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.
Our introductory guide to Swift Regex. Learn regular expressions in Swift including RegexBuilder examples and strongly-typed captures.
The Combine framework in Swift is a powerful declarative API for the asynchronous processing of values over time. It takes full advantage of Swift...
SwiftUI has changed a great many things about how developers create applications for iOS, and not just in the way we lay out our...