iOS: NSURLSession category methods not being found at runtime
Asked Answered
A

4

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
Audi answered 7/2, 2014 at 13:59 Comment(0)
A
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;
}
Aureaaureate answered 10/2, 2014 at 18:57 Comment(3)
Yep. I've re-written to use composition already. I'm still curious as to the root cause though. Bug reported to Apple.Audi
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.Bezanson
@Audi : 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.Informative
H
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];
Hendren answered 26/6, 2014 at 10:29 Comment(4)
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.Willianwillie
@Hendren he already mention in his code that we cannot make category on NSURLSession So please make category on NSObject.Gambado
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.Ormiston
you could prefix your method e.g. -(void)jq_helloCanica
A
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];

Alysa answered 17/3, 2015 at 10:11 Comment(0)
A
-3

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

#import "NSURLSession+test.h"
Automobile answered 7/2, 2014 at 14:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.