iOS

AFNetworking 的核心 AFURLSessionMan

2017-09-12  本文已影响15人  果哥爸

主要参考:
AFNetworking 的核心 AFURLSessionManager

AFURLSessionManagerAFNetworking的核心。

  1. 负责创建和管理 NSURLSession
  2. 管理 NSURLSessionTask
  3. 实现 NSURLSessionDelegate 等协议中的代理方法
  4. 使用AFURLSessionManagerTaskDelegate 管理进度
  5. 使用_AFURLSessionTaskSwizzling调剂方法。
  6. 引入AFSecurityPolicy 保证请求的安全
  7. 引入AFNetworkReachabilityManager监控网络状态

我们会在这里着重介绍上面七个功能中的五个,分析它是如何包装NSURLSession以及众多代理方法。

一. 创建和管理NSURLSession

在使用AFURLSessionManager时,第一件要做的事情一定要初始化:

- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
    self = [super init];
    if (!self) {
        return nil;
    }

    if (!configuration) {
        configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
    }

    self.sessionConfiguration = configuration;

    self.operationQueue = [[NSOperationQueue alloc] init];
    self.operationQueue.maxConcurrentOperationCount = 1;

    self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];

    self.responseSerializer = [AFJSONResponseSerializer serializer];

    self.securityPolicy = [AFSecurityPolicy defaultPolicy];

    self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];

    self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];

    self.lock = [[NSLock alloc] init];
    self.lock.name = AFURLSessionManagerLockName;

    #1: 为已有的 task 设置代理, 略

    return self;
}

在初始化方法中,需要完成初始化自己持有的实例:

二. 管理 NSURLSessionTask

接下来,在获得了AFURLSessionManager的实例之后,我们可以通过以下方法创建NSURLSessionDataTask的实例:

- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
                           uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
                         downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
                        completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject,  NSError * _Nullable error))completionHandler;

- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request
                                     fromFile:(NSURL *)fileURL
                                     progress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
                            completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError  * _Nullable error))completionHandler;

...

- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request
                                         progress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
                                      destination:(nullable NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
                                completionHandler:(nullable void (^)(NSURLResponse *response, NSURL * _Nullable filePath, NSError * _Nullable error))completionHandler;

...

这里省略了一些返回NSURLSesstionTask 的方法,因为这些接口形式都是差不多,所以我们将以- [AFURLSessionManager dataTaskWithRequest: uploadProgress: downloadProgress:completionHandler:]方法实例,分析它是如何实例化并返回一个NSURLSestionTask的实例的。

- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler {

     __block NSURLSessionDataTask *dataTask = nil; url_session_manager_create_task_safely(^{ 
        dataTask =   [self.session dataTaskWithRequest:request];
     }); 

    [self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler]; 

    return dataTask;

}

url_session_manager_create_task_safely的调用是因为苹果框架中的一个 bug #2093

static void url_session_manager_create_task_safely(dispatch_block_t block) {
    if (NSFoundationVersionNumber <</span> NSFoundationVersionNumber_With_Fixed_5871104061079552_bug) {
    // Fix of bug
    // Open Radar:[http://openradar.appspot.com/radar?id=5871104061079552](http://openradar.appspot.com/radar?id=5871104061079552) (status: Fixed in iOS8)
    // Issue about:[https://github.com/AFNetworking/AFNetworking/issues/2093](https://github.com/AFNetworking/AFNetworking/issues/2093)

//理解下,第一为什么用sync,因为是想要主线程等在这,等执行完,在返回,因为必须执行完dataTask才有数据,传值才有意义。 

//第二,为什么要用串行队列,因为这块是为了防止ios8以下内部的dataTaskWithRequest是并发创建的, 

//这样会导致taskIdentifiers这个属性值不唯一,因为后续要用taskIdentifiers来作为Key对应delegate。

       dispatch_sync(url_session_manager_creation_queue(), block);
} else {
        block();
    }
}
- (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask
            uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
          downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
         completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{
    AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] init];
    delegate.manager = self;
    delegate.completionHandler = completionHandler;

    dataTask.taskDescription = self.taskDescriptionForSessionTasks;
    [self setDelegate:delegate forTask:dataTask];

    delegate.uploadProgressBlock = uploadProgressBlock;
    delegate.downloadProgressBlock = downloadProgressBlock;
}

在这个方法中同时调用了另一个方法- [AFURLSessionManager setDelegate:forTask:]来设置代理:

- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
        forTask:(NSURLSessionTask *)task
{
    
#1: 检查参数, 略

    [self.lock lock];
    self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
    [delegate setupProgressForTask:task];
    [self addNotificationObserverForTask:task];
    [self.lock unlock];
}

正如上面所提到的,AFURLSessionManager就是通过字典 mutableTaskDelegatesKeyedByTaskIdentifier 来存储管理每一个NSURLSessionTask, 它以 taskIdentifier为键存储task

该方法使用NSLock 来保证不同线程使用mutableTaskDelegatesKeyedByTaskIdentifier 时, 不会出现线程竞争的问题。

同时调用 - setupProgressForTask:, 我们会在下面具体介绍这个方法。

三. 实现 NSURLSessionDelegate 等协议中的代理方法

AFURLSessionManager 的头文件可以看到,它遵循了多个协议,其中包括:

- NSURLSessionDelegate

- NSURLSessionTaskDelegate

- NSURLSessionDataDelegate

- NSURLSessionDownloadDelegate

它在初始化方法 - [AFURLSessionManager initWithSessionConfiguration:]NSURLSession的代理指向 self,然后实现这些方法,提供更简洁的 block 的接口:

- (void)setSessionDidBecomeInvalidBlock:(nullable void (^)(NSURLSession *session, NSError *error))block;
- (void)setSessionDidReceiveAuthenticationChallengeBlock:(nullable NSURLSessionAuthChallengeDisposition (^)(NSURLSession *session, NSURLAuthenticationChallenge *challenge, NSURLCredential * _Nullable __autoreleasing * _Nullable credential))block;
...

它为所有的代理协议都提供了对应的block接口,方法实现的思路都是相似的,我们以- [AFNRLSessionManager setSessionDidBecomeInvalidBlock:]为例。

首先调用setter方法,将block存入sessionDidBecomeInvalid属性中:

- (void)setSessionDidBecomeInvalidBlock:(void (^)(NSURLSession *session, NSError *error))block {
    self.sessionDidBecomeInvalid = block;
}

当代理方法调用时,如果存在对应的block,会执行对应的block

- (void)URLSession:(NSURLSession *)session
  didBecomeInvalidWithError:(NSError *)error
{
        if (self.sessionDidBecomeInvalid) {
            self.sessionDidBecomeInvalid(session, error);
        }

        [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDidInvalidateNotification object:session];
    }
}

其它相似的接口实现也差不多。

四. 使用AFURLSessionManagerTaskDelegate 管理进度

AFURLSessionManagerTaskDelegate类,它主要为task提供进度管理功能,并在task结束时回调,也就是调用在- [AFURLSessionManager dataTaskWithRequest:uploadProgress:downloadProgress:completionHandler:] 等方法中传入的completionHandler

我们首先分析一下AFURLSessionManagerTaskDelegate是如何对进度进行跟踪的:

- (void)setupProgressForTask:(NSURLSessionTask *)task {

    #1:设置在上传进度或者下载进度状态改变时的回调

    #2:KVO

}

该方法的实现有两个部分:

这里只有对uploadProgerss设置回调的代码,设置downloadPregress回调原理相同主要目的是在对应NSProgress的状态改变时,调用resume,suspend等方法改变task的状态。

observeValueForKeypath:ofObject:change:context:方法中改变进度,并调用 block

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
    if ([object isKindOfClass:[NSURLSessionTask class]]) {
        if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesReceived))]) {
            self.downloadProgress.completedUnitCount = [change[@"new"] longLongValue];
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))]) {
            self.downloadProgress.totalUnitCount = [change[@"new"] longLongValue];
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesSent))]) {
            self.uploadProgress.completedUnitCount = [change[@"new"] longLongValue];
        } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToSend))]) {
            self.uploadProgress.totalUnitCount = [change[@"new"] longLongValue];
        }
    }
        else if ([object isEqual:self.downloadProgress]) {
        if (self.downloadProgressBlock) {
            self.downloadProgressBlock(object);
        }
    }
        else if ([object isEqual:self.uploadProgress]) {
        if (self.uploadProgressBlock) {
            self.uploadProgressBlock(object);
         }
    }
}

对象的某些属性改变时更新NSProgress对象或使用block传递NSProgress对象self.uploadPregressBlock(object).

代理方法URLSession:task:didCompleteWithError:

在每一个 NSURLSessionTask 结束时,都会在代理方法 URLSession:task:didCompleteWithError: 中:

调用传入的completionHander block
发出 AFNetworkingTaskDidCompleteNotification 通知

- (void)URLSession:(__unused NSURLSession *)session
          task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
    #1:获取数据, 存储 `responseSerializer` 和 `downloadFileURL`

    if (error) {
        #2:在存在错误时调用 `completionHandler`
    } else {
        #3:调用 `completionHandler`
    }
}

这是整个代理方法的骨架,先看下最简单的第一部分代码:

__block NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer;

//Performance Improvement from #2672
NSData *data = nil;
if (self.mutableData) {
   data = [self.mutableData copy];
   //We no longer need the reference, so nil it out to gain back some memory.
   self.mutableData = nil;
}

if (self.downloadFileURL) {
   userInfo[AFNetworkingTaskDidCompleteAssetPathKey] = self.downloadFileURL;
} else if (data) {
   userInfo[AFNetworkingTaskDidCompleteResponseDataKey] = data;
}

这部分代码从mutableData中取出了数据,设置了userInfo

userInfo[AFNetworkingTaskDidCompleteErrorKey] = error;

dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
    if (self.completionHandler) {
        self.completionHandler(task.response, responseObject, error);
    }

    dispatch_async(dispatch_get_main_queue(), ^{
        [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
    });
});

如果当前manager持有completionGroup或者completionQueue就使用它们。否则会创建一个dispatch_group_t并在主线程中调用completionHandler并发送通知(在主线程中)。

如果在执行当前task时没有遇到错误,那么先对数据进行序列化,然后同样调用block并发送通知。

dispatch_async(url_session_manager_processing_queue(), ^{ 
NSError *serializationError = nil;
 responseObject = [manager.responseSerializer responseObjectForResponse:task.response   data:data error:&serializationError]; 
if (self.downloadFileURL) { 
    responseObject = self.downloadFileURL; 
}

 if (responseObject) { 
    userInfo[AFNetworkingTaskDidCompleteSerializedResponseKey] = responseObject;
 }
 if (serializationError) { 
  userInfo[AFNetworkingTaskDidCompleteErrorKey] = serializationError; 
}

 dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
     if (self.completionHandler) {
         self.completionHandler(task.response, responseObject, serializationError); 
        } 
        dispatch_async(dispatch_get_main_queue(), ^{     
            [[NSNotificationCenter defaultCenter]     postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo]; 
        });
     });
});

代理方法URLSession:dataTask:didReceiveData: 和 - URLSession:downloadTask:didFinishDownloadingToURL:

这两个代理方法分别会在收到数据或者完成下载对应文件时调用,作用分别是mutableData 追加数据和处理下载文件:

- (void)URLSession:(__unused NSURLSession *)session
      dataTask:(__unused NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data
{
    [self.mutableData appendData:data];
}

- (void)URLSession:(NSURLSession *)session
  downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
    NSError *fileManagerError = nil;
    self.downloadFileURL = nil;

    if (self.downloadTaskDidFinishDownloading) {
        self.downloadFileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
        if (self.downloadFileURL) {
            [[NSFileManager defaultManager] moveItemAtURL:location toURL:self.downloadFileURL error:&fileManagerError];

            if (fileManagerError) {
                [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:fileManagerError.userInfo];
            }
        }
    }
}

五. 使用_AFURLSessionTaskSwizzling调剂方法

_AFURLSessionTaskSwizzing 的唯一功能就是修改NSURLSessionTaskresumesuspend方法,使用下面的方法替换原有的实现

- (void)af_resume {
    NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state");
    NSURLSessionTaskState state = [self state];
    [self af_resume];

    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];
    }
}

这样做的目的是为了在方法resume 或者 suspend 被调用时发出通知。

具体方法调剂的过程是在+load方法中进行的

load 方法只会在整个文件被引入是调用一次

+ (void)load {
    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];
    }
}

六. 引入 AFSecurityPolicy 保证请求的安全

AFSecurityPolicyAFNetworking 用来保证HTTP请求安全的类,它被AFURLSessionManager 持有,如果AFURLSessionManager 的实现文件中搜索self.securityPlicy,你只会得到三条搜索结果:

-  初始化self.securityPolicy = [AFSecurityPolicy defaultPolicy]

- 收到连接层的验证请求

- 任务接收到验证请求

API调用上,后两者都调用了- [AFSecurityPolicy evaluateServerTrust: forDomain:] 来判断当前服务器是否被信任,我们会在接下来的文章中具体介绍这个方法的实现作用。

- (void)URLSession:(NSURLSession *)session
          task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition  disposition, NSURLCredential *credential))completionHandler
{
    NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
    __block NSURLCredential *credential = nil;

    if (self.taskDidReceiveAuthenticationChallenge) {
        disposition = self.taskDidReceiveAuthenticationChallenge(session, task, challenge, &credential);
    } else {
        if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
            if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
                disposition = NSURLSessionAuthChallengeUseCredential;
                credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
            } else {
                disposition = NSURLSessionAuthChallengeRejectProtectionSpace;
            }
        } else {
            disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        }
    }

    if (completionHandler) {
        completionHandler(disposition, credential);
    }
}

如果没有传入taskDidReceiveAuthenticationChallenge block, 只有在上述方法返回YES时,才会获得认证凭证credential.

七. 引入AFNetworkReachabilityManager 监控网络状态

AFSecurityPolicy 相同,AFURLSessionManager 对网络状态的监控是由AFNetworkReachabilityManager 来负责的,它仅仅是持有一个AFNetworkReachabilityManager的对象。

真正需要判断网络状态时,仍然需要开发者调用对应的API获取网络状态。

小结

  1. AFURLSessionManager 是对NSURLSession的封装

  2. 它通过 - [AFURLSessionManager dataTaskWithRequest:completionHandler:] 等接口创建NSURLSessionDataTask的实例

  3. 持有一个字典mutableTaskDelegatesKeyByTaskIdentifier管理这些data task 实例。

  4. 引入 AFURLSessionManagerTaskDelegate 来对传入的uploadProgressBlock downloadProgressBlock、completionHandler 在合适的时间进行调用

  5. 实现了全部的代理方法来提供block接口

  6. 通过方法调剂在data task 状态改变时,发出通知。

上一篇下一篇

猜你喜欢

热点阅读