Got a singleton class, so called RequestManager, which shall handle requests made by different modules and background tasks of my application.
@interface RequestFactory : NSObject
- (void)requestDataWith:(NSString *)token
id:(NSString *)id
sender:(id<RequestFactoryDelegate>)sender;
...
@end
Then I got another class, so called SessionDelegate, which shall handle all the callbacks during the request.
@interface SessionDelegate : NSObject <NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate>
@property (weak, nonatomic) id <RequestFactoryDelegate> delegate;
@end
My idea is to encapsulate the functions in these classes to not overload my classes, because I need a lot of helper classes with CommonCrypto and so on.
So I set quickly coded a protocol RequestFactoryDelegate to send the received data to the sender who initiated the origin request.
- (void)requestDataWith:(NSString *)token
id:(NSString *)id
sender:(id<RequestFactoryDelegate>)sender
{
self.sessionDelegate.delegate = sender;
NSMutableURLRequest *request = //create the request here
NSURLSessionDataTask *dataTask = [self.defaultSession dataTaskWithRequest:request];
[dataTask resume];
}
Well, it works if I have an object, let us call it senderA which sends the requests, because the set delegate is always senderA itself.
The problem occurs having another object, e.g. senderB which sends requests - not even at the same time - but very shortly after senderA send.
- (void)foo
{
[requestFactory requestDataWith:token
id:id
sender:senderA]; // let's assume this takes 20s
[requestFactory requestDataWith:token
id:id
sender:senderB]; // let's assume this takes 1s
}
Because the request of senderA is still in progress, senderB sets the delegate to him and what happens is the delegate function of senderB is run twice.
<senderB>
<senderB>
Well... I really need to implement an own custom delegate (whether or not in the same class as the RequestFactory or not), but how to I handle the callback methods so I can respond properly to either senderA or senderB?
My last idea is to override the NSURLSessionTasks class and implement an own delegate property or block property or whatever.
Many thanks in advance.
In Objective-C, there is an alternative to subclassing that might be what you want here: associating objects.
It works like this: you can "attach" (associate) an object to another object with a custom key and later retrieve it. So in your case, you would do something like:
#include <objc/runtime.h>
// Top level of your .m file. The type and content of this
// variable don't matter much, we need the _address_ of it.
// See the first link of this answer for details.
static char kDelegateKey = 'd';
- (void)requestDataWith:(NSString *)token
id:(NSString *)id
sender:(id<RequestFactoryDelegate>)sender
{
NSMutableURLRequest *request = //create the request here
NSURLSessionDataTask *dataTask = [self.defaultSession dataTaskWithRequest:request];
// Associate the sender with the dataTask. We use "assign" here
// to avoid retain cycles as per the delegate pattern in Obj-C.
objc_setAssociatedObject(dataTask, &kDelegateKey, sender, OBJC_ASSOCIATION_ASSIGN);
[dataTask resume];
}
- (void)someOtherMethodWithDataTask:(NSURLSessionDataTask *)dataTask
{
// Read the attached delegate.
id<RequestFactoryDelegate> delegate = objc_getAssociatedObject(dataTask, &kDelegateKey);
// Do something with the delegate.
}
You can attach an arbitrary object to a task:
NSMutableURLRequest* req = [NSMutableURLRequest requestWithURL:url];
id whatever = // anything at all!
[NSURLProtocol setProperty:whatever forKey:@"thing" inRequest:req];
NSURLSessionDownloadTask* task = [[self session] dataTaskWithRequest:req];
And retrieve it later (in the delegate) like this:
NSURLRequest* req = task.originalRequest;
id thing = [NSURLProtocol propertyForKey:@"thing" inRequest:req];
The value here (whatever
) can be any object. It can be a completion handler callback - I've done this and it works just fine.
Here is my solution.
I just just use the unique identifier of each sessionTask object. So my delegate object contains a dictionary with blocks as values to execute on success/failure and the identifier as keys to identify the correct execution block.
In the .h file I declared the dictionary and a method to add a key/value object:
@property (nonatomic, strong) NSDictionary *completionHandlerDictionary;
- (void)addCompletionHandler:(CompletionHandlerType)handler
forTask:(NSString *)identifier;
And in the .m file I call the handler block.
- (void)addCompletionHandler:(CompletionHandlerType)handler
forTask:(NSString*)identifier
{
if ([self.completionHandlerDictionary objectForKey:identifier]) {
NSLog(@"Error: Got multiple handlers for a single task identifier. This should not happen.\n");
}
[self.completionHandlerDictionary setObject:handler forKey:identifier];
}
- (void)callCompletionHandlerForTask:(NSString *)identifier
{
CompletionHandlerType handler = [self.completionHandlerDictionary objectForKey:identifier];
if (handler) {
[self.completionHandlerDictionary removeObjectForKey: identifier];
NSLog(@"Calling completion handler.\n");
handler();
}
}
That's it, simple as it is.