Fast Enumeration, Massive Inefficiency

Cocoa makes use of a system called Fast Enumeration, which allows you to write array loops in a compact fashion and is also supposed to speed things up by getting [myArray nextObject] rather than getting [myArray objectAtIndex:nextIndex] on each pass through the loop. Here’s an example:

for (NSObject *anObject in myArrayOfObjects) {
    //do stuff
}

Because I’m an idiot, I had been using Fast Enumeration in situations where I needed to know the index of each object because I couldn’t be bothered to write out the code for incrementing an index. This resulted in incredibly readable stuff like the excrescence below:

for (NSObject *anObject in myArrayOfObjects) {
    if ([myArrayOfObjects indexOfObject:anObject] < ([myArrayOfObjects count] - 1) {
        NSObject *theNextObject = [myArrayOfObjects objectAtIndex:([myArrayOfObjects indexOfObject:anObject] + 1)];
        [self doSomethingWithObject:anObject andNextObject:theNextObject];
    }
}

In case your eyes are bleeding too much for you to be able to see how hard I’ve made things for myself, here’s a rundown of what the above code actually does:

  1. Okay, I want to enumerate my array, but I can’t for bothered to write out “for (int i = 0; i < [myArrayOfObjects count]; i ++)"
  2. I’ll use the Fast Enumeration syntax. That’s quick and easy to read!
  3. Hmm, I need to compare each object with the next object in the array…. but what do I do if I reach the end of the array? My code will try to get the nonexistent next object and crash!
  4. Okay, well let’s make sure we don’t try to get the next object if we’re at the end of the array. Hang on, how do I find out where I am in the array? I don’t even have the index of my current object.
  5. Right, well the parent NSArray knows the index, doesn’t it? Okay, I’ll ask it….
  6. Great! I’ve got the index of the current object and avoided a crash. Now how do I get the next object? Well, asking the NSArray for an index worked fine last time, let’s do it again but increment the index by 1
  7. Okay, make the calculation
  8. Now let’s head back to the start of the loop again! I wonder how much of my own time I can waste on the next iteration?

I crossed the border between Merely Misguided and Profoundly Stupid when I started querying the array about the index of the object I wanted, because computers don’t run on magic but actually need to have a real method for doing something. In the case of myObject = [myArray objectAtIndex:[myArray indexOfObject:myObject]] you’re actually iterating through the NSArray and doing an isEqual: comparison on every element until you reach the object you already have – so instead of enumerating my array once, I’m actually enumerating it by (approximately) the square of the number of elements. Efficiency!

My attempt to write neat, easy-to-read code was actually doing this:

for (NSObject *anObject in myArrayOfObjects) {
    NSUInteger the_index_of_my_object = 0;
    for (int i = 0; i < [myArrayOfObjects count]; i ++) {
        if ([[myArrayOfObjects objectAtIndex:i] isEqual:anObject]) {
            the_index_of_my_object = i;
            break;
        }
    }
 
    if (the_index_of_my_object < ([myArrayOfObjects count] - 1) {
        for (int j = 0; j < [myArrayOfObjects count]; j ++) {
            if ([[myArrayOfObjects objectAtIndex:i] isEqual:anObject]) {
                the_index_of_my_object = j;
                break;
            }
        }
        NSObject *theNextObject = [myArrayOfObjects objectAtIndex:(j + 1)];
        [self doSomethingWithObject:anObject andNextObject:theNextObject];
    }
}

[anObject isEqual:anotherObject] comparisons are expensive and enumerating the entire array (2 * [array count] * [array count]) times isn’t exactly cheap, and I’ve been multiplying those costs together.

In conclusion – if you’re going to enumerate an array and you need to know the index of each object, then you’re much better off doing it the old fashioned way, both in terms of readability and in terms of speed:

for (int i = 0; i < ([myArrayOfObjects count] - 1); i ++) {
    NSObject *firstObject = [myArrayOfObjects objectAtIndex:i];
    NSObject *nextObject = [myArrayOfObjects objectAtIndex:(i + 1)];
    [self doSomethingWithObject:firstObject andNextObject:nextObject];
}

Leave a Reply

Your email address will not be published. Required fields are marked *