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.












