对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工程师的人到底会不会写代码一样。