7

To give some context: I'm trying to implement a global error handler for authentication errors (using token authentication, not basic), which should try to re-authenticate and then repeat the original failed request (see my previous question: AFNetworking: Handle error globally and repeat request)

The current approach is to register an observer for the AFNetworkingOperationDidFinishNotification which does the re-authentication and (if auth succeeded) repeats the original request:

- (void)operationDidFinish:(NSNotification *)notification
{
    AFHTTPRequestOperation *operation = (AFHTTPRequestOperation *)[notification object];

    if(![operation isKindOfClass:[AFHTTPRequestOperation class]]) {
        return;
    }

    if(403 == [operation.response statusCode]) {
        // try to re-authenticate and repeat the original request
        [[UserManager sharedUserManager] authenticateWithCredentials...
            success:^{
                // repeat original request

                // AFHTTPRequestOperation *newOperation = [operation copy]; // copies too much stuff, eg. response (although the docs suggest otherwise)
                AFHTTPRequestOperation *newOperation = [[AFHTTPRequestOperation alloc] initWithRequest:operation.request];

                // PROBLEM 1: newOperation has no completion blocks. How to use the original success/failure blocks here?

                [self enqueueHTTPRequestOperation:newOperation];
            }
            failure:^(NSError *error) {
                // PROBLEM 2: How to invoke failure block of original operation?
            }
        ];
    }
}

However, I stumbled upon some issues regarding completion blocks of request operations:

  • When repeating the original request, I obviously want its completion blocks to be executed. However, AFHTTPRequestOperation does not retain references to the passed success and failure blocks (see setCompletionBlockWithSuccess:failure:) and copying NSOperation's completionBlock is probably not a good idea, as the documentation for AFURLConnectionOperation states:

    Operation copies do not include completionBlock. completionBlock often strongly captures a reference to self, which, perhaps surprisingly, would otherwise point to the original operation when copied.

  • In case the re-authentication fails, I want to call the original request's failure block. So, again, I'd need direct access to this.

Am I missing something here? Any ideas for alternative approaches? Should I file a feature request?

Community
  • 1
  • 1
Daniel Rinser
  • 8,855
  • 4
  • 41
  • 39
  • Good idea to file a feature request, as I'm stuck on this also with oauth refresh. – shawnwall Oct 23 '12 at 17:02
  • Submitted a feature request: https://github.com/AFNetworking/AFNetworking/issues/596. I might get my own hands on this later today or tomorrow. – Daniel Rinser Oct 24 '12 at 16:11

2 Answers2

1

I've come up with this problem in Art.sy's portfolio app. My eventual conclusion was to create a NSOperationQueue subclass which had functions to create copies of various AFNetworking HTTP Operations once they failed (and to do this up to three times per URL before giving up.)

orta
  • 4,225
  • 1
  • 26
  • 34
  • Thanks for your answer, but can you give some more insight into how exactly you created the copies of the operations? Why is this easier in an `NSOperationQueue` subclass than somewhere else? – Daniel Rinser Oct 27 '12 at 20:15
  • Ok, first off, here's the implementation : https://gist.github.com/3968284 - It's an `OperationQueue` subclass because I wanted it to wrap all the handling of being given operations and being able to create copies. The problem here is that it's very specific to our operation subclasses, but in our case we can get all the same instance variables set up for copies that are then re-ran. Make sense? – orta Oct 28 '12 at 10:36
  • Ok, I see. Thanks for sharing the implementation. However, I'm afraid that in my use case I can't just re-create the completion handlers for all requests at a single place. I have very different requests that have very specific completion handlers (processing of the received data etc.). I would just like to copy those completion handlers (which, for me, is the most straight-forward thing to do when retrying requests). Is this really such an obscure use case/requirement? – Daniel Rinser Oct 29 '12 at 08:57
  • @DanielRinser Did you ever figure that out? I am running into the exact same issue. – Newcode Jun 18 '13 at 23:20
  • I found a thread on AFNetworking's Github project that offers @DanielRinser solution to this issue. https://github.com/AFNetworking/AFNetworking/issues/596#issuecomment-16473252 – Newcode Jun 19 '13 at 20:38
  • @Newcode That comment you found in github was written by me. ;-) And yes, that's what I ended up doing and I'm still very happy with that solution. – Daniel Rinser Jul 03 '13 at 08:46
0

Did you try the following?

// set success / failure block of original operation
[newOperation setCompletionBlock:[operation.completionBlock copy]];
[operation setCompletionBlock:nil];

Note that if you capture self in the original completion/failure blocks (i.e. access any ivars) you actually access the original operation instance when executing the completion block of the newOperation. But this is what you want actually, right?

The notification handler is executed before the completion block of the operation. So you should set the completion block of the original operation to nil, to prevent it from executing twice.

Note the completion block is set to nil after it has executed (see AFURLConnectionOperation).

In the authenticateWithCredentials failure block you should not do anything. The original operation has finished at that time and already has executed its failure block.

Felix
  • 35,354
  • 13
  • 96
  • 143
  • "But this is what you want actually, right?" -> No. The operation's `completionBlock` is created by AFNetworking, not by myself. Please have a look at the implementation of `- [AFHTTPRequestOperation setCompletionBlockWithSuccess:failure:]`. This constructs a completion block that does things like `if([self cancelled]) ...` and calls the provided success/failure blocks in that block. – Daniel Rinser Oct 29 '12 at 08:32
  • When I log my completion block, the original one is nil. – quantumpotato Jun 28 '15 at 23:47