5

Basically, I would like to perform a cancel if the operation I'm adding to the queue does not respond after a certain timeout :

NSOperationQueue *   queue = ...

[self.queue addOperationWithBlock:^{
        // my block...
    } timeoutInSeconds:5.0 hasTimedOutWithBlock:^{
        // called after 5.0, operation should be canceled at the end
}];

Thanks Guys !

Nicolas Henin
  • 3,244
  • 2
  • 21
  • 42
  • The `NSOperation` Class Reference would be a great place to start. – jlehr Aug 24 '14 at 20:02
  • What are you doing in that initial block? There are very few situations where it couldn't be better addressed with another pattern, but it's hard for us to offer counsel without knowing what, precisely, you're trying to do. – Rob Aug 24 '14 at 21:10

1 Answers1

10

You could do something like you asked for, but I might suggest adding a parameter to the first block by which the first block could check to see if the operation was canceled.

[queue addOperationWithBlock:^(NSOperation *operation) {

    // do something slow and synchronous here, 

    // if this consists of a loop, check (and act upon) `[operation isCancelled]` periodically 

} timeout:5.0 timeoutBlock:^{

    // what else to do when the timeout occurred

}];

Maybe you don't need to check isCancelled, but in some cases you would (generally the burden for responding to a cancellation rests on the operation itself), so that's probably a prudent parameter to add.

Anyway, if that's what you wanted, you might do something like the following:

@implementation NSOperationQueue (Timeout)

- (NSOperation *)addOperationWithBlock:(void (^)(NSOperation *operation))block timeout:(CGFloat)timeout timeoutBlock:(void (^)(void))timeoutBlock
{
    NSBlockOperation *blockOperation = [[NSBlockOperation alloc] init];  // create operation
    NSBlockOperation __weak *weakOperation = blockOperation;             // prevent strong reference cycle

    // add call to caller's provided block, passing it a reference to this `operation`
    // so the caller can check to see if the operation was canceled (i.e. if it timed out)

    [blockOperation addExecutionBlock:^{
        block(weakOperation);
    }];

    // add the operation to this queue

    [self addOperation:blockOperation];

    // if unfinished after `timeout`, cancel it and call `timeoutBlock`

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        // if still in existence, and unfinished, then cancel it and call `timeoutBlock`

        if (weakOperation && ![weakOperation isFinished]) {
            [weakOperation cancel];
            if (timeoutBlock) {
                timeoutBlock();
            }
        }
    });

    return blockOperation;
}

@end

Having provided that code sample, I must confess that there are a very narrow set of situations where something like the above might be useful. Generally it would be better addressed using another pattern. The vast majority of the time, when you want a cancelable operation, you would implement a NSOperation subclass (often a concurrent NSOperation subclass). See the Defining a Custom Operation Object section of the Operation Queues chapter of the Concurrency Programming Guide for more information.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Hi Rob, I want to use it to communicate with a BLE device... I want to handle each of my commands with a custom timeout... Does it belong for you to that very narrow set of situations you're thinking of ? – Nicolas Henin Aug 25 '14 at 14:34
  • Basically, I find this usefull for network communication... And I think AFNetworking has these kind of pattern – Nicolas Henin Aug 25 '14 at 14:35
  • 1
    No, AFNetworking is a wonderful example of the pattern I allude to in my concluding paragraph, where you do not use the above pattern, but rather it creates a concurrent `NSOperation` subclass. In the case of AFNetworking, it relies upon `NSURLConnection` to handle timeouts and finishes the operation if that occurs. And, as aside, unlike above, this means that the timeout logic is tied with the starting of the network request, not with the scheduling of the operation (which, if it was a serial queue, or had other dependencies, may start at some future time). – Rob Aug 25 '14 at 15:07
  • @NicolasHenin In answer to your question whether the timeout-rendition of `addOperationWithBlock`, above, makes sense for BLE apps, I'd be surprised if it did, because BLE requests happen asynchronously, and the above assumes the first block is running synchronously. If you use `NSOperationQueue` approach, a concurrent `NSOperation` is likely to handle asynchronous requests much better. But given that the `CBCentralManager` delegate is at the manager-level (not the request-level), I'm not sure if `NSOperationQueue` is the right architecture for BLE at all. – Rob Aug 25 '14 at 15:20
  • I'm using LGBluetooth a wrapper aboved Core Bluetooth that gives an API with Blocks, but yeah you are right or CBCentralManager... I hope Apple will release a new API soon with something more evolved than the current one... Maybe something in SWIFT ;-) – Nicolas Henin Sep 26 '14 at 17:55