90

I've been using enumerateObjectsUsingBlock: a lot lately for my fast-enumeration needs, and I'm having a hard time understanding the usage of BOOL *stop in the enumeration block.

The NSArray class reference states

stop: A reference to a Boolean value. The block can set the value to YES to stop further processing of the array. The stop argument is an out-only argument. You should only ever set this Boolean to YES within the Block.

So then of course I can add the following in my block to stop the enumeration:

if (idx == [myArray indexOfObject:[myArray lastObject]]) {
    *stop = YES;
}

From what I've been able to tell, not explicitly setting *stop to YES doesn't have any negative side effects. The enumeration seems to automatically stop itself at the end of the array. So is using *stop really necessary in a block?

Mick MacCallum
  • 129,200
  • 40
  • 280
  • 281

1 Answers1

158

The stop argument to the Block allows you to stop the enumeration prematurely. It's the equivalent of break from a normal for loop. You can ignore it if you want to go through every object in the array.

for( id obj in arr ){
    if( [obj isContagious] ){
        break;    // Stop enumerating
    }

    if( ![obj isKindOfClass:[Perefrigia class]] ){
        continue;    // Skip this object
    }

    [obj immanetizeTheEschaton];
}

[arr enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    if( [obj isContagious] ){
        *stop = YES;    // Stop enumerating
        return;
    }

    if( ![obj isKindOfClass:[Perefrigia class]] ){
        return;    // Skip this object
    }

    [obj immanentizeTheEschaton];
}];

That is an out parameter because it is a reference to a variable from the calling scope. It needs to be set inside your Block, but read inside of enumerateObjectsUsingBlock:, the same way NSErrors are commonly passed back to your code from framework calls.

- (void)enumerateObjectsUsingBlock:(void (^)(id obj, NSUInteger idx, BOOL *stop))block {
    // N.B: This is probably not how this method is actually implemented!
    // It is just to demonstrate how the out parameter operates!

    NSUInteger idx = 0;
    for( id obj in self ){

        BOOL stop = NO;

        block(obj, idx++, &stop);

        if( stop ){
            break;
        }
    }
}
jscs
  • 63,694
  • 13
  • 151
  • 195
  • 21
    Note that the `stop` flag is advisory; the enumeration may continue for some undefined number of iterations in the concurrent case, for example. I.e. you shouldn't set an `__block` variable unconditionally on each pass through the enumeration and expect it to be the "last" value when using `stop` to early terminate. You should always couple the "no, use this object" with setting `stop = YES;`. – bbum Sep 10 '12 at 19:31
  • @bbum, can you clarify whether the continuing behavior applies _only_ to concurrent enumeration? While it's perfectly understandable in that case, it isn't documented, and would be quite a surprising bite in the ass for "serial" enumeration. – jscs Sep 17 '13 at 07:43
  • 4
    now asks for clarification as the documentation doesn't say either way and it should. – bbum Sep 17 '13 at 23:29