Implementing NSFastEnumeration
for a custom class requires us to implement one method which will be called when we use for (var in coll) // { .. }
form. Let's say we have a class DLList
which is backed by an array as its main data source. For iterating elements in the DLList
object, we can do as follows.
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id _Nullable __unsafe_unretained [])buffer count:(NSUInteger)len {
if (state->state == [self count]) return 0;
__unsafe_unretained id const *arr = &_array; // (1)
state->itemsPtr = (__typeof__(state->itemsPtr))arr; // (2)
state->mutationsPtr = &state->extra[0]; // (3)
state->state = [self count];
return state->state;
}
Here state->itemsPtr
requires a pointer to any object of type id
. We use _array
and pointer to the _array
can be obtained by getting the address, &_array
. But holding a reference to the variable will change its retain count, which we do not want. So in statement marked (1)
we use __unsafe_unretained id const *
as the type. We don't check for mutation here (3)
as the collection is not being mutated during enumeration.
The Clang documentation on Objective-C Automatic Reference Counting (ARC) discusses the semantics of casts under ARC.
A program is ill-formed if an expression of type
T*
is converted, explicitly or implicitly, to the typeU*
, whereT
andU
have different ownership qualification, unless:
T
is qualified with__strong
,__autoreleasing
, or__unsafe_unretained
, andU
is qualified with bothconst
and__unsafe_unretained
; or
In statement marked (2)
, we then typecast it to the type of state->itemsPtr
which is same as __unsafe_unretained id *
removing the const
, which works because the ownership is the same.
The DLList
class snippet is given below.
@implementation DLList {
NSMutableArray *_array;
}
- (NSUInteger)count {
return [_array count];
}
// ..
Now we can use fast enumeration like:
DLList *list = [[DLList alloc] initWithArray:@[@(1), @(2), @(3)]];
NSNumber *num = nil;
for (num in list) { // fast enumeration which will call the protocol method
NSLog(@"Elems: %@", num);
}