How to cancel NSBlockOperation

I have a long work cycle that I want to run in the background using NSOperation . I would like to use a block:

 NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ while(/* not canceled*/){ //do something... } }]; 

The question is how can I check if it is canceled. The block does not accept any arguments, and operation is zero when it is captured by the block. Is there no way to cancel block operations?

+44
objective-c-blocks nsoperation nsoperationqueue
Nov 13 '11 at 17:20
source share
4 answers

Doh. Dear future googlers: of course, operation is zero when copying in block, but it does not need to be copied. It can be qualified with __block as follows:

 //THIS MIGHT LEAK! See the update below. __block NSBlockOperation *operation = [NSBlockOperation blockOperationWithBlock:^{ while( ! [operation isCancelled]){ //do something... } }]; 

UPDATE:

With further meditation, it occurs to me that this will create a save cycle in ARC. In ARC, I believe that __block storage __block preserved. If so, we have problems because NSBlockOperation also maintains 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 the block, which ...

It is slightly less elegant, but should use an explicit weak reference to break the loop:

 NSBlockOperation *operation = [[NSBlockOperation alloc] init]; __weak NSBlockOperation *weakOperation = operation; [operation addExecutionBlock:^{ while( ! [weakOperation isCancelled]){ //do something... } }]; 

Anyone who has ideas for a more elegant solution, please comment!

+63
Nov 13 '11 at 17:25
source share

To enhance the response. WWDC 2012 Session 211 - Creating Compatible User Interfaces (33 minutes)

 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]; 
+42
Oct 23
source share

With Swift 4, you can create Undo BlockOperation with addExecutionBlock(_:) . addExecutionBlock(_:) has the following declaration :

 func addExecutionBlock(_ block: @escaping () -> Void) 

Adds the specified block to the list of recipients of the blocks to execute.




The following example 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 to prevent a save cycle between an instance of BlockOperation and its execution unit, you need to use a capture list with a weak or unowned reference to BlockOperation inside the executable block.




The following playground code shows how to check if there is a save cycle between an instance of the BlockOperation subclass and its execution unit:

 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() } 

Fingerprints:

 1 2 3 ... Cancelled No retain cycle 
+2
Jun 19 '17 at 23:45
source share

I wanted to have undo blocks that my UICollectionViewController could easily undo when cells were scrolled from the screen. Blocks do not perform network operations, they perform operations with images (resizing, cropping, etc.). The blocks themselves should have a link to check if their op has been canceled, and none of the other answers (at the time I wrote this) provided that.

Here, what worked for me (Swift 3) is creating blocks that take a weak link to BlockOperation , and then complete them in the BlockOperation block BlockOperation :

  public extension OperationQueue { func addCancellableBlock(_ block: @escaping (BlockOperation?)->Void) -> BlockOperation { let op = BlockOperation.init() weak var opWeak = op op.addExecutionBlock { block(opWeak) } self.addOperation(op) return op } } 

Using it in my UICollectionViewController :

 var ops = [IndexPath:Weak<BlockOperation>]() func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { ... ops[indexPath] = Weak(value: DispatchQueues.concurrentQueue.addCancellableBlock({ (op) in cell.setup(obj: photoObj, cellsize: cellsize) })) } func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { if let weakOp = ops[indexPath], let op: BlockOperation = weakOp.value { NSLog("GCV: CANCELLING OP FOR INDEXPATH \(indexPath)") op.cancel() } } 

Image completion:

  class Weak<T: AnyObject> { weak var value : T? init (value: T) { self.value = value } } 
0
Jun 29 '17 at 6:29
source share



All Articles