Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Referencing an NSOperation object in its own completion block with ARC

I'm having difficulty converting some NSOperation code to ARC. My operation object uses a completion block, which in turn contains a GCD block that updates the UI on the main thread. Because I reference my operation object from inside its own completion block, I'm using a __weak pointer to avoid a memory leak. However, the pointer is already set to nil by the time my code runs.

I've narrowed it down to this code sample. Anyone know where I went wrong, and the right way to accomplish this?

NSOperationSubclass *operation = [[NSOperationSubclass alloc] init];
__weak NSOperationSubclass *weakOperation = operation;

[operation setCompletionBlock:^{
    dispatch_async( dispatch_get_main_queue(), ^{

        // fails the check
        NSAssert( weakOperation != nil, @"pointer is nil" );

        ...
    });
}];
like image 635
Marc Charbonneau Avatar asked Feb 10 '12 05:02

Marc Charbonneau


3 Answers

Another option would be:

NSOperationSubclass *operation = [[NSOperationSubclass alloc] init];
__weak NSOperationSubclass *weakOperation = operation;

[operation setCompletionBlock:^{
    NSOperationSubclass *strongOperation = weakOperation;

    dispatch_async(dispatch_get_main_queue(), ^{
        assert(strongOperation != nil);
        ...
    });
}];

[operationQueue addOperation:operation];

I assume you also add operation object to an NSOperationQueue. In that case, the queue is retaining an operation. It is probably also retaining it during execution of the completion block (although I haven't found official confirmation about the completion block).

But inside you completion block another block is created. That block will be run at some point in time later, possibly after NSOperations's completion block is run to an end. When this happens, operation will be released by the queue and weakOperation will be nil. But if we create another strong reference to the same object from operation's completion block, we'll make sure operation will exist when the second block is run, and avoid retain cycle because we don't capture operation variable by the block.

Apple provides this example in Transitioning to ARC Release Notes see the last code snippet in Use Lifetime Qualifiers to Avoid Strong Reference Cycles section.

like image 73
eofster Avatar answered Oct 20 '22 11:10

eofster


I'm not certain about this, but the correct way to do it is possibly to add __block to the variable in question, and then set it to nil at the end of the block to ensure that it is released. See this question.

Your new code would look like this:

NSOperationSubclass *operation = [[NSOperationSubclass alloc] init];
__block NSOperationSubclass *weakOperation = operation;

[operation setCompletionBlock:^{
    dispatch_async( dispatch_get_main_queue(), ^{

        // fails the check
        NSAssert( weakOperation != nil, @"pointer is nil" );

        ...
        weakOperation = nil;
    });

}];
like image 35
leecbaker Avatar answered Oct 20 '22 10:10

leecbaker


Accepted answer is correct. However there is no need to weakify operation as of iOS 8 / Mac OS 10.10:

the quote from NSOperation documentation on @completionBlock:

In iOS 8 and later and OS X v10.10 and later, this property is set to nil after the completion block begins executing.

See also this tweet from Pete Steinberger.

like image 4
Stanislav Pankevich Avatar answered Oct 20 '22 10:10

Stanislav Pankevich