在AFNetworking早期的版本中(2.0)中使用kvo监听NSURLSessionTask的state属性,达到对当前网络请求任务的状态的监控实现2.0版本的代码实现:
1)创建datatask时候监听datatask的state:
- static void * AFTaskStateChangedContext = &AFTaskStateChangedContext;
- - (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
- completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
- {
- NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:request];
- AFURLSessionManagerTaskDelegate *delegate = [AFURLSessionManagerTaskDelegate delegateForManager:self completionHandler:completionHandler];
- [self setDelegate:delegate forTask:dataTask];
- [dataTask addObserver:self forKeyPath:@"state" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:AFTaskStateChangedContext];
- return dataTask;
- }
2)datatask的状态发生改变的监控逻辑
- - (void)observeValueForKeyPath:(NSString *)keyPath
- ofObject:(id)object
- change:(NSDictionary *)change
- context:(void *)context
- {
- if (context == AFTaskStateChangedContext && [keyPath isEqualToString:@"state"]) {
- NSString *notificationName = nil;
- switch ([(NSURLSessionTask *)object state]) {
- case NSURLSessionTaskStateRunning:
- notificationName = AFNetworkingTaskDidStartNotification;
- break;
- case NSURLSessionTaskStateSuspended:
- notificationName = AFNetworkingTaskDidSuspendNotification;
- break;
- case NSURLSessionTaskStateCompleted:
- // AFNetworkingTaskDidFinishNotification posted by task completion handlers
- default:
- break;
- }
- if (notificationName) {
- dispatch_async(dispatch_get_main_queue(), ^{
- [[NSNotificationCenter defaultCenter] postNotificationName:notificationName object:object];
- });
- }
- } else {
- [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
- }
- }
根据代码逻对state的状态改变做了过滤,并在datatask任务暂停的时候或者开始的时候在主线程发起通知,因为有UI操作注册了当前的监听状态通知,所以需要在主线程进行操作发送状态改变的通知。
- - (void)URLSession:(NSURLSession *)session
- task:(NSURLSessionTask *)task
- didCompleteWithError:(NSError *)error
- {
- AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
- [delegate URLSession:session task:task didCompleteWithError:error];
- if (self.taskDidComplete) {
- self.taskDidComplete(session, task, error);
- }
- [self removeDelegateForTask:task];
- @try {
- [task removeObserver:self forKeyPath:@"state" context:AFTaskStateChangedContext]
- } @catch (NSException *exception) {}
- }
这个解决方案本身是个巧妙而且高效的方案,监听系统api的状态改变,但是在使用过程中,有相当多的一部分人出现了崩溃现象,有的是在嵌套请求网络请求的时出现了崩溃,有的是并发了多个网络请求。一开始人们removeObserver这个state,但是会造成AFNetworkActivityIndicatorManager功能(其中会观察state)削弱。为了解决这个问题后期的版本维护中增加了NSURLSessionDataTask的分类 (_AFStateObserving) 并在+load方法中采用methodswizzle解决此问题:
AFNetworkingTaskDidResumeNotification来通知各种UI控件当前网络任务状态为resume,那么就得调用taskDidResume:函数,而想要调用taskDidResume:函数就得调用af_resume函数。
- + (void)load {
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- if ([NSURLSessionDataTask class]) {
- NSURLSessionDataTask *dataTask = [[NSURLSession sessionWithConfiguration:nil] dataTaskWithURL:nil];
- Class taskClass = [dataTask superclass];
- af_addMethod(taskClass, @selector(af_resume), class_getInstanceMethod(self, @selector(af_resume)));
- af_addMethod(taskClass, @selector(af_suspend), class_getInstanceMethod(self, @selector(af_suspend)));
- af_swizzleSelector(taskClass, @selector(resume), @selector(af_resume));
- af_swizzleSelector(taskClass, @selector(suspend), @selector(af_suspend));
- [dataTask cancel];
- }
- });
- }
实现resume和suspend的内部类:
iOS7: __NSCFLocalSessionTask 、 __NSCFURLSessionTask
iOS8: NSURLSessionTask
iOS9、iOS10:__NSCFURLSessionTask 、 NSURLSessionTask
各个类的关系
iOS7:__NSCFLocalDataTask:__NSCFLocalSessionTask:__NSCFURLSessionTask
iOS8:__NSCFLocalDataTask:__NSCFLocalSessionTask:NSURLSessionTask
iOS9、iOS10:__NSCFLocalDataTask:__NSCFLocalSessionTask:__NSCFURLSessionTask:NSURLSessionTask因此解决以上的问题,增加了_AFURLSessionTaskSwizzling类,并在其+load方法中对各个层次的类只要实现了resume和suspend都进行方法交换操作:
_AFURLSessionTaskSwizzling 这个类在#issues 1477上reopen了多次,讨论还是很激烈的。
+ (void)load {
/**
WARNING: 高能预警
https://github.com/AFNetworking/AFNetworking/pull/2702
*/
// 担心以后iOS中不存在NSURLSessionTask
if (NSClassFromString(@"NSURLSessionTask")) {
/**
iOS 7和iOS 8在NSURLSessionTask实现上有些许不同,这使得下面的代码实现略显trick
关于这个问题,大家做了很多Unit Test,足以证明这个方法是可行的
目前我们所知的:
- NSURLSessionTasks是一组class的统称,如果你仅仅使用提供的API来获取NSURLSessionTask的class,并不一定返回的是你想要的那个(获取NSURLSessionTask的class目的是为了获取其resume方法)
- 简单地使用[NSURLSessionTask class]并不起作用。你需要新建一个NSURLSession,并根据创建的session再构建出一个NSURLSessionTask对象才行。
- iOS 7上,localDataTask(下面代码构造出的NSURLSessionDataTask类型的变量,为了获取对应Class)的类型是 __NSCFLocalDataTask,__NSCFLocalDataTask继承自__NSCFLocalSessionTask,__NSCFLocalSessionTask继承自__NSCFURLSessionTask。
- iOS 8上,localDataTask的类型为__NSCFLocalDataTask,__NSCFLocalDataTask继承自__NSCFLocalSessionTask,__NSCFLocalSessionTask继承自NSURLSessionTask
- iOS 7上,__NSCFLocalSessionTask和__NSCFURLSessionTask是仅有的两个实现了resume和suspend方法的类,另外__NSCFLocalSessionTask中的resume和suspend并没有调用其父类(即__NSCFURLSessionTask)方法,这也意味着两个类的方法都需要进行method swizzling。
- iOS 8上,NSURLSessionTask是唯一实现了resume和suspend方法的类。这也意味着其是唯一需要进行method swizzling的类
- 因为NSURLSessionTask并不是在每个iOS版本中都存在,所以把这些放在此处(即load函数中),比如给一个dummy class添加swizzled方法都会变得很方便,管理起来也方便。
一些假设前提:
- 目前iOS中resume和suspend的方法实现中并没有调用对应的父类方法。如果日后iOS改变了这种做法,我们还需要重新处理
- 没有哪个后台task会重写resume和suspend函数
*/
// 1) 首先构建一个NSURLSession对象session,再通过session构建出一个_NSCFLocalDataTask变量
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
NSURLSession * session = [NSURLSession sessionWithConfiguration:configuration];
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnonnull"
NSURLSessionDataTask *localDataTask = [session dataTaskWithURL:nil];
#pragma clang diagnostic pop
// 2) 获取到af_resume实现的指针
IMP originalAFResumeIMP = method_getImplementation(class_getInstanceMethod([self class], @selector(af_resume)));
Class currentClass = [localDataTask class];
// 3) 检查当前class是否实现了resume。如果实现了,继续第4步。
while (class_getInstanceMethod(currentClass, @selector(resume))) {
// 4) 获取到当前class的父类(superClass)
Class superClass = [currentClass superclass];
// 5) 获取到当前class对于resume实现的指针
IMP classResumeIMP = method_getImplementation(class_getInstanceMethod(currentClass, @selector(resume)));
// 6) 获取到父类对于resume实现的指针
IMP superclassResumeIMP = method_getImplementation(class_getInstanceMethod(superClass, @selector(resume)));
// 7) 如果当前class对于resume的实现和父类不一样(类似iOS7上的情况),并且当前class的resume实现和af_resume不一样,才进行method swizzling。
if (classResumeIMP != superclassResumeIMP &&
originalAFResumeIMP != classResumeIMP) {
[self swizzleResumeAndSuspendMethodForClass:currentClass];
}
// 8) 设置当前操作的class为其父类class,重复步骤3~8
currentClass = [currentClass superclass];
}
[localDataTask cancel];
[session finishTasksAndInvalidate];
}
}
+ (void)swizzleResumeAndSuspendMethodForClass:(Class)theClass {
// 因为af_resume和af_suspend都是类的实例方法,所以使用class_getInstanceMethod获取这两个方法
Method afResumeMethod = class_getInstanceMethod(self, @selector(af_resume));
Method afSuspendMethod = class_getInstanceMethod(self, @selector(af_suspend));
// 给theClass添加一个名为af_resume的方法,使用@selector(af_resume)获取方法名,使用afResumeMethod作为方法实现
if (af_addMethod(theClass, @selector(af_resume), afResumeMethod)) {
// 交换resume和af_resume的方法实现
af_swizzleSelector(theClass, @selector(resume), @selector(af_resume));
}
// 同上
if (af_addMethod(theClass, @selector(af_suspend), afSuspendMethod)) {
af_swizzleSelector(theClass, @selector(suspend), @selector(af_suspend));
}
}
// 根据两个方法名称交换两个方法,内部实现是先根据函数名获取到对应方法实现
// 再调用method_exchangeImplementations交换两个方法
static inline void af_swizzleSelector(Class theClass, SEL originalSelector, SEL swizzledSelector) {
Method originalMethod = class_getInstanceMethod(theClass, originalSelector);
Method swizzledMethod = class_getInstanceMethod(theClass, swizzledSelector);
method_exchangeImplementations(originalMethod, swizzledMethod);
}
// 给theClass添加名为selector,对应实现为method的方法
static inline BOOL af_addMethod(Class theClass, SEL selector, Method method) {
// 内部实现使用的是class_addMethod方法,注意method_getTypeEncoding是为了获得该方法的参数和返回类型
return class_addMethod(theClass, selector, method_getImplementation(method), method_getTypeEncoding(method));
}
- (NSURLSessionTaskState)state {
NSAssert(NO, @"State method should never be called in the actual dummy class");
// 初始状态是NSURLSessionTaskStateCanceling;
return NSURLSessionTaskStateCanceling;
}
- (void)af_resume {
NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state");
NSURLSessionTaskState state = [self state];
[self af_resume]; // 因为经过method swizzling后,此处的af_resume其实就是之前的resume,所以此处调用af_resume就是调用系统的resume。但是在程序中我们还是得使用resume,因为其实际调用的是af_resume
// 如果之前是其他状态,就变回resume状态,此处会通知调用taskDidResume
if (state != NSURLSessionTaskStateRunning) {
[[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidResumeNotification object:self];
}
}
// 同上
- (void)af_suspend {
NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state");
NSURLSessionTaskState state = [self state];
[self af_suspend];
if (state != NSURLSessionTaskStateSuspended) {
[[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidSuspendNotification object:self];
}
}
总结: 1)对于需要根据属性或者某些SEL的返回值的状态发生了改变需要进行一些逻辑操作的使用KVO解决
2)使用methodswizzle 实现AOP模式
3)控制好多线程环境的数据读写操作,避免引起数据的混乱
4)子线程做复杂数据逻辑操作,完成操作之后通知主线程操作UI,写代码要遵循这样的大规范