对NSURLSessionTask 撰写Category

在写category 的时候,有时候你也会遇到像NSURLSessionTask 这种地雷。

假如我们在iOS 7 上,对NSURLSessionTask 写了一个category 之后,你会发现,如果我们从[NSURLSession sharedSession] 产生了data task 实例,之后,对这个实例调用category 里头的method,很奇怪,会收到找不到selector 的错误。照理说一个data task 是NSURLSessionDataTask,继承自NSURLSessionTask,为什么我们写的NSURLSessionTask category 没用?

到了iOS 8 的环境下又正常,可以对这个实例呼叫NSURLSessionTask category 里头的method,但如果是写成NSURLSessionDataTask 的category,结果又遇到找不到selector 的错误。

我们来写一个简单的category:

@interface NSURLSessionTask (Test)
- (void)test;
@end

@implementation NSURLSessionTask (Test)
- (void)test
{
    NSLog(@"test");
}
@end

然后执行看看

NSURLSessionDataTask *task = [[NSURLSession sharedSession]
    dataTaskWithURL:[NSURL URLWithString:@"http://kkbox.com"]];
[task test];

结果就发生错误了:

这件事情很奇怪,你于是会开始想各种可能的方向。—对了,如果有一个category 不是直接写在你的app 里头,而是写在某个static library,你要在编译app 的最后才把这个library link 进来,预设category 并不会linker 给link进来,你必须要另外在Xcode Project设定的other linker flag,加上-ObjC 或是-all_load。会是这个问题吗?你试了一下,发现没用,还是一样收到unsupported selector 错误。

然后你想到,NSURLSessionTask 是个Foundation 物件,而Foundation 物件往往真正的实作与最上层的介面并不是同一个。所以,我们来看看NSURLSessionTask 的继承树到底长成什么样子。

程式如下:

NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:@"http://kkbox.com"]];
NSLog(@"%@", [task class]);
NSLog(@"%@", [task superclass]);
NSLog(@"%@", [[task superclass] superclass]);
NSLog(@"%@", [[[task superclass] superclass] superclass]);

在iOS 8 上的结果是:

__NSCFLocalDataTask

__NSCFLocalSessionTask

NSURLSessionTask

NSObject

在iOS 7 上的结果是:

__NSCFLocalDataTask

__NSCFLocalSessionTask

__NSCFURLSessionTask

NSObject

结论,无论是iOS 8 或iOS 7,我们建立的data task,都不是直接产生NSURLSessionDataTask 物件,而是产生__NSCFLocalDataTask 这样的private class 的物件。iOS 8 上,__NSCFLocalDataTask 并不继承NSURLSessionDataTask,而iOS 7 上__NSCFLocalDataTask 甚至连NSURLSessionTask 都不是。

但如果我们去问,我们建立的这些data task到底是不是NSURLSessionDataTask,像调用[task isKindOfClass:[NSURLSessionDataTask class]],还是会回传YES。其实-isKindOfClass:是可以override掉的,所以,即使__NSCFLocalDataTask根本就不是NSURLSessionDataTask,但我们可以把__NSCFLocalDataTask的-isKindOfClass写成这样:

- (BOOL)isKindOfClass:(Class)aClass
{
    if (aClass == NSClassFromString(@"NSURLSessionDataTask")) {
        return YES;
    }
    if (aClass == NSClassFromString(@"NSURLSessionTask")) {
        return YES;
    }
    return [super isKindOfClass:aClass];
}

也就是说,-isKindOfClass其实并不像你所想像的那么值得信任。这也就是写Objective-C这门语言的迷人之处:写Objective-C就像真实的人生一样,一个实例就像人一般,一个人能做什么绝对不是在出生的时候就决定的,而是在他的一生当中可以随时改变,而当你用-isKindOfClass去问一个实例到底是什么实例的时候,大概就跟你去问一个PM到底会不会规划产品,或是一个号称是iOS工程师的人到底会不会写代码一样。

results matching ""

    No results matching ""