NSURLSessionDataDelegate and objc/runtime.h
While checking out the new
NSURLSession system, I was surprised to find that the session, and not the individual tasks, take a delegate.
One of the delegate methods is this:
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data;
Say you’re writing an RSS reader or podcast client. The app downloads a bunch of feeds, and passes data to a streaming XML parser, one per download, as it arrives.
How do you know which feed is associated with which task?
NSURLSessionDataTask had its own delegate, you could give that delegate a feed property. Easy. (That’s how you’d have done it with the old
I can think of a few options:
taskDescription — give it the URL of the feed, for instance. I don’t like this, though, because that’s really supposed to be, according to the headers, a descriptive label for the task. Not a data container.
• Look at
originalRequest and look up the feed object based on the URL. Feels a little fragile (though it shouldn’t be) and it’s kind of a pain.
NSURLSessionDataTask and add a feed property. I don’t see a way to do that, though, since the
NSURLSession creates data tasks.
• Maintain some kind of map — using an
NSMutableDictionary — that maps data tasks to feeds. Do-able, but ugly. Not particularly object-oriented.
• Hack it.
Rule of thumb: if
#import <objc/runtime.h> appears anywhere in your production code, you’re probably making a mistake.
It’s fun to play with the runtime, and you absolutely should — it’s part of becoming a good Cocoa developer. But you shouldn’t ship those hacks.
This may be a case where it’s warranted, though.
I may change my mind, but right now I’m thinking that using associated objects is the way to go. Associated objects let you attach any data to any object.
On creating the data task, I’d have code like this:
static const char *associated_feed = @"associated_feed";
objc_setAssociatedObject(dataTask, associated_feed, feed, OBJC_ASSOCIATION_RETAIN);
Then, in the delegate method, here’s how I’d get the associated feed:
feed = objc_getAssociatedObject(dataTask, associated_feed);
Of all the runtime things you can do, this has to be about the safest. It exists for situations like this one. It’s simple.
But still, it makes me feel a little weird.
I’ll file a Radar. What I really want here is a
userInfo property for
NSURLSessionTask. It would take a dictionary and I could put whatever I wanted in there.
Even though using associated objects works, the APIs should handle reasonable and expected cases like this without making the developer have to choose a least-bad option.
NSURLSession Is Cool
Despite this one odd thing, I like the new
NSURLSession a ton. I like the design, and I like that it makes some previously-difficult things easy. I like that we can do background tasks.
If you haven’t yet, you should read From NSURLConnection to NSURLSession in the latest objc.io.
Update Oct. 17, 2013
A wise man clued me in to this: there are APIs for attaching data to an
NSMutableURLRequest. I’d not noticed this part in
NSMutableURLRequestare designed to be customized to support protocol-specific requests. Protocol implementors who need to extend the capabilities of
NSMutableURLRequestare encouraged to provide categories on these classes as appropriate to support protocol-specific data. To store and retrieve data, category methods can use the
+setProperty:forKey:inRequest:class methods on
NSURLProtocol. See the
NSMutableURLRequestfor examples of such extensions.
To attach data to an
NSMutableURLRequest, write some code like this:
[NSURLProtocol setProperty:someObject forKey:someKey inRequest:request]
There are methods for getting and removing properties also.
So: problem solved. (Well. I haven’t actually tried it. But I have no reason to believe it wouldn’t work.)