iosobjective-cnsoperationcompletion-block

NSOperation completionBlock is called twice


I'm working on a NSOperation subclass and I came across this very weird issue were the completion block is called twice in a row. The KVO calls seem fine but the completionBlock is still strangely called twice. Am I misunderstanding NSOperation? The documentation says that the completion block is called when isFinished becomes YES and that only happens once in my code:

- (void)main {
    @autoreleasepool {
        [self willChangeValueForKey:@"isExecuting"];
        [self willChangeValueForKey:@"isReady"];
        executing = YES;
        [self didChangeValueForKey:@"isReady"];
        [self didChangeValueForKey:@"isExecuting"];

        //start the operation
    }
}

I then simply set the completionBlock like this:

self.completionBlock = ^{
    NSLog(@"Completed");
}

When it finishes this method is called (it is called just ONCE, i double checked that)

- (void)completeOperation {
    [self willChangeValueForKey:@"isExecuting"];
    [self willChangeValueForKey:@"isFinished"];
    executing = NO;
    completed = YES;
    [self didChangeValueForKey:@"isExecuting"];
    [self didChangeValueForKey:@"isFinished"];
}

But the completionBlock is called twice and prints "Completed" twice into the console.

And here are the methods that indicate the current state:

- (BOOL)isReady {
    if (executing || cancelled || completed) {
        return NO;
    }
    return YES;
}
- (BOOL)isCancelled {
    return cancelled;
}

- (BOOL)isConcurrent {
    return YES;
}

- (BOOL)isExecuting {
    return executing;
}

- (BOOL)isFinished {
    return completed;
}

isCancelled never turns to YES in my testing code so that couldn't be the cause of it.

I really don't get why the completionBlock is called twice. Even when setting the completion block to nil from inside the completion block it is sometimes called twice which is even stranger.


Solution

  • Not sure if this is the cause, but in my experience, there is no need to override the read-only state properties. You are responsible for checking isCancelled periodically in the main loop and if it's set bailing out of whatever you are doing, but I believe the other state flags (isReady, isFinished, isExecuting) are taken care of automatically.

    How many times does it fire if you strip away the state flag handling and just do your process in -main?

    EDIT: Assuming you are overriding those flags to allow concurrency, then you should read through the notes in the docs

    By the looks of it, you should never need to override isReady or isCancelled and instead override -start according to the instructions in the docs.