在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操作注册了当前的监听状态通知,所以需要在主线程进行操作发送状态改变的通知。
3)在请求结束的时候移除监听:
- (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的状态改变,但是在使用过程中,有相当多的一部分人出现了崩溃现象,有的是在嵌套请求网络请求的时出现了崩溃,有的是并发了多个网络请求为了解决这个问题后期的版本维护中增加了NSURLSessionDataTask的分类 (_AFStateObserving) 并在+load方法中采用methodswizzle解决此问题:
+ (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];
}
});
}
此解决方案可以说是比较完美的,但是又存在一个问题就是当时在iOS7和iOS8中api对于resume和suspend的实现不一样,而且后期经过调研发现iOS9和iOS10的也不一样具体区别如下:
实现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都进行方法交换操作:
+ (void)load {
/**
WARNING: Trouble Ahead
https://github.com/AFNetworking/AFNetworking/pull/2702
*/
if (NSClassFromString(@"NSURLSessionTask")) {
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
IMP originalAFResumeIMP = method_getImplementation(class_getInstanceMethod([self class], @selector(af_resume)));
Class currentClass = [localDataTask class];
while (class_getInstanceMethod(currentClass, @selector(resume))) {
Class superClass = [currentClass superclass];
IMP classResumeIMP = method_getImplementation(class_getInstanceMethod(currentClass, @selector(resume)));
IMP superclassResumeIMP = method_getImplementation(class_getInstanceMethod(superClass, @selector(resume)));
if (classResumeIMP != superclassResumeIMP &&
originalAFResumeIMP != classResumeIMP) {
[self swizzleResumeAndSuspendMethodForClass:currentClass];
}
currentClass = [currentClass superclass];
}
[localDataTask cancel];
[session finishTasksAndInvalidate];
}
}
总结:1)对于需要根据属性或者某些SEL的返回值的状态发生了改变需要进行一些逻辑操作的使用KVO解决
2)使用methodswizzle 实现AOP模式
3)控制好多线程环境的数据读写操作,避免引起数据的混乱
4)子线程做复杂数据逻辑操作,完成操作之后通知主线程操作UI,写代码要遵循这样的大规范
_AFURLSessionTaskSwizzling是用来解决早期版本(2.x)中使用kvo机制监听NSURLSessionTask的state