AFNetworking AFURLSessionManager中的_AFURLSessionTaskSwizzling

在AFNetworking早期的版本中(2.0)中使用kvo监听NSURLSessionTask的state属性,达到对当前网络请求任务的状态的监控实现2.0版本的代码实现:

1)创建datatask时候监听datatask的state:

[html]  view plain  copy
  1. static void * AFTaskStateChangedContext = &AFTaskStateChangedContext;  
  2.   
  3. - (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request  
  4.                             completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler  
  5. {  
  6.     NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:request];  
  7.   
  8.     AFURLSessionManagerTaskDelegate *delegate = [AFURLSessionManagerTaskDelegate delegateForManager:self completionHandler:completionHandler];  
  9.     [self setDelegate:delegate forTask:dataTask];  
  10.                  [dataTask addObserver:self forKeyPath:@"state" options:NSKeyValueObservingOptionOld|NSKeyValueObservingOptionNew context:AFTaskStateChangedContext]; 
  11.   
  12.     return dataTask;  
  13. }  

2)datatask的状态发生改变的监控逻辑

[html]  view plain  copy
  1. - (void)observeValueForKeyPath:(NSString *)keyPath  
  2.                       ofObject:(id)object  
  3.                         change:(NSDictionary *)change  
  4.                        context:(void *)context  
  5. {  
  6.     if (context == AFTaskStateChangedContext && [keyPath isEqualToString:@"state"]) {  
  7.         NSString *notificationName = nil;  
  8.         switch ([(NSURLSessionTask *)object state]) {  
  9.             case NSURLSessionTaskStateRunning:  
  10.                 notificationName = AFNetworkingTaskDidStartNotification;  
  11.                 break;  
  12.             case NSURLSessionTaskStateSuspended:  
  13.                 notificationName = AFNetworkingTaskDidSuspendNotification;  
  14.                 break;  
  15.             case NSURLSessionTaskStateCompleted:  
  16.                 // AFNetworkingTaskDidFinishNotification posted by task completion handlers  
  17.             default:  
  18.                 break;  
  19.         }  
  20.   
  21.         if (notificationName) {  
  22.             dispatch_async(dispatch_get_main_queue(), ^{  
  23.                 [[NSNotificationCenter defaultCenter] postNotificationName:notificationName object:object];  
  24.             });  
  25.         }  
  26.     } else {  
  27.         [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];  
  28.     }  
  29. }  

根据代码逻对state的状态改变做了过滤,并在datatask任务暂停的时候或者开始的时候在主线程发起通知,因为有UI操作注册了当前的监听状态通知,所以需要在主线程进行操作发送状态改变的通知。


3)在请求结束的时候移除监听:
[html]  view plain  copy
  1. - (void)URLSession:(NSURLSession *)session  
  2.               task:(NSURLSessionTask *)task  
  3. didCompleteWithError:(NSError *)error  
  4. {  
  5.     AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];  
  6.     [delegate URLSession:session task:task didCompleteWithError:error];  
  7.   
  8.     if (self.taskDidComplete) {  
  9.         self.taskDidComplete(session, task, error);  
  10.     }  
  11.   
  12.     [self removeDelegateForTask:task];  
  13.       
  14.     @try {  
  15.        [task removeObserver:self forKeyPath:@"state" context:AFTaskStateChangedContext]
  16.     } @catch (NSException *exception) {}  
  17. }  

这个解决方案本身是个巧妙而且高效的方案,监听系统api的状态改变,但是在使用过程中,有相当多的一部分人出现了崩溃现象,有的是在嵌套请求网络请求的时出现了崩溃,有的是并发了多个网络请求。一开始人们removeObserver这个state,但是会造成AFNetworkActivityIndicatorManager功能(其中会观察state)削弱。为了解决这个问题后期的版本维护中增加了NSURLSessionDataTask的分类 (_AFStateObserving) 并在+load方法中采用methodswizzle解决此问题:

AFNetworkingTaskDidResumeNotification来通知各种UI控件当前网络任务状态为resume,那么就得调用taskDidResume:函数,而想要调用taskDidResume:函数就得调用af_resume函数。


[html]  view plain  copy
  1. + (void)load {  
  2.      static dispatch_once_t onceToken;  
  3.      dispatch_once(&onceToken, ^{  
  4.           
  5.         if ([NSURLSessionDataTask class]) {  
  6.             NSURLSessionDataTask *dataTask = [[NSURLSession sessionWithConfiguration:nil] dataTaskWithURL:nil];  
  7.             Class taskClass = [dataTask superclass];  
  8.             af_addMethod(taskClass, @selector(af_resume),  class_getInstanceMethod(self, @selector(af_resume)));  
  9.             af_addMethod(taskClass, @selector(af_suspend), class_getInstanceMethod(self, @selector(af_suspend)));  
  10.             af_swizzleSelector(taskClass, @selector(resume), @selector(af_resume));  
  11.             af_swizzleSelector(taskClass, @selector(suspend), @selector(af_suspend));  
  12.             [dataTask cancel];  
  13.         }  
  14.      });  
  15.  }  
此解决方案可以说是比较完美的,但是又存在一个问题就是当时在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都进行方法交换操作:

_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,写代码要遵循这样的大规范


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值