Sunday, September 1, 2019

Implement NSFastEnumeration

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.

Section 4.3.3 of the Automatic Reference counting documentation 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 type U*, where T and U have different ownership qualification, unless:
* T is qualified with __strong, __autoreleasing, or __unsafe_unretained, and U is qualified with both const 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);