AFNetworking 框架小结 三 (AFURLSessionManager)(转)

原创作者: 那夜的星空分外清澈
版权声明: 本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u011374318/article/details/79302085

AFURLSessionManager

如果说 AFURLRequestSerialization 是对网络请求的前期准备,而 AFURLResponseSerialization 是对网络请求结束后,对返回数据的后续处理的话,那么所缺少的便是两者之间的网络请求过程了。要开启一个网络请求过程,便需要创建一个 NSURLSessionTask 网络请求任务,而前提是需要先创建一个 NSURLSession 实例对象,所以 AFURLSessionManager 自然便是用来创建和管理 NSURLSession 会话对象的管理器了。

AFURLSessionManager 还有一个 AFHTTPSessionManager 子类,用于构造 HTTP 会话请求管理对象。

AFURLSessionManager 还遵循下面几个协议:

  • <NSURLSessionTaskDelegate>
  • <NSURLSessionDataDelegate>
  • <NSURLSessionDownloadDelegate>
  • <NSURLSessionDelegate>

在 AFURLSessionManager 中实现这些协议中声明的方法,用来处理网络请求过程中诸如暂停、取消、数据保存、更新进度条等操作。

AFURLSessionManager 中的属性

属性 类型 含义
session NSURLSession 会话管理器管理的对话
operationQueue NSOperationQueue 用于执行代理回调方法的队列
responseSerializer id 返回数据的解析器,默认时AFJSONResponseSerializer解析器
securityPolicy AFSecurityPolicy 建立安全会话时,使用的安全策略
reachabilityManager AFNetworkReachabilityManager 网络状态管理器
task NSArray<NSURLSessionTask *> 当前会话所管理的所有任务,包含数据请求/下载/上传
dataTasks NSArray<NSURLSessionDataTask *> 当前会话关联的数据请求任务
uploadTasks NSArray <NSURLSessionUploadTask *> 当前会话所关联的上传任务
downloadTasks NSArray <NSURLSessionDownloadTask *> 当前会话所关联的下载任务
completionQueue dispatch_queue_t 指定 completionBlock 执行时的队列,默认 NULL ,使用主队列
completionGroup dispatch_group_t 指定 completionBlock 相关联的组,默认 NULL ,将创建一个私有的组
attemptsToRecreateUploadTasksForBackgroundSessions BOOL 指明当创建后台上传任务失败后,是否重新尝试创建,default NO

下面为该类的内部属性

属性 类型 含义
sessionConfiguration NSURLSessionConfiguration 当前会话管理器用于创建会话的配置
mutableTaskDelegatesKeyedByTaskIdentifier NSMutableDictionary 用于保存当前会话创建的任务与任务的代理对象的对应关系
taskDescriptionForSessionTasks NSString 用于描述当前会话创建的任务(其实就是当前会话管理器的地址)
lock NSLock 锁,操作 mutableTaskDelegatesKeyedByTaskIdentifier 时使用

下面是一些回调代码块

  1. NSURLSessionDelegate 协议中方法使用的回调代码块

    //会话失效时的回调代码块
    @property (readwrite, nonatomic, copy) AFURLSessionDidBecomeInvalidBlock sessionDidBecomeInvalid;
    
    //连接服务器,接收到认证请求时,该回调代码块可以返回指定的认证选项
    @property (readwrite, nonatomic, copy) AFURLSessionDidReceiveAuthenticationChallengeBlock sessionDidReceiveAuthenticationChallenge;
    
  2. NSURLSessionTaskDelegate 协议中方法使用的回调代码块

    //任务需要流传递数据给服务端时,该代码块可以返回一个输入流
    @property (readwrite, nonatomic, copy) AFURLSessionTaskNeedNewBodyStreamBlock taskNeedNewBodyStream;
    
    //接收到重定向反馈时,该代码块可以指定重定向的链接
    @property (readwrite, nonatomic, copy) AFURLSessionTaskWillPerformHTTPRedirectionBlock taskWillPerformHTTPRedirection;
    
    //当数据任务被要求进行证书加密时,该代码块可以指定相关选择项,如使用证书、默认处理、取消请求
    @property (readwrite, nonatomic, copy) AFURLSessionTaskDidReceiveAuthenticationChallengeBlock taskDidReceiveAuthenticationChallenge;
    
    //当数据上传时,该代码回调可以用来获取本次上传的字节数、已经上传的字节数、该任务需要上传的总字节数
    @property (readwrite, nonatomic, copy) AFURLSessionTaskDidSendBodyDataBlock taskDidSendBodyData;
    
    //当任务结束时,该代码回调会被执行,可以获取错误信息,如果有的话
    @property (readwrite, nonatomic, copy) AFURLSessionTaskDidCompleteBlock taskDidComplete;
    
  3. NSURLSessionDataDelegate 协议中方法使用的回调代码块

    //当数据任务接收到服务器响应时,该回调可以选择取消或允许等选项
    @property (readwrite, nonatomic, copy) AFURLSessionDataTaskDidReceiveResponseBlock dataTaskDidReceiveResponse;
    
    //当数据任务将转变为数据下载任务时,该回调可以进行一些处理,回调参数中包含了原任务,和将要转变为的目标任务
    @property (readwrite, nonatomic, copy) AFURLSessionDataTaskDidBecomeDownloadTaskBlock dataTaskDidBecomeDownloadTask;
    
    //当接收到服务端数据时,该回调被执行
    @property (readwrite, nonatomic, copy) AFURLSessionDataTaskDidReceiveDataBlock dataTaskDidReceiveData;
    
    //将要缓存响应报文时,可以对返回的响应报文进行一些处理,回调参数中包含了 NSCachedURLResponse 的实例对象,也是即将返回的实例
    @property (readwrite, nonatomic, copy) AFURLSessionDataTaskWillCacheResponseBlock dataTaskWillCacheResponse;
    
    //当应用进入后台时创建的后台会话的相关任务均执行完毕时,该回调在主队列中被执行
    /**
    这里需要注意在 UIApplication 的下述方法中重新创建会话对象,注意使用 identifier 参数
    - (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler NS_AVAILABLE_IOS(7_0);
    */
    @property (readwrite, nonatomic, copy) AFURLSessionDidFinishEventsForBackgroundURLSessionBlock didFinishEventsForBackgroundURLSession;
    
  4. NSURLSessionDownloadDelegate 协议中方法使用的回调代码块

    //当下载任务结束时,该回调代码块可以指定下载的缓存数据移动到的目标地址
    @property (readwrite, nonatomic, copy) AFURLSessionDownloadTaskDidFinishDownloadingBlock downloadTaskDidFinishDownloading;
    
    //在数据下载的过程中,该回调会被调用,其参数包含有本次下载的数据字节数、已经下载的字节数、该任务需要下载的总字节数
    @property (readwrite, nonatomic, copy) AFURLSessionDownloadTaskDidWriteDataBlock downloadTaskDidWriteData;
    
    //当下载任务再次启动时,该回调执行,回调参数包含有文件的字节偏移量和整个文件的字节长度
    @property (readwrite, nonatomic, copy) AFURLSessionDownloadTaskDidResumeBlock downloadTaskDidResume;
    

这些回调代码块都是在 AFURLSessionManager 实现的代理方法中调用的,所以其都是在自定义队列中执行的(除了 didFinishEventsForBackgroundURLSession)所以,如果有需要在主队列中执行的回调,那么需要在创建代码时注意指定主队列。

这些代码块属性都是内部属性,每一个都声明了相应的赋值方法。

AFURLSessionManager 中的方法

  1. 初始化实例对象方法

    - (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration;
    

    在该方法中,如果入参 configuration 为 nil ,则调用 NSURLSessionConfiguration 的 defaultSessionConfiguration 方法创建一个作为会话配置,并使用该配置创建一个会话对象,并设置会话对象的代理为该会话管理器,创建代理方法的执行队列。

    另外,还初始化了安全策略、锁、返回数据解析器(JSON 数据解析器)等属性。

  2. 取消会话 session 对象

    - (void)invalidateSessionCancelingTasks:(BOOL)cancelPendingTasks;
    

    如果参数 cancelPendingTasks 为 YES ,那么直接取消会话,其相关联的任务和回调代码等都释放;如果为 NO ,则允许会话中的任务执行完毕后,再取消会话,会话一经取消将无法重启。

  3. 创建数据任务

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

    这里在使用会话对象 session 和入参 request 创建任务时,如果 NSFoundationVersionNumber 的值小于 NSFoundationVersionNumber_iOS_8_0 那么 dataTask 的创建会放在 af_url_session_manager_creation_queue 串行队列中同步执行,否则就由当前线程执行。接着,会调用下面的方法:

    - (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] initWithTask:dataTask];
        delegate.manager = self;
        delegate.completionHandler = completionHandler;
    
        dataTask.taskDescription = self.taskDescriptionForSessionTasks;
        [self setDelegate:delegate forTask:dataTask];
    
        delegate.uploadProgressBlock = uploadProgressBlock;
        delegate.downloadProgressBlock = downloadProgressBlock;
    }
    

    在这个方法中,会创建一个 AFURLSessionManagerTaskDelegate 对象,设置其相关联的管理器、任务描述(会话地址)、结束回调、上传回调、下载回调等属性,并且使用当前任务的 taskIdentifier 标识(通常从 1 开始,并在生成该任务的会话中是唯一的)同该 AFURLSessionManagerTaskDelegate 对象作为一个映射关系保存在会话 session 的 mutableTaskDelegatesKeyedByTaskIdentifier 字典中。

    除此之外,还会将当前会话注册为监听者,监听 task 任务发出的 AFNSURLSessionTaskDidResumeNotificationAFNSURLSessionTaskDidSuspendNotification 通知。当接收到该通知后,分别执行 taskDidResume:taskDidSuspend: 方法,在这两个方法中又发出了 AFNetworkingTaskDidResumeNotificationAFNetworkingTaskDidSuspendNotification 通知。

  4. 创建数据任务,不指定上传和下载回调代码块

    - (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
                                completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
    {
        return [self dataTaskWithRequest:request uploadProgress:nil downloadProgress:nil completionHandler:completionHandler];
    }
    

    这个方法其实是调用了上一个方法,只是参数 uploadProgressBlock 和 downloadProgressBlock 传 nil 。

  5. 创建文件上传任务

    - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request
                                             fromFile:(NSURL *)fileURL
                                             progress:(void (^)(NSProgress *uploadProgress)) uploadProgressBlock
                                    completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
    {
        __block NSURLSessionUploadTask *uploadTask = nil;
        url_session_manager_create_task_safely(^{
            uploadTask = [self.session uploadTaskWithRequest:request fromFile:fileURL];
        });
    
        // uploadTask may be nil on iOS7 because uploadTaskWithRequest:fromFile: may return nil despite being documented as nonnull (https://devforums.apple.com/message/926113#926113)
        if (!uploadTask && self.attemptsToRecreateUploadTasksForBackgroundSessions && self.session.configuration.identifier) {
            for (NSUInteger attempts = 0; !uploadTask && attempts < AFMaximumNumberOfAttemptsToRecreateBackgroundSessionUploadTask; attempts++) {
                uploadTask = [self.session uploadTaskWithRequest:request fromFile:fileURL];
            }
        }
    
        [self addDelegateForUploadTask:uploadTask progress:uploadProgressBlock completionHandler:completionHandler];
    
        return uploadTask;
    }
    

    在该方法中,如果后台会话对象创建文件上传任务失败时,会根据条件尝试重新创建,当然 AFMaximumNumberOfAttemptsToRecreateBackgroundSessionUploadTask 为 3 ,所以只能尝试 3 次。如果任务创建成功,则进而为任务创建一个 AFURLSessionManagerTaskDelegate 对象,作为任务的代理。

    请求报文的请求体数据即为根据参数 fileURL 获取的文件数据。

  6. 创建数据上传任务

    - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request
                                             fromData:(NSData *)bodyData
                                             progress:(void (^)(NSProgress *uploadProgress)) uploadProgressBlock
                                    completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
    {
        __block NSURLSessionUploadTask *uploadTask = nil;
        url_session_manager_create_task_safely(^{
            uploadTask = [self.session uploadTaskWithRequest:request fromData:bodyData];
        });
    
        [self addDelegateForUploadTask:uploadTask progress:uploadProgressBlock completionHandler:completionHandler];
    
        return uploadTask;
    }
    

    该方法上传数据,与上传文件类似,但待上传的数据直接由参数 bodyData 给出。

  7. 创建上传流任务

    - (NSURLSessionUploadTask *)uploadTaskWithStreamedRequest:(NSURLRequest *)request
                                                     progress:(void (^)(NSProgress *uploadProgress)) uploadProgressBlock
                                            completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
    {
        __block NSURLSessionUploadTask *uploadTask = nil;
        url_session_manager_create_task_safely(^{
            uploadTask = [self.session uploadTaskWithStreamedRequest:request];
        });
    
        [self addDelegateForUploadTask:uploadTask progress:uploadProgressBlock completionHandler:completionHandler];
    
        return uploadTask;
    }
    

    这里直接使用指定的请求报文头创建一个流任务,然后将任务与代理对象的关系保存到映射表中。

  8. 创建下载任务

    - (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request
                                                 progress:(void (^)(NSProgress *downloadProgress)) downloadProgressBlock
                                              destination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
                                        completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler
    {
        __block NSURLSessionDownloadTask *downloadTask = nil;
        url_session_manager_create_task_safely(^{
            downloadTask = [self.session downloadTaskWithRequest:request];
        });
    
        [self addDelegateForDownloadTask:downloadTask progress:downloadProgressBlock destination:destination completionHandler:completionHandler];
    
        return downloadTask;
    }
    
    • request 创建任务时使用的请求报文头信息
    • downloadProgressBlock 下载进度更新时调用的代码块,这个代码会在会话队列中调用,所以如果更新视图,需要自己在任务代码中指定主队列
    • destination 任务下载结束后,该参数可以返回指定的文件保存地址,缓存数据被移动到该地址,targetPath 为下载的数据缓存地址
    • completionHandler 下载任务结束后的回调

    在该方法中,使用 request 创建一个下载任务后,调用下面的方法:

    - (void)addDelegateForDownloadTask:(NSURLSessionDownloadTask *)downloadTask
                              progress:(void (^)(NSProgress *downloadProgress)) downloadProgressBlock
                           destination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
                     completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler
    {
        AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] initWithTask:downloadTask];
        delegate.manager = self;
        delegate.completionHandler = completionHandler;
    
        if (destination) {
            delegate.downloadTaskDidFinishDownloading = ^NSURL * (NSURLSession * __unused session, NSURLSessionDownloadTask *task, NSURL *location) {
                return destination(location, task.response);
            };
        }
    
        downloadTask.taskDescription = self.taskDescriptionForSessionTasks;
    
        [self setDelegate:delegate forTask:downloadTask];
    
        delegate.downloadProgressBlock = downloadProgressBlock;
    }
    

    与上面创建任务代理对象的方法类似,只是这里多出来一个为 downloadTaskDidFinishDownloading 赋值的步骤,这个代码块会在下载数据结束时用于获取数据的保存地址。

  9. 创建重用数据的下任务

    - (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData
                                                    progress:(NSProgress * __autoreleasing *)progress
                                                 destination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
                                           completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler
    {
        __block NSURLSessionDownloadTask *downloadTask = nil;
        dispatch_sync(url_session_manager_creation_queue(), ^{
            downloadTask = [self.session downloadTaskWithResumeData:resumeData];
        });
    
        [self addDelegateForDownloadTask:downloadTask progress:progress destination:destination completionHandler:completionHandler];
    
        return downloadTask;
    }
    

    使用已经下载的部分数据 resumeData 创建一个下载任务,继续进行下载。

  10. 获取任务的数据上传进度

`- (nullable NSProgress *)uploadProgressForTask:(NSURLSessionTask *)task;`
  1. 获取任务的数据下载进度
`- (nullable NSProgress *)downloadProgressForTask:(NSURLSessionTask *)task;`

该方法和上一个方法,都会调用下面的方法获取任务的代理对象,进而获取相应的进度信息。

```
- (AFURLSessionManagerTaskDelegate *)delegateForTask:(NSURLSessionTask *)task {
    NSParameterAssert(task);

    AFURLSessionManagerTaskDelegate *delegate = nil;
    [self.lock lock];
    delegate = self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)];
    [self.lock unlock];

    return delegate;
}
```

AFURLSessionManager 中实现的代理方法

AFURLSessionManager 遵循 NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate 协议,以处理网络请求过程中的数据。

有些代理方法中所做的任务,完全由 AFURLSessionManager 的代码块属性决定。如果这些属性并没有设置,那么相应的代理方法就没必要响应。所以 AFURLSessionManager 中重写了 respondsToSelector: 过滤了一些不必响应的代理方法。

- (BOOL)respondsToSelector:(SEL)selector {
    if (selector == @selector(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:)) {
        return self.taskWillPerformHTTPRedirection != nil;
    } else if (selector == @selector(URLSession:dataTask:didReceiveResponse:completionHandler:)) {
        return self.dataTaskDidReceiveResponse != nil;
    } else if (selector == @selector(URLSession:dataTask:willCacheResponse:completionHandler:)) {
        return self.dataTaskWillCacheResponse != nil;
    } else if (selector == @selector(URLSessionDidFinishEventsForBackgroundURLSession:)) {
        return self.didFinishEventsForBackgroundURLSession != nil;
    }

    return [[self class] instancesRespondToSelector:selector];
}

NSURLSessionDelegate

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

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

会话将要失效时,在这个方法中调用 sessionDidBecomeInvalid 回调,并发送一个 AFURLSessionDidInvalidateNotification 通知。

当会话连接,接收到服务端的加密要求时,执行下面的代理方法。

- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
    //首先选择默认的处理方式
    NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;

    //用于保存服务端返回的证书
    __block NSURLCredential *credential = nil;

    if (self.sessionDidReceiveAuthenticationChallenge) {
        //返回自己的处理方式
        disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
    } else {

        //如果对保护空间的验证方式不是 NSURLAuthenticationMethodServerTrust 则选择 NSURLSessionAuthChallengePerformDefaultHandling 处理方式
        if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {

            //如果校验返回的证书不通过,那么选择 NSURLSessionAuthChallengeCancelAuthenticationChallenge ,即取消会话
            if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
                credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
                if (credential) {
                    disposition = NSURLSessionAuthChallengeUseCredential;
                } else {
                    disposition = NSURLSessionAuthChallengePerformDefaultHandling;
                }
            } else {
                disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
            }
        } else {
            disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        }
    }

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

当应用进入后台,方法 -application:handleEventsForBackgroundURLSession:completionHandler: 执行,根据 identifier 入参创建适用于后台的会话对象,当所有与会话相关联的任务均已执行后,会话代理会接收到 URLSessionDidFinishEventsForBackgroundURLSession: 消息,从而进行一些回调操作。

- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
    if (self.didFinishEventsForBackgroundURLSession) {
        dispatch_async(dispatch_get_main_queue(), ^{
            self.didFinishEventsForBackgroundURLSession(session);
        });
    }
}

NSURLSessionTaskDelegate

该方法进行重定向,完全是执行了属性 taskWillPerformHTTPRedirection 设置的代码块任务。

- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
        newRequest:(NSURLRequest *)request
 completionHandler:(void (^)(NSURLRequest *))completionHandler

该方法

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

同上述 NSURLSessionDelegate 协议中实现的下述方法类似。

- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler;

其他方法参见如下源码:

- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
 needNewBodyStream:(void (^)(NSInputStream *bodyStream))completionHandler
{
    NSInputStream *inputStream = nil;

    if (self.taskNeedNewBodyStream) {
        inputStream = self.taskNeedNewBodyStream(session, task);
    } else if (task.originalRequest.HTTPBodyStream && [task.originalRequest.HTTPBodyStream conformsToProtocol:@protocol(NSCopying)]) {
        inputStream = [task.originalRequest.HTTPBodyStream copy];
    }

    if (completionHandler) {
        completionHandler(inputStream);
    }
}
- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
   didSendBodyData:(int64_t)bytesSent
    totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
{

    int64_t totalUnitCount = totalBytesExpectedToSend;
    if(totalUnitCount == NSURLSessionTransferSizeUnknown) {
        NSString *contentLength = [task.originalRequest valueForHTTPHeaderField:@"Content-Length"];
        if(contentLength) {
            totalUnitCount = (int64_t) [contentLength longLongValue];
        }
    }

    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];

    if (delegate) {
        [delegate URLSession:session task:task didSendBodyData:bytesSent totalBytesSent:totalBytesSent totalBytesExpectedToSend:totalBytesExpectedToSend];
    }

    if (self.taskDidSendBodyData) {
        self.taskDidSendBodyData(session, task, bytesSent, totalBytesSent, totalUnitCount);
    }
}
- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];

    // delegate may be nil when completing a task in the background
    if (delegate) {
        [delegate URLSession:session task:task didCompleteWithError:error];

        [self removeDelegateForTask:task];
    }

    if (self.taskDidComplete) {
        self.taskDidComplete(session, task, error);
    }
}

NSURLSessionDataDelegate

- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask
{
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask];
    if (delegate) {
        [self removeDelegateForTask:dataTask];
        [self setDelegate:delegate forTask:downloadTask];
    }

    if (self.dataTaskDidBecomeDownloadTask) {
        self.dataTaskDidBecomeDownloadTask(session, dataTask, downloadTask);
    }
}

这个方法中,会先获取原任务的代理,并将这个代理设置为新任务的代理,其他协议方法不再赘述(参见源码)。

NSURLSessionDownloadDelegate

当下载任务结束后,调用该代理方法。

- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
    //获取与任务相对应的代理对象
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask];

    //如果设置了回调任务,先执行回调任务
    if (self.downloadTaskDidFinishDownloading) {

        //获取下载数据要保存的地址        
        NSURL *fileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
        if (fileURL) {
            delegate.downloadFileURL = fileURL;
            NSError *error = nil;

            //移动下载的数据到指定地址
            if (![[NSFileManager defaultManager] moveItemAtURL:location toURL:fileURL error:&error]) {
                [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:error.userInfo];
            }

            return;
        }
    }

    if (delegate) {
        [delegate URLSession:session downloadTask:downloadTask didFinishDownloadingToURL:location];
    }
}

从上面的代码可知,如果会话管理器的 downloadTaskDidFinishDownloading 的代码块返回了地址,那么便不会去执行任务本身所对应的代理方法了,并且如果移动文件失败便会推送一个 AFURLSessionDownloadTaskDidFailToMoveFileNotification 通知。

下面两个协议方法中,都是先执行任务所关联的代理对象的方法,再执行会话对象设置的 downloadTaskDidWriteDatadownloadTaskDidResume 任务。

- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
      didWriteData:(int64_t)bytesWritten
 totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite;

- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
 didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes;

AFURLSessionManagerTaskDelegate

AFURLSessionManagerTaskDelegate 是个内部类,完全用于会话管理器内部,创建网络任务时,会相应的为该任务创建一个该对象作为其代理对象,其遵循 NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate 协议,实现的协议方法,在会话管理器实现的相应协议方法中被调用。

AFURLSessionManagerTaskDelegate 中的属性

属性 类型 含义
manager AFURLSessionManager 与该代理对象相关联的会话管理器
mutableData NSMutableData 用于保存在 NSURLSessionDataDelegate 协议方法中接收到的数据
uploadProgress NSProgress 上传进度
downloadProgress NSProgress 下载进度
downloadFileURL NSURL 下载文件的存储路径
//下载结束时,用于获取数据保存路径,其返回值会赋给 downloadFileURL
@property (nonatomic, copy) AFURLSessionDownloadTaskDidFinishDownloadingBlock downloadTaskDidFinishDownloading;

//uploadProgress.fractionCompleted 变化时的回调任务
@property (nonatomic, copy) AFURLSessionTaskProgressBlock uploadProgressBlock;

//downloadProgress.fractionCompleted 变化时的回调任务
@property (nonatomic, copy) AFURLSessionTaskProgressBlock downloadProgressBlock;

//任务结束时的回调,如果 manager.completionQueue 值为 NULL 则,该任务在主队列中执行
@property (nonatomic, copy) AFURLSessionTaskCompletionHandler completionHandler;

AFURLSessionManagerTaskDelegate 中的方法

- (instancetype)initWithTask:(NSURLSessionTask *)task {
    self = [super init];
    if (!self) {
        return nil;
    }

    _mutableData = [NSMutableData data];
    _uploadProgress = [[NSProgress alloc] initWithParent:nil userInfo:nil];
    _downloadProgress = [[NSProgress alloc] initWithParent:nil userInfo:nil];

    __weak __typeof__(task) weakTask = task;
    for (NSProgress *progress in @[ _uploadProgress, _downloadProgress ])
    {
        progress.totalUnitCount = NSURLSessionTransferSizeUnknown;
        progress.cancellable = YES;
        progress.cancellationHandler = ^{
            [weakTask cancel];
        };
        progress.pausable = YES;
        progress.pausingHandler = ^{
            [weakTask suspend];
        };
        if ([progress respondsToSelector:@selector(setResumingHandler:)]) {
            progress.resumingHandler = ^{
                [weakTask resume];
            };
        }
        [progress addObserver:self
                   forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
                      options:NSKeyValueObservingOptionNew
                      context:NULL];
    }
    return self;
}

在这个初始化方法中,主要为上传和下载进度进行了取消、暂停、重启的设置,并为它们的 fractionCompleted 属性值注册了监听者,即当前代理对象。

下面是 KVO 模式的监听响应方法,执行了相应的回调任务。

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
   if ([object isEqual:self.downloadProgress]) {
        if (self.downloadProgressBlock) {
            self.downloadProgressBlock(object);
        }
    }
    else if ([object isEqual:self.uploadProgress]) {
        if (self.uploadProgressBlock) {
            self.uploadProgressBlock(object);
        }
    }
}

AFURLSessionManagerTaskDelegate 中实现的代理方法

NSURLSessionDataDelegate

在实现该协议的方法中,主要是修改了上传和下载进度以及保存接收的数据。

- (void)URLSession:(__unused NSURLSession *)session
          dataTask:(__unused NSURLSessionDataTask *)dataTask
    didReceiveData:(NSData *)data
{
    self.downloadProgress.totalUnitCount = dataTask.countOfBytesExpectedToReceive;
    self.downloadProgress.completedUnitCount = dataTask.countOfBytesReceived;

    [self.mutableData appendData:data];
}

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
   didSendBodyData:(int64_t)bytesSent
    totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend{

    self.uploadProgress.totalUnitCount = task.countOfBytesExpectedToSend;
    self.uploadProgress.completedUnitCount = task.countOfBytesSent;
}

NSURLSessionDownloadDelegate

文件下载的过程中,下面的代理方法可能不止一次被调用,而其主要任务也是修改下载进度。

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
      didWriteData:(int64_t)bytesWritten
 totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{

    self.downloadProgress.totalUnitCount = totalBytesExpectedToWrite;
    self.downloadProgress.completedUnitCount = totalBytesWritten;
}

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
 didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes{

    self.downloadProgress.totalUnitCount = expectedTotalBytes;
    self.downloadProgress.completedUnitCount = fileOffset;
}

在这个方法中,首先将 downloadFileURL 属性置为 nil ,并且 downloadTaskDidFinishDownloading 代码块必需要返回数据的保存地址,而后才能将文件从缓存空间移动到指定的位置,如果移动出错,也会推送 AFURLSessionDownloadTaskDidFailToMoveFileNotification 通知。

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

    if (self.downloadTaskDidFinishDownloading) {
        self.downloadFileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
        if (self.downloadFileURL) {
            NSError *fileManagerError = nil;

            if (![[NSFileManager defaultManager] moveItemAtURL:location toURL:self.downloadFileURL error:&fileManagerError]) {
                [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:fileManagerError.userInfo];
            }
        }
    }
}

需要注意的是,如果 manager 管理器的 downloadTaskDidFinishDownloading 任务代码块能够返回保存地址,那么上述方法便不会被调用,所以对于多个文件同时下载,并且都需要更新下载进度或其他操作时,注意应将 downloadTaskDidFinishDownloading 设置为 nil 。

NSURLSessionTaskDelegate

该类只实现了 NSURLSessionTaskDelegate 协议中的一个方法(排除继承的协议中的方法),首先构造 userInfo 用于回传信息,字典中的信息如下:

键名称 值含义
AFNetworkingTaskDidCompleteResponseSerializerKey 会话管理器 manager 的解析器 responseSerializer
AFNetworkingTaskDidCompleteAssetPathKey 文件地址
AFNetworkingTaskDidCompleteResponseDataKey 数据
AFNetworkingTaskDidCompleteErrorKey 下载任务过程中的报错信息
AFNetworkingTaskDidCompleteSerializedResponseKey 解析器的解析结果,如果是下载任务,则为文件保存地址
AFNetworkingTaskDidCompleteErrorKey 解析器解析数据过程中的报错信息

当任务是因发生错误而结束时,直接调用 completionHandler 回调。

当任务正常结束时,那么会话管理器 manager 的解析器 responseSerializer 便会调用解析方法对接收的报文数据进行解析,并返回解析结果。而后,将该解析结果传给 completionHandler 回调任务。

另外,调用 completionHandler 回调之后,还会在主线程中推送了一个 AFNetworkingTaskDidCompleteNotification 通知,其携带 userInfo 信息。

参见下面的源码:

- (void)URLSession:(__unused NSURLSession *)session
              task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
    __strong AFURLSessionManager *manager = self.manager;

    __block id responseObject = nil;

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

    if (error) {
        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];
            });
        });
    } else {
        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];
                });
            });
        });
    }
}

类型定义参考
typedef void (^AFURLSessionDidBecomeInvalidBlock)(NSURLSession *session, NSError *error);
typedef NSURLSessionAuthChallengeDisposition (^AFURLSessionDidReceiveAuthenticationChallengeBlock)(NSURLSession *session, NSURLAuthenticationChallenge *challenge, NSURLCredential * __autoreleasing *credential);

typedef NSURLRequest * (^AFURLSessionTaskWillPerformHTTPRedirectionBlock)(NSURLSession *session, NSURLSessionTask *task, NSURLResponse *response, NSURLRequest *request);
typedef NSURLSessionAuthChallengeDisposition (^AFURLSessionTaskDidReceiveAuthenticationChallengeBlock)(NSURLSession *session, NSURLSessionTask *task, NSURLAuthenticationChallenge *challenge, NSURLCredential * __autoreleasing *credential);
typedef void (^AFURLSessionDidFinishEventsForBackgroundURLSessionBlock)(NSURLSession *session);

typedef NSInputStream * (^AFURLSessionTaskNeedNewBodyStreamBlock)(NSURLSession *session, NSURLSessionTask *task);
typedef void (^AFURLSessionTaskDidSendBodyDataBlock)(NSURLSession *session, NSURLSessionTask *task, int64_t bytesSent, int64_t totalBytesSent, int64_t totalBytesExpectedToSend);
typedef void (^AFURLSessionTaskDidCompleteBlock)(NSURLSession *session, NSURLSessionTask *task, NSError *error);

typedef NSURLSessionResponseDisposition (^AFURLSessionDataTaskDidReceiveResponseBlock)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLResponse *response);
typedef void (^AFURLSessionDataTaskDidBecomeDownloadTaskBlock)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLSessionDownloadTask *downloadTask);
typedef void (^AFURLSessionDataTaskDidReceiveDataBlock)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSData *data);
typedef NSCachedURLResponse * (^AFURLSessionDataTaskWillCacheResponseBlock)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSCachedURLResponse *proposedResponse);

typedef NSURL * (^AFURLSessionDownloadTaskDidFinishDownloadingBlock)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, NSURL *location);
typedef void (^AFURLSessionDownloadTaskDidWriteDataBlock)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite);
typedef void (^AFURLSessionDownloadTaskDidResumeBlock)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, int64_t fileOffset, int64_t expectedTotalBytes);
typedef void (^AFURLSessionTaskProgressBlock)(NSProgress *);

typedef void (^AFURLSessionTaskCompletionHandler)(NSURLResponse *response, id responseObject, NSError *error);

</article>

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容