How to immediately force cancel an NSOperation wit

2019-01-24 21:26发布

问题:

I am using AFNetworking in my application and I am attempting to implement a "Tap To Cancel" feature in my progress HUD. I have a singleton class that manages all of the HTTP requests. If the progress HUD is tapped, I call:

[[[HTTPRequestSingleton sharedClient] operationQueue] cancelAllOperations];

But this doesn't "cancel" the operation like I need it to. I read up on the NSOperationQueue docs and came across this:

Canceling an operation object leaves the object in the queue but notifies the object that it should abort its task as quickly as possible. For currently executing operations, this means that the operation object’s work code must check the cancellation state, stop what it is doing, and mark itself as finished. For operations that are queued but not yet executing, the queue must still call the operation object’s start method so that it can processes the cancellation event and mark itself as finished.

And regarding the cancelAllOperations method:

This method sends a cancel message to all operations currently in the queue. Queued operations are cancelled before they begin executing. If an operation is already executing, it is up to that operation to recognize the cancellation and stop what it is doing.

My problem seems to specifically involve an operation that is already executing, which I want to immediately cancel. With AFNetworking, how can I alert the operation that it should cancel and discard all information about the request?

Code used for operation

AFJSONRequestOperation *loginOperation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {

    //operation was successful

    if (loginOperation.isCancelled)
    {
        //can't do this. variable 'loginOperation' is uninitialized when captured by block      
    }

} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {

    //operation failed

}];

回答1:

After a full morning of digging through AFNetworking source code, it turns out that the reason my operations weren't canceling had nothing to do with the operations themselves, but rather because I've been starting the operations incorrectly all this time. I've been using

[NSOperation start];

when I should have been adding it to my HTTPRequestSingleton's operation queue:

[[[HTTPRequestSingleton sharedClient] operationQueue] addOperation:NSOperation];

Adding it to the queue allows it to be canceled properly without having to check the isCancelled property.



回答2:

completionBlock

Returns the block to execute when the operation’s main task is complete.

  • (void (^)(void))completionBlock Return Value The block to execute after the operation’s main task is completed. This block takes no parameters and has no return value.

Discussion The completion block you provide is executed when the value returned by the isFinished method changes to YES. Thus, this block is executed by the operation object after the operation’s primary task is finished or cancelled.

Check the operation isCancelled property to understand why is the callback called.


Look into the initialization code:

+ (AFJSONRequestOperation *)JSONRequestOperationWithRequest:(NSURLRequest *)urlRequest
                                                    success:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, id JSON))success 
                                                    failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON))failure
{
    AFJSONRequestOperation *requestOperation = [[[self alloc] initWithRequest:urlRequest] autorelease];
    [requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
        if (success) {
            success(operation.request, operation.response, responseObject);
        }
    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
        if (failure) {
            failure(operation.request, operation.response, error, [(AFJSONRequestOperation *)operation responseJSON]);
        }
    }];

    return requestOperation;
}

What you'll want to do to get the operation var in the callback is to use setCompletionBlockWithSuccess after the JSONRequestOperationWithRequest:success:failure: initialization which is kind of overkill, the better way would be to copy the code and use

    AFJSONRequestOperation *requestOperation = [[[self alloc] initWithRequest:urlRequest] autorelease];
    [requestOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {

    // ... here you can check `isCancelled` flag