How to cancel NSBlockOperation
Doh. Dear future googlers: of course operation
is nil when copied by the block, but it doesn't have to be copied. It can be qualified with __block
like so:
//THIS MIGHT LEAK! See the update below.
__block NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{
while( ! [operation isCancelled]){
//do something...
}
}];
UPDATE:
Upon further meditation, it occurs to me that this will create a retain cycle under ARC. In ARC, I believe __block
storage is retained. If so, we're in trouble, because NSBlockOperation
also keeps a strong references to the passed in block, which now has a strong reference to the operation, which has a strong reference to the passed in block, which…
It's a little less elegant, but using an explicit weak reference should break the cycle:
NSBlockOperation *operation = [[NSBlockOperation alloc] init];
__weak NSBlockOperation *weakOperation = operation;
[operation addExecutionBlock:^{
while( ! [weakOperation isCancelled]){
//do something...
}
}];
Anyone that has ideas for a more elegant solution, please comment!
To reinforce jemmons answer. WWDC 2012 session 211 - Building Concurent User Interfaces (33 mins in)
NSOperationQueue* myQueue = [[NSOperationQueue alloc] init];
NSBlockOperation* myOp = [[NSBlockOperation alloc] init];
// Make a weak reference to avoid a retain cycle
__weak NSBlockOperation* myWeakOp = myOp;
[myOp addExecutionBlock:^{
for (int i = 0; i < 10000; i++) {
if ([myWeakOp isCancelled]) break;
precessData(i);
}
}];
[myQueue addOperation:myOp];
With Swift 5, you can create a cancellable BlockOperation
with addExecutionBlock(_:)
. addExecutionBlock(_:)
has the following declaration:
func addExecutionBlock(_ block: @escaping () -> Void)
Adds the specified block to the receiver’s list of blocks to perform.
The example below shows how to implement addExecutionBlock(_:)
:
let blockOperation = BlockOperation()
blockOperation.addExecutionBlock({ [unowned blockOperation] in
for i in 0 ..< 10000 {
if blockOperation.isCancelled {
print("Cancelled")
return // or break
}
print(i)
}
})
Note that, in order to prevent a retain cycle between the BlockOperation
instance and its execution block, you have to use a capture list with a weak
or unowned
reference to blockOperation
inside the execution block.
The following Playground code shows how to cancel a BlockOperation
subclass instance and check that there is no retain cycle between it and its execution block:
import Foundation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
class TestBlockOperation: BlockOperation {
deinit {
print("No retain cycle")
}
}
do {
let queue = OperationQueue()
let blockOperation = TestBlockOperation()
blockOperation.addExecutionBlock({ [unowned blockOperation] in
for i in 0 ..< 10000 {
if blockOperation.isCancelled {
print("Cancelled")
return // or break
}
print(i)
}
})
queue.addOperation(blockOperation)
Thread.sleep(forTimeInterval: 0.5)
blockOperation.cancel()
}
This prints:
0
1
2
3
...
Cancelled
No retain cycle