Range Over Iterators in Objective-C

Here is the translation of the given code example into Objective-C:


Starting with version 1.23, Go has added support for iterators, which lets us range over pretty much anything!

Let’s look at the List type from the previous example again. In that example, we had an AllElements method that returned a slice of all elements in the list. With Go iterators, we can do it better - as shown below.

@interface Element<ObjectType> : NSObject {
    Element *_Nullable next;
    ObjectType value;
}
@property (nonatomic, strong) Element *_Nullable next;
@property (nonatomic, strong) ObjectType value;
@end

@implementation Element
@end

@interface List<ObjectType> : NSObject {
    Element<ObjectType> *_Nullable head;
    Element<ObjectType> *_Nullable tail;
}
- (void)push:(ObjectType)value;
- (void)all:(BOOL (NS_NOESCAPE ^ _Nonnull)(ObjectType value))yield;
@end

@implementation List
- (void)push:(id)value {
    Element *newElement = [Element new];
    newElement.value = value;
    
    if (!self.tail) {
        self.head = newElement;
        self.tail = newElement;
    } else {
        self.tail.next = newElement;
        self.tail = newElement;
    }
}

- (void)all:(BOOL (NS_NOESCAPE ^ _Nonnull)(id value))yield {
    for (Element *e = self.head; e != nil; e = e.next) {
        if (!yield(e.value)) {
            return;
        }
    }
}
@end

All returns an iterator, which in Go is a function with a special signature.

The iterator function takes another function as a parameter, called yield by convention (but the name can be arbitrary). It will call yield for every element we want to iterate over, and note yield’s return value for a potential early termination.

BOOL genFib(BOOL (NS_NOESCAPE ^ _Nonnull)(NSInteger value)) {
    NSInteger a = 1, b = 1;
    while (yield(a)) {
        NSInteger next = a + b;
        a = b;
        b = next;
    }
    return YES;
}

Iteration doesn’t require an underlying data structure, and doesn’t even have to be finite! Here’s a function returning an iterator over Fibonacci numbers: it keeps running as long as yield keeps returning true.

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        List<NSNumber *> *list = [List new];
        [list push:@10];
        [list push:@13];
        [list push:@23];
        
        [list all:^BOOL(NSNumber *value) {
            NSLog(@"%@", value);
            return YES;
        }];
        
        NSMutableArray<NSNumber *> *allValues = [NSMutableArray new];
        [list all:^BOOL(NSNumber *value) {
            [allValues addObject:value];
            return YES;
        }];
        NSLog(@"all: %@", allValues);
        
        genFib(^BOOL(NSInteger value) {
            if (value >= 10) {
                return NO;
            }
            NSLog(@"%ld", (long)value);
            return YES;
        });
    }
    return 0;
}

Since List.All returns an iterator, we can use it in a regular for loop.

Packages like slices have a number of useful functions to work with iterators. For example, Collect takes any iterator and collects all its values into a slice.

Once the loop hits break or an early return, the yield function passed to the iterator will return false.

10
13
23
all: (10, 13, 23)
1
1
2
3
5
8

Next example: Errors.