6

I've read many many articles which say "BLOCKS ARE THE FUTURE!!!". I'm wondering if it relates to running operations in the background.

For example, I have a table view which has images that will come from the web. Right now I can get them using +[NSOperationQueue addOperationWithBlock:]. An operation gets sent to the queue when the cell becomes visible. But is there a way to cancel it once the cell gets scrolled off the screen? Or is the only way to do it to subclass NSOperation? Using blocks is so easy, so I'm just asking this question before I try to tackle this example of NSOperation subclass...

jscs
  • 63,694
  • 13
  • 151
  • 195
denikov
  • 877
  • 2
  • 17
  • 35
  • 1
    I know it is just an example, but you probably shouldn't make these kind of requests from the cell itself without any other layer in the middle. AFNetworking's setImage:withURL: can be cancelled. – Fernando Mazzon Jun 06 '14 at 17:10
  • @FernandoMazzon thanks for the reply. I forgot to mention I wanted to learn the core of how it works before using a framework. Can you elaborate on what you mean about another layer in the middle? – denikov Jun 06 '14 at 17:17
  • 1
    I meant that if you shouldn't have your cells add network operations directly to a shared queue. Instead, you'd have some class in the middle that manages downloads. The cells needn't know how those downloads are handled. That way, if you decide to change how the download is done, or even start using a third party library for it, you wouldn't have to modify your cells since it's ENCAPSULATED in your downloader class. If you had to modify all your UI to account for a network library change, you'd have a problem in your design. Makes sense? – Fernando Mazzon Jun 06 '14 at 17:25
  • Ok that makes sense. Just get that operation out of the cellForRow method. But as to my original question...is there any way to cancel the block or does everyone still have to subclass NSOperation? – denikov Jun 06 '14 at 17:42
  • Yes, you can do it. Added as response. – Fernando Mazzon Jun 06 '14 at 17:47

4 Answers4

11

The question appears to be whether you can create a cancelable NSBlockOperation. As this answer suggests, quoting from WWDC 2012 session #211, Building Concurrent User Interfaces, you certainly can.

This approach consists of limitations, though. Notably, you have to put the cancellation logic in your block. This works fine if your block is running some loop in which it can repeatedly check the isCancelled status. But if you're in the middle of some network request, this is going to be awkward to perform in a NSBlockOperation.

Using the pattern outlined in that other answer (and that WWDC 2012 video), you could write a NSBlockOperation which employed a tortured combination of block-based NSURLSession and a polling loop which cancels the NSURLSessionTask if the operation is canceled, which accomplishes what you intend, but it's a horrible solution (inefficient, cumbersome, encumbering your app code with cancellation logic in the block, etc.).

If you want to make a cancelable network operation, a NSOperation subclass is going to be a far more elegant way to do this. The first time you do this, it's going to seem cumbersome, but once you familiarize yourself with the pattern, it becomes second nature and trivial to implement. And you'll find yourself coming back to this pattern again and again. See the Defining a Custom Operation Object section of the Operation Queues chapter of the Concurrency Programming Guide for discussions about making cancelable, concurrent operations, notably the discussion about "Responding to Cancellation Events".

As a final observation, you describe this "use blocks" and NSOperation-subclass as an "either/or" proposition. Frequently, though, you actually marry the two techniques, creating an NSOperation subclass that takes block parameters that specify what you want to do when the download is done. See AFNetworking as a wonderful example of how to marry blocks and NSOperation subclass.

Community
  • 1
  • 1
Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • 1
    Thanks Rob for a clear explanation. Once again I came back to this question, as you helped me in the past. I've put it off for some time trying to figure out subclassing NSOperation, but it's time now. – denikov Jun 06 '14 at 19:21
  • 1
    @denikov BTW, if you're interested, a year ago I posted an answer showing someone how to wrap a `NSURLConnection`-based network request in a cancelable, concurrent `NSOperation` subclass: http://stackoverflow.com/a/18173953/1271826. Maybe that can help jumpstart your efforts... – Rob Jun 06 '14 at 19:54
1

On a side note, Checkout the WWDC 2015 session, it's a great example of how you can use NSOperations in your project:

https://developer.apple.com/videos/wwdc/2015/?id=226

About your cancellable blocks, you should checkout ReactiveCocoa. For me it's a perfect solution, as you can cancel signals of network requests: https://github.com/ReactiveCocoa/ReactiveCocoa

I've also used it to create cancellable delayed blocks. You can read more about it here: http://www.avanderlee.com/2015/07/25/cancellable-delayed-blocks/

Antoine
  • 23,526
  • 11
  • 88
  • 94
0

Based on @Antoine's idea

- (void(^)())executeSomeBlock:(void(^)())someBlock { __block volatile int32_t isCancelled = 0; [self.someOperationQueue addOperationWithBlock:^(){ if (!isCancelled) { someBlock(); } }]; return ^(){OSAtomicCompareAndSwap32Barrier(0, 1, &isCancelled);}; }

Usage:

void (^cancelMe)() = [self executeSomeBlock:myBlock];

If you want to cancel the block:

cancelMe();

Never test it though. Feel free to try it.

I guess it is possible to wrap the idea into a NSOperation's category.

*** updated by @CouchDeveloper's suggestion

Hai Feng Kao
  • 5,219
  • 2
  • 27
  • 38
  • 1
    There's a probability, that the imported variable `isCancelled` will be accessed from two different threads, where at least one will modify it. This is the definition of a data race. In order to fix that, you could utilise `OSAtomicXxx`. Or, as probably the better alternative, use `dispatch_block_create` and `dispatch_block_cancel`. – CouchDeveloper Apr 17 '16 at 10:28
  • @CouchDeveloper I thought immutable objects are thread-safe (https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/ThreadSafetySummary/ThreadSafetySummary.html). Am I wrong? – Hai Feng Kao Apr 18 '16 at 03:55
  • 1
    There's no immutable object, it's the `isCancelled` object which gets modified. Even if it were "immutable", the statement that "immutable objects are thread-safe" is a gross simplification of a rather complex topic. For example it's not "thread-safe", when there exist two threads A, and B. In A you create and initialise an "immutable" object (that's modifying it), then in thread B you access it. This is still a data race. – CouchDeveloper Apr 18 '16 at 09:56
  • @CouchDeveloper got it. Thanks – Hai Feng Kao Apr 18 '16 at 11:18
-3

You can do it this way

NSOperationQueue *opQueue = [[NSOperationQueue alloc] init];
//...
NSOperation *operation = [NSBlockOperation blockOperationWithBlock:^{

}];
[opQueue addOperation:operation];
//...
[operation cancel];
Fernando Mazzon
  • 3,562
  • 1
  • 19
  • 21
  • 2
    I think the problem there is, the way NSOperation -cancel works, you see the block operation is unable to respond to the cancellation, so it will just continue working until it reaches the end of the block. At least I'm pretty sure there is no way of checking -isCancelled within that block. – Henri Normak Jun 06 '14 at 17:51
  • A block Operation on a nsoperationqueue can not be canceled this way. See the nsoperation reference – Quxflux Jun 06 '14 at 17:53
  • Of course, cancelling != interrupting. In contrast with operationBlock: this will leave you a reference to the operation itself so that you can cancel it should you need to. I am aware that this wouldn't interrupt a download process happening within the block. – Fernando Mazzon Jun 06 '14 at 17:58
  • You're free to to monitor isCancelled from within the block @HenriNormak: http://stackoverflow.com/questions/8113268/how-to-cancel-nsblockoperation . Since it's very dependent on your implementation of the block, i didn't do it in the sample. – Fernando Mazzon Jun 06 '14 at 18:01
  • So doing it this way, there is no way to interrupt the download process if, like in my question, the cell would scroll off the screen? You couldn't just put many checks "if cancelled" throughout the operation? – denikov Jun 06 '14 at 19:26
  • @denikov Well, if you use `NSURLConnection` method `sendSynchronousRequest`, you have no way of checking during the request. Or if you use `sendAsynchronousRequest`, you can check, but you have no way to cancel the request. If you used block-based `NSURLSession` request, you could theoretically then set up a loop that continued until the request was done, also checking `isCancelled`, and if the operation was cancelled, then cancel the `NSURLSessionTask`. But as I argued in my answer, this is a horribly inefficient solution. – Rob Jun 06 '14 at 20:04
  • you should consider you blockoperation to be __weak as block and operation would have a strong reference of each other, resultant retain cycles conitnues. NSOperationQueue *operationQueue = [[NSOperationQueue alloc]init]; operationQueue.name =@"Feteching Cabs"; NSBlockOperation *operation = [[NSBlockOperation alloc]init]; __weak NSBlockOperation *weakOp = operation; [ operation addExecutionBlock:^{ if (weakOp.cancelled) return ; }];' – Khurram Iqbal Jan 02 '15 at 06:07