I am using a NSProxy
subclass and forwardInvocation:
for capturing calls to my Backend API object (a shared instance).
Some Background information: I want to capture the API calls so I can check everytime if I have to refresh my authentication token. If yes I just perform the refresh before.
The method parameters (of invocation
) contain blocks.
Some simplified code:
- (void)forwardInvocation:(NSInvocation *)invocation {
[invocation setTarget:self.realAPI];
[invocation retainArguments];
// Perform refresh call and forward invocation after
// successfully refreshed
if (authenticationRefreshNeeded) {
[self.realAPI refreshWithBlock:^(NSObject *someObject) {
[invocation invokeWithTarget:self.realAPI];
}];
}
// Otherwise we just forward the invocation immediately
else {
[invocation invokeWithTarget:self.realAPI];
}
return;
}
I am already calling retainArguments
so my blocks and other parameters don't get lost because of the late execution of invokeWithTarget:
(refreshWithBlock:
makes an async API call).
Everything works fine so far - BUT:
The return value of invocation is always nil
when invokeWithTarget:
is performed within the refresh block. Is there any way to retain the return value (like the arguments)?
Any hints? Suggestions?
Update
As response to @quellish:
The problem is that the return value is of type NSURLSessionDataTask
(that I use to show an activity indicator) which I read directly after making the call. But the proxy does not forward the call immediately so the return value is not there - of course (I was blind).
What would be a possible workaround? Can I return a placeholder value or how can I know as caller when the method gets invoked so I can retrieve the return value later?
To perform an operation when your invocation is complete, passing the result:
if (authenticationRefreshNeeded) {
[self.realAPI refreshWithBlock:^(NSObject *someObject) {
NSURLSessionDataTask *resultTask = nil;
[invocation invokeWithTarget:self.realAPI];
[invocation getReturnValue:&resultTask];
if (completion != nil){
completion(resultTask);
}
}];
}
Where completion()
is a block that takes an NSURLSessionDataTask
as a parameter. Blocks can be used as callbacks, which make them well suited to what you are trying to do ("when I'm done, do this() ") Ideally, this would have been passed into the method containing the above - but since this is forwardInvocation:
, that gets a little more... challenging. You could set it as a property on this proxy object and read it from there.
Another approach would be to extend UIApplication with a category or informal protocol with a method like addDataTask:
which you could call instead of your block, which would hand off responsbility for the "i just added a data task" to another receiver, most likely the application's delegate (and you can extend the UIApplicationDelegate protocol with a new method, application:didAddDataTask:
to handle this). It sounds like your data task and activity indicator are application-level concerns, which may make this a good fit.
That said, I have some experience with almost exactly the problems you are trying to solve (token based authorization). I would suggest taking at a look at how ACAccountStore approaches this problem , it may offer some ideas for alternative implementations.