Objective-C 2.0 provides a language feature that allows you to efficiently and safely enumerate over the contents of a collection using a concise syntax.
Objective-C 2.0 provides a language feature that allows you to enumerate over the contents of a collection. The syntax is defined as follows:
for ( Type newVariable in expression ) { stmts } |
or
Type existingItem; |
for ( existingItem in expression ) { stmts } |
In both cases, expression yields an object that conforms to the NSFastEnumeration protocol, typically an array or enumerator (the Cocoa collection classes—NSArray, NSDictionary, and NSSet—adopt this protocol, as does NSEnumerator). Any class whose instances provide access to a collection of other objects can adopt the NSFastEnumeration protocol (see “Implementing the NSFastEnumeration Protocol”). It should be obvious that in the cases of NSArray and NSSet the enumeration is over their contents. Other classes should make clear what property is iterated over—for example, NSDictionary and the Core Data class NSManagedObjectModel provide support for fast enumeration; NSDictionary enumerates its keys, and NSManagedObjectModel enumerates its entities.
There are several advantages to using fast enumeration:
The enumeration is considerably more efficient than, for example, using NSEnumerator directly.
The syntax is concise.
Enumeration is “safe”—the enumerator has a mutation guard so that if you attempt to modify the collection during enumeration, an exception is raised.
Since mutation of the object during iteration is forbidden, you can perform multiple enumerations concurrently.
The following code example illustrates using fast enumeration with NSArray and NSDictionary objects.
NSArray *array = [NSArray arrayWithObjects: |
@"One", @"Two", @"Three", @"Four", nil]; |
for (NSString *element in array) { |
NSLog(@"element: %@", element); |
} |
NSDictionary *dictionary = [NSDictionary dictionaryWithObjectsAndKeys: |
@"quattuor", @"four", @"quinque", @"five", @"sex", @"six", nil]; |
NSString *key; |
for (key in dictionary) { |
NSLog(@"English: %@, Latin: %@", key, [dictionary valueForKey:key]); |
} |
You can also use NSEnumerator objects with fast enumeration, as illustrated in the following example:
NSArray *array = [NSArray arrayWithObjects: |
@"One", @"Two", @"Three", @"Four", nil]; |
NSEnumerator *enumerator = [array reverseObjectEnumerator]; |
for (NSString *element in enumerator) { |
if ([element isEqualToString:@"Three"]) { |
break; |
} |
} |
NSString *next = [enumerator nextObject]; |
// next = "Four" |
For collections or enumerators that have a well-defined order—such as NSArray or NSEnumerator instance derived from an array—the enumeration proceeds in that order, so simply counting iterations will give you the proper index into the collection if you need it.
NSArray *array = /* assume this exists */; |
NSInteger index = 0; |
BOOL ok = NO; |
for (id element in array) { |
if (/* some test for element */) { |
ok = YES; |
break; |
} |
index++; |
} |
if (ok) { |
NSLog(@"Test passed by element at index %d", index"); |
} |
To implement a custom class that supports fast enumeration, you must implement the NSFastEnumeration protocol. The protocol comprises a single method—countByEnumeratingWithState:objects:count:.
@protocol NSFastEnumeration |
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id *)stackbuf count:(NSUInteger)len; |
@end |
The first parameter is an enumeration structure (NSFastEnumerationState):
typedef struct { |
unsigned long state; |
id *itemsPtr; |
unsigned long *mutationsPtr; |
unsigned long extra[5]; |
} NSFastEnumerationState; |
The following rules must be obeyed by any implementation:
On first entry, the "state" field will be 0, otherwise the client is neither allowed or required to alter state. State must be set to a non-zero before returning a non-zero count (otherwise the client will go into an infinite loop expecting a zero return, and the object will always be starting a new enumeration). For complicated enumerations "extra" is supplied to hold extra state.
On exit, a count of 0 means that there are no objects available for enumeration. This may reflect a bad or inconsistent internal state, or more normally it may simply reflect that no more objects are available. But if the value is zero, the client can make no assumptions about the content of state.
A non-zero count return implies that itemsPtr has been set to point to the first of the non-zero count objects. This is encouraged to be a pointer to internal object state where feasible. If not feasible, the client has supplied a stack buffer at "objects" and the length in the "count" parameter, and the object pointers to be iterated should be copied there (no GC primitives required). In addition, mutationsPtr is set to a valid address that should track some form of change counter for this object. If the object is immutable, this pointer could point to the object itself.
The enumeration must traverse in order collections or enumerators that have a well-defined order, such as NSArray.
The protocol is designed to work when sent to nil objects.
When countByEnumeratingWithState:objects:count: is invoked, the receiver is passed an enumeration structure with the first field state set to zero. When enumeration is ended, this method returns 0. Items are returned indirectly by setting the itemsPtr field. The mutationsPtr field must be set to point to a location that changes if your object is mutated. If mutations are not possible, this pointer can be set to the object itself, whose first field will never change. If the stackbuf argument is supplied, you can use it as a buffer to hold several copied items, or the “extra” state fields may be used to hold the returned values. You can use the state fields in any way necessary to perform the iteration. The state structure is assumed to be of stack local memory and—from a garbage collection perspective—does not require write-barriers on stores, so you can recast the passed-in state structure to one more suitable for your iteration.
The compiler transforms the following code:
for ( id elem in collection ) { stmts } |
into:
{ |
id elem; |
NSEnumerationState __enumState = { 0 }; |
id __items[16]; |
unsigned long __limit = |
[collection countByEnumeratingWithState:&__enumState objects:__items count:16]; |
unsigned __counter = 0; |
unsigned long __startMutations = * __enumState.mutationsPtr; |
if (__limit) do { |
__counter = 0 |
do { |
if (__startMutations != * __enumState.mutationsPtr) objc_enumerationMutation(); |
elem = __enumState.itemsPtr[__counter++]; |
stmts; |
} while (__counter0 < __limit); |
} while (__limit = [collection countByEnumeratingWithState:&__enumState objects:__items count:16]); |
}; |
First, the state field is set to 0 to indicate the first time entry. Next, countByEnumeratingWithState:objects:count: is invoked which returns the count of the first batch of items to iterate. If this is zero, the enumeration is finished, otherwise the program enters an inner loop to iterate through the items ready at the end of which it exits to the outer loop to get another batch. For each item, the compiler first checks to see if the collection has been mutated before attempting to dereference the indirect pointer. If the collection has been mutated, an exception is raised. If it has not, the item is extracted and control passes to the stmts provided.
Last updated: 2008-02-05