AFNetworking3.1.0源码分析(二)AFURLSessionManager中的_AFURLSessionTaskSwizzling

在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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值