The For...In Loop
We've looked at the standard for
loop for the last
two
weeks. This
week we will dive into the for...in
loop introduced in
Objective-C 2.0. Unlike the standard for
loop, the
for...in
loop is not available in plain old C.
In Objective-C, collection classes such as
NSArray
,
NSSet
and
NSDictionary
are a key part of the Objective-C Foundation framework. Collections
provide high level ways to group and organize objects.
In older versions of Objective-C, looping over items in a collection is
done using an
NSEnumerator
object in a while
loop:
NSSet *items = [NSSet setWithObjects:@"foo", @"bar", nil];
NSEnumerator *enumerator = [items objectEnumerator];
NSString *item = nil;
while (item = [enumerator nextObject]) {
// do something with item
}
It's possible to loop over items in a NSArray
using a
standard for
loop, since NSArray
has a well
defined order:
NSArray *items = [NSArray arrayWithObjects:@"foo", @"bar", nil];
for (NSUInteger i = 0; i < [items count]; i++) {
NSString *item = [items objectAtIndex:i];
// do something with item
}
Unfortunately, some collection classes (such as NSSet
)
don't have a well defined order; NSEnumerator
used to be
the only option.
The for...in
loop works on any collection that conforms to
the NSFastEnumeration
protocol (all the standard ones do).
The for...in
loop is similar to a standard
for
loop but simpler. Instead of the three sections of a
standard for
loop, there are two, the loop
variable and the collection expression:
for (loop variable in collection expression) {
// do something with loop variable
}
Loop Variable
The loop variable can be a previous declared variable:
NSString *item = nil;
// ...
for (item in collection expression) {
// do something with item
}
or it can be declared inside the parentheses:
for (NSString *item in collection expression) {
// do something with item
}
Collection Expression
The collection expression can be any expression that evaluates
to an object conforming to NSFastEnumeration
. Typically,
this is simply a collection variable defined elsewhere:
NSSet *items = [NSSet setWithObjects:@"foo", @"bar", nil];
// ...
for (NSString *item in items) {
// do something with item
}
but can be a function call or method call:
for (NSString *item in [NSSet setWithObjects:@"foo", @"bar", nil]) {
// do something with item
}
Dictionaries
When using a for...in
loop with a
NSDictionary
, the loop variable receives the dictionary
keys; to work with the dictionary values inside the loop, use
objectForKey:
NSDictionary *numbers = [NSDictionary dictionaryWithObjectsAndKeys:
@"zero", @"0",
@"one", @"1",
nil];
for (NSString *key in numbers) {
NSString *value = [numbers objectForKey:key];
// do something with key and value
}
Mutation Guard
Modifying a collection while iterating over it can cause very
unintuitive behavior, so the for...in
loop uses a
mutation guard behind the scenes. If items are added or
removed from a collection while your for...in
loop is
running, an exception is thrown. This is generally a good thing, but
it makes filtering a collection somewhat tricky:
NSMutableSet *items = [NSMutableSet setWithObjects:@"", @"a", @"aa", @"aaa", nil];
for (NSString *item in items) {
if (item.length < 2) {
[items removeObject:item]; // WARNING: exception thrown on mutation
}
}
The way to get around this restriction is to iterate over a copy of the
collection and modify the original (or vice versa):
NSMutableSet *items = [NSMutableSet setWithObjects:@"", @"a", @"aa", @"aaa", nil];
for (NSString *item in [[items copy] autorelease]) {
if (item.length < 2) {
[items removeObject:item]; // OKAY: looping over copy, changing original
}
}
Next week, we
will look at the while
loop.