13

I wanted to create a category on NSURLSession. The app compiles ok, but when I attempt to call the category methods I get

-[__NSCFURLSession doSomething]: unrecognized selector sent to instance 0xbf6b0f0
<unknown>:0: error: -[NSURLSession_UPnPTests testCategory] : -[__NSCFURLSession doSomething]: unrecognized selector sent to instance 0xbf6b0f0

Very strange, here is a test class I built to show the issue. The category on the NSString works fine, but the category on NSURLSession fails to find the method at runtime. I suspect this is something internal.

Opinions please :-)

#import <XCTest/XCTest.h>

@interface NSString (test)
-(void) doSomethingHere;
@end

@implementation NSString (test)
-(void) doSomethingHere {
    NSLog(@"Hello string");
}
@end

@interface NSURLSession (test)
-(void) doSomething;
@end

@implementation NSURLSession (test)
-(void) doSomething {
    NSLog(@"Hello!!!!");
}
@end

@interface NSURLSession_UPnPTests : XCTestCase
@end

@implementation NSURLSession_UPnPTests
-(void) testCategory {
    [@"abc" doSomethingHere];
    NSURLSession *session = [NSURLSession sharedSession];
    [session doSomething];
}
@end
drekka
  • 20,957
  • 14
  • 79
  • 135

4 Answers4

6

I've had similar results with backgrounded NSURLSessionUploadTasks, which get deserialized as __NSCFURLSessionUploadTasks during the -URLSession:task:didCompleteWithError: delegate callback.

If I were you, I'd give up on that approach, and use composition (i.e. make the NSURLSession an ivar in another object). If you need to store some info with the NSURLSession, you could stuff a JSON-encoded dictionary into the .sessionDescription.

Here's the code that I used to do that for a task:

#pragma mark - Storing an NSDictionary in NSURLSessionTask.description

// This lets us attach some arbitrary information to a NSURLSessionTask by JSON-encoding
// an NSDictionary, and storing it in the .description field.
//
// Attempts at creating subclasses or categories on NSURLSessionTask have not worked out,
// because the –URLSession:task:didCompleteWithError: callback passes an
// __NSCFURLSessionUploadTask as the task argument. This is the best solution I could
// come up with to store arbitray info with a task.

- (void)storeDictionary:(NSDictionary *)dict inDescriptionOfTask:(NSURLSessionTask *)task
{
    NSData *data = [NSJSONSerialization dataWithJSONObject:dict options:0 error:nil];
    NSString *stringRepresentation = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
    [task setTaskDescription:stringRepresentation];
    [stringRepresentation release];
}

- (NSDictionary *)retrieveDictionaryFromDescriptionOfTask:(NSURLSessionTask *)task
{
    NSString *desc = [task taskDescription];
    if (![desc length]) {
        DDLogError(@"No description for %@", task);
        return nil;
    }
    NSData *data = [desc dataUsingEncoding:NSUTF8StringEncoding];
    NSDictionary *dict = (data ? (id)[NSJSONSerialization JSONObjectWithData:data options:0 error:nil] : nil);
    if (!dict) {
        DDLogError(@"Could not parse dictionary from task %@, description\n%@", task, desc);
    }
    return dict;
}
Clay Bridges
  • 11,602
  • 10
  • 68
  • 118
  • 1
    Yep. I've re-written to use composition already. I'm still curious as to the root cause though. Bug reported to Apple. – drekka Feb 10 '14 at 22:53
  • Did you ever find out anything more on this issue? I'm having the same problem. Any response from Apple on your bug report? I would like to be able to use a category if at all possible. – stevekohls Jun 24 '14 at 19:06
  • @drekka : Can you please post the bug link on the apple forum? Even I'm facing the similar problem, and not able to call any custom NSURLSession subclass/category methods from anywhere but only in iOS7. It's working fine in >=iOS8. Please let me know if you can help me out anyway. – Rohit saraf Dec 15 '15 at 11:32
2

Here is an alternative solution if you really want to add category to NSURLSession. I found this solution from BETURLSession. Of course, you have to be really careful about the name to avoid name conflicting.

Code for header

#import <Foundation/Foundation.h>

@interface NSURLSession (YTelemetry)

-(void) hello;
@end

Code for implementation

#import "NSURLSession+YTelemetry.h"

@implementation NSObject (YTelemetry)

-(void) hello
{
    NSLog(@"hello world");

}
@end

Now in the code, I can

NSURLSession *session = [NSURLSession sharedSession];

[session hello];
jqyao
  • 170
  • 6
  • 1
    The problem is that NSURLSession objects are being converted to their Core Foundation counterparts. Using this approach still doesn't let you interact with the self object inside the category method. – mph Nov 04 '14 at 22:48
  • @jqyao he already mention in his code that we cannot make category on NSURLSession So please make category on NSObject. – Saurav Nagpal Dec 23 '14 at 12:40
  • This is quite a bit of a hack but it does work. To get access to the NSURLSession methods inside the category method, verify that the object is a NSURLSession and then create a local variable that typecasts self to NSURLSession. – SwampThingTom Feb 09 '15 at 17:34
  • you could prefix your method e.g. -(void)jq_hello – malhal Apr 20 '15 at 16:00
0

I tried to do the same as @clay-bridges (i.e. storing a userInfo NSDictionary for each task), and it works great when using a NSURLSession with a session configuration different from background. If you use a NSURLSession with a background configuration, and your app is terminated or crashes, the upload/download continues in the background, managed by the OS. When the upload/download finishes, if I try to retrieve the userInfo NSDictionary previously stored, I get nothing. My solution is to store that custom object/NSDictionary in the request, not the task, using NSURLProtocol: +setProperty:​forKey:​inRequest:, and later on, retrieve the information using:

id customObject = [NSURLProtocol propertyForKey:@"yourkey" inRequest:sessionTask.originalRequest];

Luis Valdés
  • 1,409
  • 1
  • 12
  • 12
-3

you must import the your custom category where you call the custom methods

#import "NSURLSession+test.h"
alessio271288
  • 348
  • 1
  • 7