Introducing MFIndexSetForeach

Index sets can be a little annoying in Objective-C. Unlike arrays, sets and dictionaries, you can’t iterate over them using a straightforward for (a in b) loop. That’s because unlike other Objective-C containers, index sets contains integers, not objects. Now, since Mac OS X 10.6 you can use a block to iterate over an index set, but this isn’t always ideal: breaking out of the loop is more complicated, and returning from the block doesn’t return from the outer function. So what can we do better?

In an ideal world you could write this and it’d work, but alas it doesn’t:

for (NSUInteger index in indexSet)
{
    // loop body
}

What I managed to achieve using some macro wizardry is this:

MFIndexSetForeach(index, indexSet)
{
    // loop body
}

MFIndexSetForeach iterates over all indexes in a set. It uses the getIndexes:maxCount:inIndexRange: method of NSIndexSet to fill a small buffer of indexes, iterates over the buffer then refills the buffer and continue iterating until all indexes have been iterated over.

This loop behaves in every way like you’d expect a loop to behave: you can break from it, you can continue in it, and if you return it’ll truly return from the function:

MFIndexSetForeach(index, indexSet)
{
    if (index % 33) continue;
    if (index % 22) break;
    if (index % 11) return YES;
}

Oh, and the braces are optional too.

The equivalent generated code would look somewhat like this:

NSRange _indexRange = NSMakeRange(0, NSUIntegerMax);
for (NSUInteger index,
     _bufferIndex = _bufferLength-1,
     _indexCount = 0,
     _indexBuffer[_bufferLength];
     _MFIndexSetForeachNextIndex(
         &_bufferIndex, &_indexCount, indexSet, _indexBuffer,
         _bufferLength, &_indexRange, &index);
    )
{
    if (index % 33) continue;
    if (index % 22) break;
    if (index % 11) return YES;
}

where, _MFIndexSetForeachNextIndex is an inline function doing all the work.

Have fun with it: Index Set Foreach


Follow-ups


  • © 2003–2017 Michel Fortin.