Search code examples
objective-cafnetworkingnsoperationnsoperationqueue

AFNetworking cancelAllOperations prevents batch completionBlock from firing


I'm using enqueueBatchOfHTTPRequestOperations to submit a batch of requests. If any of the requests fail, I want to immediately cancel any other requests that are still going. To do so, I'm setting the failure callback on the individual operations to do a [client.operationQueue cancelAllOperations];.

This seems to cancel all remaining operations, but it's also preventing the overall completionBlock of the batch from executing... Here's the code I'm trying to test this behavior with (one of the requests is always set to fail on the server).

AFHTTPClient *client = [AFHTTPClient clientWithBaseURL:[NSURL URLWithString:@"http://arahlf.com"]];

NSMutableArray *requests = [[NSMutableArray alloc] init];

for (int i = 0; i < 10; i++) {
    NSURLRequest *request = [client requestWithMethod:@"GET" path:@"echo.php" parameters:@{ @"sleep": @(i) }];
    AFHTTPRequestOperation *operation = [client HTTPRequestOperationWithRequest:request success:nil failure:nil];
    [operation setCompletionBlockWithSuccess:nil failure:^(AFHTTPRequestOperation *operation, NSError *error) {
        NSLog(@"Request failed, cancelling all operations.");
        [client.operationQueue cancelAllOperations];
    }];

    [requests addObject:operation];
}

[client enqueueBatchOfHTTPRequestOperations:requests progressBlock:^(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations) {
    NSLog(@"Progress: %i/%i", numberOfFinishedOperations, totalNumberOfOperations);

} completionBlock:^(NSArray *operations) {
    NSLog(@"All done!");
}];

For me, the completionBlock is never executed. Also, since one failing request cancels the remaining (which also fires the failure block), cancelAllOperations is getting executed many times actually.

Is there a better way to achieve this effect?


Solution

  • When you do operationQueue cancelAllOperations, you are actually canceling the dependent operation that fires on batch completion, in addition to all of the other operations.

    That is to say, in your example, 11 operations are cancelled: 10 network operations + the dependent batch completion operation.

    The following change in setCompletionblock:... allows the batch completion to fire as expected:

    [[client.operationQueue.operations filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
      return [evaluatedObject isKindOfClass:[AFHTTPRequestOperation class]];
    }]] makeObjectsPerformSelector:@selector(cancel)];