AFNetworking之AFURLSessionManager深入学习

此文章主要记录笔者在AFNetworking源码阅读中的一下个人理解, 每次阅读都会记录一下, 如有错误, 请指正.

1. AFNetworking的使用

    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
    manager.requestSerializer.timeoutInterval = 60.0f;
    manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript",@"text/html",nil];
    
    [manager GET:@"" parameters:nil progress:^(NSProgress * _Nonnull downloadProgress) {
        NSLog(@"%@", downloadProgress);
    } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        NSLog(@"%@", responseObject);
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        NSLog(@"%@", error);
    }];

我们在使用AFNetworking发送GET请求时如上诉代码, 这里我们以一个GET请求为例, 逐步分析代码.
AFNetworking中核心的发送网络请求的代码在AFURLSessionManager中, AFHTTPSessionManagerAFURLSessionManager的子类, 主要对HTTP请求做了一写封装.

2. AFURLSessionManager实例创建


2. GET请求方法

- (NSURLSessionDataTask *)GET:(NSString *)URLString
                   parameters:(id)parameters
                     progress:(void (^)(NSProgress * _Nonnull))downloadProgress
                      success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success
                      failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure
{
    // 创建一个task
    NSURLSessionDataTask *extractedExpr = [self dataTaskWithHTTPMethod:@"GET"
                                                             URLString:URLString
                                                            parameters:parameters
                                                        uploadProgress:nil
                                                      downloadProgress:downloadProgress
                                                               success:success
                                                               failure:failure];
    NSURLSessionDataTask *dataTask = extractedExpr;

    // 执行task, 开始网络请求
    [dataTask resume];

    return dataTask;
}

这个方法看起来很简单, 根据传入的参数使用dataTaskWithHTTPMethod方法创建dataTask任务, 并且调用[dataTask resume]方法开启任务, 将创建的dataTask任务返回.
dataTask任务可以进行cancel, suspendresume操作.

  • cancel:取消当前的任务, 但是会标记一个被取消的任务, 任务取消以后会调用-URLSession:task:didCompleteWithError:方法, 将错误信息{ NSURLErrorDomain, NSURLErrorCancelled }传递出去. 处于suspended状态的任务也可以被取消.
  • suspend:暂停当前任务, 被暂停的任务可能还会继续调用代理方法, 比如汇报接收数据的情况, 但是不会在发送数据. 超时计时器会在任务被暂停时挂起.
  • resume:不仅可以启动任务, 还可以唤醒状态为suspend的任务.

3. dataTaskWithHTTPMethod方法

我们来看一下dataTaskWithHTTPMethod方法是怎么创建dataTask任务的.

- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
                                       URLString:(NSString *)URLString
                                      parameters:(id)parameters
                                  uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress
                                downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress
                                         success:(void (^)(NSURLSessionDataTask *, id))success
                                         failure:(void (^)(NSURLSessionDataTask *, NSError *))failure
{
    // 1.将传入的参数, 以及其他参数创建一个request, 其中设置了header请求头, 将参数进行百分号编码
    NSError *serializationError = nil;
    NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
    if (serializationError) {
        if (failure) {
            dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
                failure(nil, serializationError);
            });
        }

        return nil;
    }

    // 2.将request和session做关联, 在init的时候创建了session的代理, 利用session创建datatask, 以datatask的id作为key AFN代理作为value存入mutableTaskDelegatesKeyedByTaskIdentifier字典中, 建立关系. 利用session的代理将信息转发到AFN的代理中做统一处理.
    __block NSURLSessionDataTask *dataTask = nil;
    dataTask = [self dataTaskWithRequest:request
                          uploadProgress:uploadProgress
                        downloadProgress:downloadProgress
                       completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
        if (error) {
            if (failure) {
                failure(dataTask, error);
            }
        } else {
            if (success) {
                success(dataTask, responseObject);
            }
        }
    }];

    return dataTask;
}

可以看到这个方法内部分为了两部分, 第一部分是创建request请求, 第二部分是通过request请求创建dataTask任务.
request请求是通过AFURLRequestSerializationrequestWithMethod方法构建的,
关于 AFURLRequestSerialization我会有一篇专门的文章去介绍,
附上传送门 AFNetworking之AFURLRequestSerialization深入学习.
这里我们主要讲解AFURLSessionManager是如何处理dataTask任务, 将代理方法在AFURLSessionManagerTaskDelegate中做统一处理的.

4. 构建NSURLSessionDataTask

有了request后, 就可以调用AFURLSessionManager的方法来构建NSURLSessionDataTask

4.1 dataTaskWithRequest:方法

- (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 {

    NSLog(@"%@", [NSThread currentThread]);
    // 创建NSURLSessionDataTask
    __block NSURLSessionDataTask *dataTask = nil;
    url_session_manager_create_task_safely(^{
        dataTask = [self.session dataTaskWithRequest:request];
    });

    // 这里的作用:
    // manager管理dataTask和afn自定义的delegate
    [self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];

    return dataTask;
}

同样这个方法也分为两个部分, 第一部分创建dataTask任务, 第二部分调用addDelegateForDataTask方法管理dataTaskAFN自定义的delegate.
dataTask传入block中用__block修饰, 是指针传递, 这样就可以为dataTask赋值.

4.2 url_session_manager_create_task_safely函数

static void url_session_manager_create_task_safely(dispatch_block_t block) {
    if (NSFoundationVersionNumber < NSFoundationVersionNumber_With_Fixed_5871104061079552_bug) {
        // Fix of bug
        // Open Radar:http://openradar.appspot.com/radar?id=5871104061079552 (status: Fixed in iOS8)
        // Issue about:https://github.com/AFNetworking/AFNetworking/issues/2093
        // 串行队列中同步执行任务
        dispatch_sync(url_session_manager_creation_queue(), block);
    } else {
        block();
    }
}

使用url_session_manager_create_task_safely函数创建dataTask, 主要为了解决在iOS8以前的一个Bug.

4.3 addDelegateForDataTask:方法

- (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和manager建立关系
    AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] initWithTask:dataTask];
    // delegate弱引用self
    delegate.manager = self;
    // 将回调赋值给delegate, 在delegate的代理方法中执行
    delegate.completionHandler = completionHandler;

    // self.taskDescriptionForSessionTasks其实就是manager地址
    dataTask.taskDescription = self.taskDescriptionForSessionTasks;
    
    // 这是这里的关键---
    // 将datatask和delegate建立关系, 以task.taskIdentifier作为key, delegate作为value, 存储到一个mutableTaskDelegatesKeyedByTaskIdentifier(manager的属性)字典中
    [self setDelegate:delegate forTask:dataTask];

    // 设置上传和下载进度块
    delegate.uploadProgressBlock = uploadProgressBlock;
    delegate.downloadProgressBlock = downloadProgressBlock;
}

问题: 为什么要使用AFNURLSessionManagerTaskDelegate?
使用一个全局的字段保存AFNURLSessionManagerTaskDelegate和task, 在session的代理方法中如果需要处理数据, 就通过task取出对应的AFNURLSessionManagerTaskDelegate, 调用AFNURLSessionManagerTaskDelegate代理的对应方法进行统一数据的处理.

4.4 各种回调

@property (readwrite, nonatomic, copy) AFURLSessionDidBecomeInvalidBlock sessionDidBecomeInvalid;
@property (readwrite, nonatomic, copy) AFURLSessionDidReceiveAuthenticationChallengeBlock sessionDidReceiveAuthenticationChallenge;
@property (readwrite, nonatomic, copy) AFURLSessionDidFinishEventsForBackgroundURLSessionBlock didFinishEventsForBackgroundURLSession;
@property (readwrite, nonatomic, copy) AFURLSessionTaskWillPerformHTTPRedirectionBlock taskWillPerformHTTPRedirection;
@property (readwrite, nonatomic, copy) AFURLSessionTaskDidReceiveAuthenticationChallengeBlock taskDidReceiveAuthenticationChallenge;
@property (readwrite, nonatomic, copy) AFURLSessionTaskNeedNewBodyStreamBlock taskNeedNewBodyStream;
@property (readwrite, nonatomic, copy) AFURLSessionTaskDidSendBodyDataBlock taskDidSendBodyData;
@property (readwrite, nonatomic, copy) AFURLSessionTaskDidCompleteBlock taskDidComplete;
@property (readwrite, nonatomic, copy) AFURLSessionDataTaskDidReceiveResponseBlock dataTaskDidReceiveResponse;
@property (readwrite, nonatomic, copy) AFURLSessionDataTaskDidBecomeDownloadTaskBlock dataTaskDidBecomeDownloadTask;
@property (readwrite, nonatomic, copy) AFURLSessionDataTaskDidReceiveDataBlock dataTaskDidReceiveData;
@property (readwrite, nonatomic, copy) AFURLSessionDataTaskWillCacheResponseBlock dataTaskWillCacheResponse;
@property (readwrite, nonatomic, copy) AFURLSessionDownloadTaskDidFinishDownloadingBlock downloadTaskDidFinishDownloading;
@property (readwrite, nonatomic, copy) AFURLSessionDownloadTaskDidWriteDataBlock downloadTaskDidWriteData;
@property (readwrite, nonatomic, copy) AFURLSessionDownloadTaskDidResumeBlock downloadTaskDidResume;

各种回调, 供外界赋值, 会在代理方法中调用响应的回调, 将信息传递出去.

5.代理方法

AFHTTPSessionManager进行初始化的时候, 调用它的父类AFURLSessionManager的初始化方法.

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

AFURLSessionManager中创建session时, 设置了代理为AFURLSessionManager, 所以NSURLSession的相关代理是在AFURLSessionManager中实现的.

我们来看一下AFHTTPSessionManager实现的代理方法.

5.1 NSURLSessionDelegate

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

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

当前session失效时, 代理调用此代理方法.
如果外界实现了sessionDidBecomeInvalid的回调, 就会将当前session和错误信息error发送出去, 并且发送一个名字为AFURLSessionDidInvalidateNotification通知出去, 用户可以监听这个通知.

- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
    // 默认方式处理
    /*
    NSURLSessionAuthChallengeUseCredential 使用指定证书
    NSURLSessionAuthChallengePerformDefaultHandling 默认方式
    NSURLSessionAuthChallengeCancelAuthenticationChallenge 取消挑战
    */
    NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
    __block NSURLCredential *credential = nil;

    // sessionDidReceiveAuthenticationChallenge自定义的block, 外界可以通过set方法调用赋值, 自定义处理跳转
    if (self.sessionDidReceiveAuthenticationChallenge) {
        disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
    } else {
        // 这里服务器要求客户端接收挑战的方式是NSURLAuthenticationMethodServerTrust
        if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
            // 根据跳转的保护空间提供的信任 创建挑战证书
            // 检查服务端是否可以信任(在authenticationMethod为NSURLAuthenticationMethodServerTrust的情况下)
            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);
    }
}

当web服务器收到客户端的请求时, 有时候需要验证客户端用户是不是正常用户, 在决定是否返回真实数据, 这种情况成为客户端接收到挑战.
客户端根据服务器返回的challenge, 生成所需要的disposition(枚举类型, 应对挑战的方式)和credential(应对挑战生成的证书), 调用completionHandler将disposition和credential回应给服务器.
关于这部分的内容会在我的另一篇文章中做相关介绍, 传送门AFNetworking之AFSecurityPolicy深入学习.

5.2 NSURLSessionTaskDelegate

- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
        newRequest:(NSURLRequest *)request
 completionHandler:(void (^)(NSURLRequest *))completionHandler
{
    NSURLRequest *redirectRequest = request;
    
    if (self.taskWillPerformHTTPRedirection) {
        redirectRequest = self.taskWillPerformHTTPRedirection(session, task, response, request);
    }

    if (completionHandler) {
        completionHandler(redirectRequest);
    }
}

服务器重定向的时候会调用这个代理方法, 通过completionHandler将重定向的request传递给服务器.
如果用户实现了taskWillPerformHTTPRedirection方法, 就将taskWillPerformHTTPRedirection方法返回的重定向请求发送给服务器.

- (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 = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
            }
        } else {
            disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        }
    }

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

同上, 这里不再做介绍.

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

需要重新发送一个含有bodyStream的request给服务器的时候会调用调用此代理方法.
该方法会在下边两种情况下调用:

  • task是由uploadTaskWithStramedRequest创建的, 那么在提供初始化的request body stram是会调用
  • 因为认证挑战或者其他可恢复的服务器错误,而导致需要客户端重新发送一个含有body stream的request,这时候会调用
- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
   didSendBodyData:(int64_t)bytesSent
    totalBytesSent:(int64_t)totalBytesSent
totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
{

    int64_t totalUnitCount = totalBytesExpectedToSend;
    // 如果totalUnitCount获取失败, 就是用http header的Content-Length
    if(totalUnitCount == NSURLSessionTransferSizeUnknown) {
        NSString *contentLength = [task.originalRequest valueForHTTPHeaderField:@"Content-Length"];
        if(contentLength) {
            totalUnitCount = (int64_t) [contentLength longLongValue];
        }
    }
    
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
    // 转发到自定义的delegate中
    if (delegate) {
        [delegate URLSession:session task:task didSendBodyData:bytesSent totalBytesSent:totalBytesSent totalBytesExpectedToSend:totalBytesExpectedToSend];
    }

    // 如果实现了自定义的block, 将数据传送出去, 例如做进度条的展示
    if (self.taskDidSendBodyData) {
        self.taskDidSendBodyData(session, task, bytesSent, totalBytesSent, totalUnitCount);
    }
}

周期性调用, 通知代理发送到服务器端数据的进度.
调用delegateForTask方法获取和task相关联的AFURLSessionManagerTaskDelegate对象, 将任务转发到AFURLSessionManagerTaskDelegate对象方法中做处理, 这个我们会在第6部分讲解.
这是AFN转发的第一个方法

- (void)URLSession:(NSURLSession *)session
              task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
    // 根据task的taskIdentifier找delegate
    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];
        // 完成以后, 从字典mutableTaskDelegatesKeyedByTaskIdentifier移除当前task
        [self removeDelegateForTask:task];
    }

    // 调用block
    if (self.taskDidComplete) {
        self.taskDidComplete(session, task, error);
    }
}

数据传输完成的就会调用此代理方法, 无论是成功还是失败.
调用delegateForTask方法获取和task相关联的AFURLSessionManagerTaskDelegate对象, 将任务转发到AFURLSessionManagerTaskDelegate对象方法中做处理
这是AFN转发的第二个方法

5.3 NSURLSessionDataDelegate

- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
 completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler
{
    /*
    NSURLSessionResponseCancel, dataTask会取消, 相当于调用了[task cancel]方法
    NSURLSessionResponseAllow, dataTask正常运行
    NSURLSessionResponseBecomeDownload, 变为downloadTask, 会调用URLSession:dataTask:didBecomeDownloadTask:方法
    NSURLSessionResponseBecomeStream
    */
    
    // 设置为请求继续进行
    NSURLSessionResponseDisposition disposition = NSURLSessionResponseAllow;

    if (self.dataTaskDidReceiveResponse) {
        disposition = self.dataTaskDidReceiveResponse(session, dataTask, response);
    }

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

接收到服务端的相应的时候调用此代理方法, 询问是否要将当前的dataTask任务变成其他类型的任务, 如果不实现这个方法, 默认是任务继续执行.
completionHandler回调用需要传入NSURLSessionResponseDisposition类型, 就是接下来要怎样处理dataTask.
NSURLSessionResponseDisposition是一个枚举类型:

  • NSURLSessionResponseCancel, dataTask会取消, 相当于调用了[task cancel]方法
  • NSURLSessionResponseAllow, dataTask正常运行
  • NSURLSessionResponseBecomeDownload, 变为downloadTask, 会调用URLSession:dataTask:didBecomeDownloadTask:方法
  • NSURLSessionResponseBecomeStream, 变为一个流任务.
- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask
{
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask];
    if (delegate) {
        // 将dataTask关联的delegate删除
        [self removeDelegateForTask:dataTask];
        // 为downloadTask关联delegate
        [self setDelegate:delegate forTask:downloadTask];
    }

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

dataTask变成下载任务的时候就会调用这个代理方法.
根据dataTask获取与其关联的delegate, 并将dataTaskmutableTaskDelegatesKeyedByTaskIdentifier字典中移除, 将downloadTaskdelegate做关联.

- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
    didReceiveData:(NSData *)data
{

    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask];
    [delegate URLSession:session dataTask:dataTask didReceiveData:data];

    if (self.dataTaskDidReceiveData) {
        self.dataTaskDidReceiveData(session, dataTask, data);
    }
}

接收到服务器的数据时会周期性调用此代理方法.
data返回是自上一个调用以来接收到的数据, 所以我们要用一个容器去存储这些data数据.
AFN将这个方法转发到AFURLSessionManagerTaskDelegate对象中去处理, 将接收到的data数据拼接到mutableData中, 供后续处理.
这是AFN转发的第三个方法

- (void)URLSession:(NSURLSession *)session
          dataTask:(NSURLSessionDataTask *)dataTask
 willCacheResponse:(NSCachedURLResponse *)proposedResponse
 completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler
{
    NSCachedURLResponse *cachedResponse = proposedResponse;

    if (self.dataTaskWillCacheResponse) {
        cachedResponse = self.dataTaskWillCacheResponse(session, dataTask, proposedResponse);
    }

    if (completionHandler) {
        completionHandler(cachedResponse);
    }
}

询问是否缓存response响应, 当接收到所有的数据以后会调用此代理方法.
如果没有实现这个方法, 默认使用创建session时使用的configuration对象决定缓存策略.

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

当一个后台任务完成, 并且你的app在后台状态, app会在后台自动重新运行, 并且调用app的UIApplicationDelegate对象的-(void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)(void))completionHandler方法. 客户端应当存储completionHandler回调, 并且在- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session方法中调用这个completionHandler回调.

5.4 NSURLSessionDownloadDelegate

- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask];
    if (self.downloadTaskDidFinishDownloading) {
        // 自定义的block, 返回的是你想存储的文件地址路径
        NSURL *fileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
        // 如果fileURl路径存在, 说明用户想把数据存储起来
        if (fileURL) {
            delegate.downloadFileURL = fileURL;
            NSError *error = nil;
            
            // 从临时路径移动到我们定义的路径, 将location位置的文件全部移动到自定义的路径fileURL处
            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];
    }
}

当下载任务完成的时候回调用此代理方法.
由于这个location地址是临时的, 所以必须将其移动到应用程序的沙箱容器目录的永久位置中, 才能读取.
如果要打开阅读这个文件, 应该在其他线程进行操作.
这是AFN转发的第四个方法

- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
      didWriteData:(int64_t)bytesWritten
 totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
    
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask];
    
    if (delegate) {
        [delegate URLSession:session downloadTask:downloadTask didWriteData:bytesWritten totalBytesWritten:totalBytesWritten totalBytesExpectedToWrite:totalBytesExpectedToWrite];
    }

    if (self.downloadTaskDidWriteData) {
        self.downloadTaskDidWriteData(session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
    }
}

周期性调用此代理方法, 通知下载进度.
bytesWritten:从上次调用这个代理方法以后接收到的数据
totalBytesWritten:接收到数据的总字节数
totalBytesExpectedToWrite:期望接收到的数据字节总数, 有header中的Content-Length提供, 如果没有提供的话 默认是NSURLSessionTransferSizeUnknown
将数据转发到AFURLSessionManagerTaskDelegate代理中.
这是AFN转发的第五个方法

- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
 didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes
{
    
    AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask];
    
    if (delegate) {
        [delegate URLSession:session downloadTask:downloadTask didResumeAtOffset:fileOffset expectedTotalBytes:expectedTotalBytes];
    }

    if (self.downloadTaskDidResume) {
        self.downloadTaskDidResume(session, downloadTask, fileOffset, expectedTotalBytes);
    }
}

当下载任务重新开始时, 会调用此代理方法.
如果断点续传的下载任务被取消或者失败了, 你可以请求一个resumeData对象, 这个对象包含了足够的信息去重新开始下载任务.
你可以调用downloadTaskWithResumeData:方法或者downloadTaskWithResumeData:completionHandler:, 将resumeData作为方法的参数.
当你调用这两个方法时, 你会得到一个新的下载任务, 如果你重新开这个下载任务, 那么就会调用URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:代理方法, 意味着下载重新开始了.
这是AFN转发的第六个方法

至此, AFN中实现的代理方法已经全部描述完毕.

6. AFURLSessionManagerTaskDelegate中的方法

AFURLSessionManagerTaskDelegate中的方法是有AFURLSessionManager中实现的代理方法转发过来的. 在AFURLSessionManagerTaskDelegate的这些方法中做统一的处理.

6.1 NSURLSessionTaskDelegate

- (void)URLSession:(__unused NSURLSession *)session
              task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
    // 强引用, 防止被提前释放, 因为AFURLSessionManager强引用delegate, delegate有一个弱引用属性manager.
    __strong AFURLSessionManager *manager = self.manager;

    __block id responseObject = nil;

    // 创建一个userInfo字典, 存储信息, 这里创建的userInfo会通过通知发送出去.
    __block NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
    userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer;

    //Performance Improvement from #2672
    // 这里也是值得我们学习地方, mutableData存储的数据我们用一个局部变量保存, 并且清空mutableData, 在这个方法结束以后局部变量被清空, mutableData也指向nil.
    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中, 通知发送出去.
        userInfo[AFNetworkingTaskDidCompleteErrorKey] = error;
        
        // 使用group处理, 当所有的task任务都完成, 才会发送通知(个人理解为, AFN中并没有监听这个队列组, 是为了让使用者通过监听队列组)
        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(), ^{
                // 将userInfo通过通知信息发出去
                [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
            });
        });
    } else {
        // 并发队列异步线程解析数据, 不阻塞线程
        dispatch_async(url_session_manager_processing_queue(), ^{
            NSError *serializationError = nil;
            // 解析数据, 在AFURLResponseSerialization中解析, AFJSONResponseSerializer将数据解析成 json 格式
            responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];

            // 注意: 如果存在downloadFileURL, 证明已经将数据存储到了磁盘上了, 所以此处的responseObject存储的是data存放的位置, 将responseObject通过completionHandler传出去.
            if (self.downloadFileURL) {
                responseObject = self.downloadFileURL;
            }

            // 如果responseObject不为空, 就存储到userInfo字典中, 会通过通知发送出去
            if (responseObject) {
                userInfo[AFNetworkingTaskDidCompleteSerializedResponseKey] = responseObject;
            }

            // 如果解析错误, 将错误信息, 存储到userInfo字典中, 会通过通知发送出去
            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];
                });
            });
        });
    }
}

task完成以后会调用此代理方法, 无论成功还是失败都会调用.
url_session_manager_completion_group()是一个创建队列组单例的函数.
解析data数据会调用AFJSONResponseSerializerresponseObjectForResponse方法将data数据解析成responseObject, 关于AFJSONResponseSerializer我会在另外一篇文章讲解.
部分代码的功能已经在代码中注释.

6.2 NSURLSessionDataDelegate

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

    // 将接受到的数据拼接到mutableData中, 在接受数据完成的方法中, 操作mutableData解析即可.
    [self.mutableData appendData:data];
}

此方法在AFURLSessionManager的URLSession:dataTask:dataTask:方法中调用.
data数据拼接到mutableData中, 并且根据dataTask设置downloadProgress的进度.

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

此方法主要记录上传进度.

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

这两个方法主要记录下载的进度.

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

将下载文件移动到指定的下载位置.

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