我们先看一下AFNetworking.h文件都给了我们什么方法
#import <Foundation/Foundation.h>
AFNetWorking基本上是所有iOS项目的标配。现在升级带最新版的3.X了。得益于苹果从NSURLConnection升级到NSURLSession,AFN也实现了api的简化,同时功能却一点没少。我们来看一下AFN3.X的目录结构:
AFNetWorking 这个文件是一个头文件。啥也没做,就是引入了其他文件方便使用。
AFURLSessionManager 这个文件是核心类,基本上通过它来实现了大部分核心功能。负责请求的建立、管理、销毁、安全、请求重定向、请求重启等各种功能。他主要实现了NSURLSession和NSRULSessionTask的封装。
AFHTTPSessionManager 这个文件是AFURLSessionManager的子类。主要实现了对HTTP请求的优化。
AFURLRequestSerialization 这个主要用于请求头的编码解码、序列化、优化处理、简化请求拼接过程等。
AFURLResponseSerialization 这个主要用于网络返回数据的序列化、编码解码、序列化、数据处理等。
AFSecurityPolicy 这个主要用于请求的认证功能。比如https的认证模式等。
AFNetworkReachabilityManager 这个主要用于监听网络请求状态变化功能。
首先说明,看AFN源码之前一定要搞清楚NSURLSession系列的api,这样能让你事半功倍,具体可以看AFNetWorking源码之NSRULSession系列概述。在这篇文章里,我们主要讲解AFURLSessionManager的实现原理和封装过程。首先我们通过一个简单的网络请求看一下他的基本用法(大部分都是非必须的,这里为了掩饰写出来):
- (IBAction)clickButton:(id)sender {//通过默认配置初始化SessionNSURLSessionConfiguration*configuration = [NSURLSessionConfigurationdefaultSessionConfiguration]; AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];//设置网络请求序列化对象AFHTTPRequestSerializer *requestSerializer = [AFHTTPRequestSerializer serializer]; [requestSerializer setValue:@"test"forHTTPHeaderField:@"requestHeader"]; requestSerializer.timeoutInterval =60; requestSerializer.stringEncoding =NSUTF8StringEncoding;//设置返回数据序列化对象AFHTTPResponseSerializer *responseSerializer = [AFHTTPResponseSerializer serializer]; manager.responseSerializer = responseSerializer;//网络请求安全策略if(true) { AFSecurityPolicy *securityPolicy; securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModePublicKey]; securityPolicy.allowInvalidCertificates =false; securityPolicy.validatesDomainName =YES; manager.securityPolicy = securityPolicy; }else{ manager.securityPolicy.allowInvalidCertificates =true; manager.securityPolicy.validatesDomainName =false; }//是否允许请求重定向if(true) { [manager setTaskWillPerformHTTPRedirectionBlock:^NSURLRequest*(NSURLSession*session,NSURLSessionTask*task,NSURLResponse*response,NSURLRequest*request) {if(response) {returnnil; }returnrequest; }]; }//监听网络状态[manager.reachabilityManager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {NSLog(@"%ld",(long)status); }]; [manager.reachabilityManager startMonitoring];NSURL*URL = [NSURLURLWithString:bigPic];NSURLRequest*request = [NSURLRequestrequestWithURL:URL];NSURLSessionDownloadTask*downloadTask = [manager downloadTaskWithRequest:request progress:^(NSProgress*downloadProgress){NSLog(@"下载进度:%lld",downloadProgress.completedUnitCount); } destination:^NSURL*(NSURL*targetPath,NSURLResponse*response) {NSURL*documentsDirectoryURL = [[NSFileManagerdefaultManager] URLForDirectory:NSDocumentDirectoryinDomain:NSUserDomainMaskappropriateForURL:nilcreate:NOerror:nil];NSURL*fileURL = [documentsDirectoryURL URLByAppendingPathComponent:[response suggestedFilename]];NSLog(@"fileURL:%@",[fileURL absoluteString]);returnfileURL; } completionHandler:^(NSURLResponse*response,NSURL*filePath,NSError*error) {self.imageView.image = [UIImageimageWithData:[NSDatadataWithContentsOfURL:filePath]];NSLog(@"File downloaded to: %@", filePath); }]; [downloadTask resume];}
通过这个请求,我们发现AFURLSessionManager要负责以下几块功能。
初始化和管理NSURLSession,通过它来建立和管理各种Task。
初始化和管理NSRULSessionTask,通过不同task来发送不同请求。
管理各种认证功能、安全功能、请求重定向、数据处理。
管理和组织每个task的各种状态管理和通知管理。不同task的回调处理。
帮我们管理和处理了NSRULSession系列api的各种代理方法。简化了我们的处理。
2 AFURLSessionManager的声明分析
AFURLSessionManager根据一个指定的NSURLSessionConfiguration创建和管理一个NSURLSession对象。并且这个对象实现了,,, 和这几个协议的协议方法。同时实现NSSecureCoding和NSCopying来实现归档解档和copy功能。
2.1AFURLSessionManager的初始化api
这些api主要用于初始化、安全策略、网络状态监听等:
interface AFURLSessionManager :NSObject//指定的初始化方法、通过他来初始化一个Manager对象。- (instancetype)initWithSessionConfiguration:(nullableNSURLSessionConfiguration*)configuration//AFURLSessionManager通过session来管理和创建网络请求。一个manager就实现了对这个session的管理,他们是一一对应的关系。@property(readonly,nonatomic,strong)NSURLSession*session;//处理网络请求回调的操作队列,就是我们初始化session的时候传入的那个OperationQueue参数。如果不传入,默认是MainOperationQueue。@property(readonly,nonatomic,strong)NSOperationQueue*operationQueue;//对返回数据的处理都通过这个属性来处理,比如数据的提取、转换等。默认是一个`AFJSONResponseSerializer`对象用JSON的方式解析。@property(nonatomic,strong)id responseSerializer;//用于指定session的安全策略。用于处理信任主机和证书认证等。默认是`defaultPolicy`。@property(nonatomic,strong) AFSecurityPolicy *securityPolicy;//观测网络状态的变化,具体可以看我的Demo用法。@property(readwrite,nonatomic,strong) AFNetworkReachabilityManager *reachabilityManager;@end
2.2AFURLSessionManager获取Task的api
这部分api主要是任务的创建、任务的分类、任务完成队列处理、特殊情况的任务重新创建等:
//当前session创建的所有Task,这个是下面三种task的总和。@property(readonly,nonatomic,strong)NSArray *tasks;//当前session创建的DataTask@property(readonly,nonatomic,strong)NSArray *dataTasks;//当前session创建的uploadTask@property(readonly,nonatomic,strong)NSArray *uploadTasks;//当前session创建的downloadTask@property(readonly,nonatomic,strong)NSArray *downloadTasks;//用于处理任务回调的GCD对象,默认是dispatch_main_queue。@property(nonatomic,strong,nullable)dispatch_queue_tcompletionQueue;//用于处理任务回调的GCD的group对象,如果不初始化、则一个默认的Group被使用。@property(nonatomic,strong,nullable) dispatch_group_t completionGroup;//在iOS7的环境下,我们通过background模式的session创建的uploadTask有时会是nil,如果这个属性是yes,AFN会尝试再次创建uploadTask。@property(nonatomic,assign)BOOLattemptsToRecreateUploadTasksForBackgroundSessions;//废除manager对应的Session。通过传入的参数来决定是否立即取消已经用session发出去的任务。- (void)invalidateSessionCancelingTasks:(BOOL)cancelPendingTasks;
2.3AFURLSessionManager为管理Task创建Block
AFURLSessionManager提供了很多创建Task的api。并且提供了很多处理Task的Block。应该说着几个api就是AFN为我们提供的最大价值,他把所有delegate方法细节都处理好。直接提供给我们一些最实用的api,我们就不用去管理session系列繁琐的delegate方法了。
//创建一个NSURLSessionDataTask- (NSURLSessionDataTask*)dataTaskWithRequest:(NSURLRequest*)request completionHandler:(nullablevoid(^)(NSURLResponse*response,id_Nullable responseObject,NSError* _Nullable error))completionHandler;//创建一个NSURLSessionDataTask,并且能获取上传或者下载进度- (NSURLSessionDataTask*)dataTaskWithRequest:(NSURLRequest*)request uploadProgress:(nullablevoid(^)(NSProgress*uploadProgress))uploadProgressBlock downloadProgress:(nullablevoid(^)(NSProgress*downloadProgress))downloadProgressBlock completionHandler:(nullablevoid(^)(NSURLResponse*response,id_Nullable responseObject,NSError* _Nullable error))completionHandler;//创建一个上传Task,并且指定上传文件的路径。- (NSURLSessionUploadTask*)uploadTaskWithRequest:(NSURLRequest*)request fromFile:(NSURL*)fileURL progress:(nullablevoid(^)(NSProgress*uploadProgress))uploadProgressBlock completionHandler:(nullablevoid(^)(NSURLResponse*response,id_Nullable responseObject,NSError* _Nullable error))completionHandler;////创建一个上传Task,并且指定上传的数据。- (NSURLSessionUploadTask*)uploadTaskWithRequest:(NSURLRequest*)request fromData:(nullableNSData*)bodyData progress:(nullablevoid(^)(NSProgress*uploadProgress))uploadProgressBlock completionHandler:(nullablevoid(^)(NSURLResponse*response,id_Nullable responseObject,NSError* _Nullable error))completionHandler;//创建一个uploadTask,然后上传数据- (NSURLSessionUploadTask*)uploadTaskWithStreamedRequest:(NSURLRequest*)request progress:(nullablevoid(^)(NSProgress*uploadProgress))uploadProgressBlock completionHandler:(nullablevoid(^)(NSURLResponse*response,id_Nullable responseObject,NSError* _Nullable error))completionHandler;//新建一个download任务,destination表示的下载文件的缓存路径- (NSURLSessionDownloadTask*)downloadTaskWithRequest:(NSURLRequest*)request progress:(nullablevoid(^)(NSProgress*downloadProgress))downloadProgressBlock destination:(nullableNSURL* (^)(NSURL*targetPath,NSURLResponse*response))destination completionHandler:(nullablevoid(^)(NSURLResponse*response,NSURL* _Nullable filePath,NSError* _Nullable error))completionHandler;//继续恢复一个download任务。resumeData参数表示的是恢复下载的时候初始化数据,比如前面已经下载好的部分数据。- (NSURLSessionDownloadTask*)downloadTaskWithResumeData:(NSData*)resumeData progress:(nullablevoid(^)(NSProgress*downloadProgress))downloadProgressBlock destination:(nullableNSURL* (^)(NSURL*targetPath,NSURLResponse*response))destination completionHandler:(nullablevoid(^)(NSURLResponse*response,NSURL* _Nullable filePath,NSError* _Nullable error))completionHandler;//获取指定Task的上传进度- (nullableNSProgress*)uploadProgressForTask:(NSURLSessionTask*)task;//获取指定Task的下载进度- (nullableNSProgress*)downloadProgressForTask:(NSURLSessionTask*)task;
注意:上面所有Task的progress都不在主线程、所以要在progress中做UI更新,都必须手动在主线程操作。
2.4AFURLSessionManager设置各种情况的代理回调
这些回调Block主要是用于处理网络请求过程或者结束以后的数据处理、认证、通知、缓存等。我们可以通过设置这些Block来获取或者检测各种状态。相当于就是钩子函数。通过下面的这些Block,我们基本可以获取请求过程中的所有状态以及需要做的各种处理。
//设置Session出错或者无效的手的回调Block。这个Block主要在`NSURLSessionDelegate`代理的`URLSession:didBecomeInvalidWithError:`方法中执行。- (void)setSessionDidBecomeInvalidBlock:(nullablevoid(^)(NSURLSession*session,NSError*error))block{ }//当网络请需要的认证信息比如用户名密码已经发送了的时候,就可以通过这个Block来处理。这个Block是在`NSURLSessionDelegate`代理里面的`URLSession:didReceiveChallenge:completionHandler:`方法中被执行。注意这个是针对Session- (void)setSessionDidReceiveAuthenticationChallengeBlock:(nullableNSURLSessionAuthChallengeDisposition(^)(NSURLSession*session,NSURLAuthenticationChallenge*challenge,NSURLCredential* _Nullable __autoreleasing * _Nullable credential))block{ }////当网络请需要的认证信息比如用户名密码已经发送了的时候,就可以通过这个Block来处理。这个Block是在`NSURLSessionTaskDelegate`代理里面的`URLSession:task:didReceiveChallenge:completionHandler:`方法中被执行。注意这个是针对Task。- (void)setTaskDidReceiveAuthenticationChallengeBlock:(nullableNSURLSessionAuthChallengeDisposition(^)(NSURLSession*session,NSURLSessionTask*task,NSURLAuthenticationChallenge*challenge,NSURLCredential* _Nullable __autoreleasing * _Nullable credential))block{ }//当请求需要一个新的bodystream的时候,就可以通过这个Block来设置。这个Block在`NSURLSessionTaskDelegate` 代理协议的`URLSession:task:needNewBodyStream:`方法里面设置。- (void)setTaskNeedNewBodyStreamBlock:(nullableNSInputStream* (^)(NSURLSession*session,NSURLSessionTask*task))block{ }//当一个网络请求需要重定向的时候。就会调用这个Block。这个Block是在`NSURLSessionTaskDelegate`协议的`URLSession:willPerformHTTPRedirection:newRequest:completionHandler:`方法中调用的。- (void)setTaskWillPerformHTTPRedirectionBlock:(nullableNSURLRequest* (^)(NSURLSession*session,NSURLSessionTask*task,NSURLResponse*response,NSURLRequest*request))block{ }//可以通过设置这个Block来获取上传进度。这个Block主要在`NSURLSessionTaskDelegate`协议的 `URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:`方法中调用.- (void)setTaskDidSendBodyDataBlock:(nullablevoid(^)(NSURLSession*session,NSURLSessionTask*task, int64_t bytesSent, int64_t totalBytesSent, int64_t totalBytesExpectedToSend))block{ }//设置一个Task完成以后执行的Block,这个Block在`NSURLSessionTaskDelegate`协议的 `URLSession:task:didCompleteWithError:`方法中执行。- (void)setTaskDidCompleteBlock:(nullablevoid(^)(NSURLSession*session,NSURLSessionTask*task,NSError* _Nullable error))block{ }//当接收到网络请求返回以后,可以调用这个Block。这个Block是在`NSURLSessionDataDelegate`协议的 `URLSession:dataTask:didReceiveResponse:completionHandler:`- (void)setDataTaskDidReceiveResponseBlock:(nullableNSURLSessionResponseDisposition(^)(NSURLSession*session,NSURLSessionDataTask*dataTask,NSURLResponse*response))block{ }//如果一个dataTask转换为downLoadTask以后,就可以设置这个Block来调用。在`NSURLSessionDataDelegate` 协议的`URLSession:dataTask:didBecomeDownloadTask:`方法中调用。- (void)setDataTaskDidBecomeDownloadTaskBlock:(nullablevoid(^)(NSURLSession*session,NSURLSessionDataTask*dataTask,NSURLSessionDownloadTask*downloadTask))block{ }//当dataTask接收到数据以后,可以设置调用这个Block。具体在`NSURLSessionDataDelegate`协议的`URLSession:dataTask:didReceiveData:`方法。- (void)setDataTaskDidReceiveDataBlock:(nullablevoid(^)(NSURLSession*session,NSURLSessionDataTask*dataTask,NSData*data))block{ }//设置一个Block来决定是否处理或者换成网络请求缓存。具体在`NSURLSessionDataDelegate`协议的`URLSession:dataTask:willCacheResponse:completionHandler:`方法中。- (void)setDataTaskWillCacheResponseBlock:(nullableNSCachedURLResponse* (^)(NSURLSession*session,NSURLSessionDataTask*dataTask,NSCachedURLResponse*proposedResponse))block{ }//当session所有的任务都发送出去以后,就可以通过这个Block来获取。具体在`NSURLSessionDataDelegate`协议的 `URLSessionDidFinishEventsForBackgroundURLSession:`方法中。- (void)setDidFinishEventsForBackgroundURLSessionBlock:(nullablevoid(^)(NSURLSession*session))block{ }//当一个downloadTask执行完毕以后,可以通过这个Block来获取下载信息,我们可以通过这个Block获取下载文件的位置。具体在`NSURLSessionDownloadDelegate`协议的`URLSession:downloadTask:didFinishDownloadingToURL:`方法中被调用。- (void)setDownloadTaskDidFinishDownloadingBlock:(nullableNSURL* _Nullable (^)(NSURLSession*session,NSURLSessionDownloadTask*downloadTask,NSURL*location))block{ }//可以通过这个Block获取一个downloadTask的下载进度。这个Block会在下载过程中多次被调用。具体是在`NSURLSessionDownloadDelegate`协议中的`URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesWritten:totalBytesExpectedToWrite:`方法中被调用。- (void)setDownloadTaskDidWriteDataBlock:(nullablevoid(^)(NSURLSession*session,NSURLSessionDownloadTask*downloadTask, int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite))block{ }//当一个downloadTask重新开始以后,我们可以通过这个Block获取fileOffSet等信息获取已经下载的部分以及总共有多少要下载。具体是在`NSURLSessionDownloadDelegate`协议的`URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:`方法中被调用。- (void)setDownloadTaskDidResumeBlock:(nullablevoid(^)(NSURLSession*session,NSURLSessionDownloadTask*downloadTask, int64_t fileOffset, int64_t expectedTotalBytes))block{ }
除了上面的部分,AFURLSessionManager的头文件还提供了很多notification的声明。通过这些通知,我们可以获取Task是否开始、是否完成、是否挂起、是否无效等各种通知。具体可以去文件里看。
3 AFURLSessionManager的实现分析
AFURLSessionManager.m文件里面除了有AFURLSessionManager.h定义的各种接口的实现意外,还有处理不同iOS版本下NSRULSession不同的部分,以及多个全局dispatch_queue_t的定义、以及处理NSURLSeesionTash的各种代理方法的实现和处理。具体划分如下:
NSURLSessionManager的实现。主要实现了接口文件定义的各种api的实现,比如Task的创建、Task的获取、Task的各种代理方法的实现、NSCoping和NSCoding协议、以及各种Block的实现。
基本属性的初始化。比如sessionConfiguration、operationQueue、session、mutableTaskDelegatesKeyedByTaskIdentifier等属性。以及用于实现task和AFURLSessionManagerTaskDelegate的绑定的taskDescriptionForSessionTasks、还有关键操作的锁属性lock。
接口文件的各种Block对应的属性,一个Block对应一个属性。
处理Task暂停与重启操作的方法。
给Task设置AFURLSessionManagerTaskDelegate代理的方法。
初始化Task的各种方法。
设置B接口文件定义的各种Block。
NSURLSession系列代理方法。
_AFURLSessionTaskSwizzling私有类。主要实现了iOS7和iOS8系统上NSURLSession差别的处理。让不同系统版本NSURLSession版本基本一致。
AFURLSessionManagerTaskDelegate这个类主要是把NSURLSeesion的部分代理方法让他处理。从而达到简化代码的目的。
处理Task的上传或者下载进度。
处理封装NSURLSeesion返回的数据。
Task完成等的通知封装。
全局dispatch_queue_t和dispatch_group_t的定义。各种通知名称的初始化,各种Block的类型定义。
3.1 AFURLSessionManager一个网络请求实现过程
我们通过一个网络请求过程来分析AFURLSessionManager.m的实现。我们通过initWithSessionConfiguration方法初始化一个manager。在这个方法里会初始化各种属性、以及为session属性设置代理:
接口文件中的代码如下:
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfigurationdefaultSessionConfiguration]];
实现文件中对应的处理如下:
/**
初始化方法
@return 返回一个manager对象
*/- (instancetype)init {return[selfinitWithSessionConfiguration:nil];}/**
默认初始化方法、通过这个方法来做manager的具体化初始化动作
@param configuration NSURLSession的配置
@return 返回一个manager对象
*/- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration*)configuration {self= [superinit];if(!self) {returnnil; }//如果用户没有手动指定,则使用默认的configuration来初始化if(!configuration) { configuration = [NSURLSessionConfigurationdefaultSessionConfiguration]; }//赋值给属性self.sessionConfiguration = configuration;//初始化NSURLSession的task代理方法执行的队列。//这里有一个很关键的点是task的代理执行的queque一次性只能执行一个task。这样就避免了task的代理方法执行的混乱。self.operationQueue = [[NSOperationQueuealloc] init];self.operationQueue.maxConcurrentOperationCount =1;//出丝滑NSURLSession对象,最核心的对象。self.session = [NSURLSessionsessionWithConfiguration:self.sessionConfiguration delegate:selfdelegateQueue:self.operationQueue];//如果用户没有手动指定,则返回的数据是JSON格式序列化。self.responseSerializer = [AFJSONResponseSerializer serializer];//指定https处理的安全策略。self.securityPolicy = [AFSecurityPolicy defaultPolicy];#if !TARGET_OS_WATCH//初始化网络状态监听属性self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];#endif//用于记录Task与他的`AFURLSessionManagerTaskDelegate`代理对象的一一对应关系。通过这个self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionaryalloc] init];//初始化一个锁对象,关键操作加锁。self.lock = [[NSLockalloc] init];self.lock.name = AFURLSessionManagerLockName;/**
获取当前session正在执行的所有Task。同时为每一个Task添加`AFURLSessionManagerTaskDelegate`代理对象,这个代理对象主要用于管理uplaodTak和downloadTask的进度管理。并且在Task执行完毕以后调用相应的Block。同时发送相应的notification对象,实现对task数据或者状态改变的检测。
@param dataTasks dataTask列表
@param uploadTasks uplaodTask列表
@param downloadTasks downloadTask列表
@return
*/[self.session getTasksWithCompletionHandler:^(NSArray*dataTasks,NSArray*uploadTasks,NSArray*downloadTasks) {for(NSURLSessionDataTask*taskindataTasks) { [selfaddDelegateForDataTask:task uploadProgress:nildownloadProgress:nilcompletionHandler:nil]; }for(NSURLSessionUploadTask*uploadTaskinuploadTasks) { [selfaddDelegateForUploadTask:uploadTask progress:nilcompletionHandler:nil]; }for(NSURLSessionDownloadTask*downloadTaskindownloadTasks) { [selfaddDelegateForDownloadTask:downloadTask progress:nildestination:nilcompletionHandler:nil]; } }];returnself;}
请求执行,接口文件如下:
NSURLSessionDownloadTask*downloadTask = [manager downloadTaskWithRequest:request progress:^(NSProgress*downloadProgress){NSLog(@"下载进度:%lld",downloadProgress.completedUnitCount);} destination:^NSURL*(NSURL*targetPath,NSURLResponse*response) {NSURL*documentsDirectoryURL = [[NSFileManagerdefaultManager] URLForDirectory:NSDocumentDirectoryinDomain:NSUserDomainMaskappropriateForURL:nilcreate:NOerror:nil];NSURL*fileURL = [documentsDirectoryURL URLByAppendingPathComponent:[response suggestedFilename]];NSLog(@"fileURL:%@",[fileURL absoluteString]);returnfileURL;} completionHandler:^(NSURLResponse*response,NSURL*filePath,NSError*error) {self.imageView.image = [UIImageimageWithData:[NSDatadataWithContentsOfURL:filePath]];NSLog(@"File downloaded to: %@", filePath);}];
实现文件则调用了很多方法:
1 首先是初始化一个NSURLSessionDownLoadTask对象
//通过session创建一个downloadTask,__blockNSURLSessionDownloadTask*downloadTask =nil;//url_session_manager_create_task_safely作用是修复在iOS8下面的系统bug。url_session_manager_create_task_safely(^{ downloadTask = [self.session downloadTaskWithRequest:request]; }); [selfaddDelegateForDownloadTask:downloadTask progress:downloadProgressBlock destination:destination completionHandler:completionHandler];returndownloadTask;
2 通过[self addDelegateForDownloadTask:downloadTask progress:downloadProgressBlock destination:destination completionHandler:completionHandler];这句话来为Task设置一个AFURLSessionManagerTaskDelegate代理对象。从而可以实现对进度处理、Block调用、Task完成返回数据的拼装的功能。
//根据指定的Task,初始化一个AFURLSessionManagerTaskDelegateAFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] initWithTask:downloadTask]; delegate.manager =self;//设置Task完成的回调Blockdelegate.completionHandler = completionHandler;if(destination) {//任务完成以后,调用destination这个Blockdelegate.downloadTaskDidFinishDownloading = ^NSURL* (NSURLSession* __unused session,NSURLSessionDownloadTask*task,NSURL*location) {returndestination(location, task.response); }; }//指定Task与taskDescriptionForSessionTasks的关联关系,方便后面的通知中做对应的处理。downloadTask.taskDescription =self.taskDescriptionForSessionTasks;//添加通知[selfsetDelegate:delegate forTask:downloadTask];//设置一个下载进度的Block,以便在后面代理方法中调用。delegate.downloadProgressBlock = downloadProgressBlock;
3 初始化一个AFURLSessionManagerTaskDelegate对象。在这个对象中对Task的请求过程进行处理和控制。
/**
初始化一个AFURLSessionManagerTaskDelegate对象
@param task 对象绑定的Task
@return 返回对象
*/- (instancetype)initWithTask:(NSURLSessionTask*)task {self= [superinit];if(!self) {returnnil; }//这个属性用于存储Task下载过程中的数据_mutableData = [NSMutableDatadata];//存储Task上传和下载的进度_uploadProgress = [[NSProgressalloc] initWithParent:niluserInfo:nil]; _downloadProgress = [[NSProgressalloc] initWithParent:niluserInfo:nil]; __weak__typeof__(task) weakTask = task;for(NSProgress*progressin@[ _uploadProgress, _downloadProgress ]) { progress.totalUnitCount =NSURLSessionTransferSizeUnknown; progress.cancellable =YES;//当progress对象取消的时候,取消Taskprogress.cancellationHandler = ^{ [weakTask cancel]; }; progress.pausable =YES; progress.pausingHandler = ^{//挂起Task[weakTask suspend]; };if([progress respondsToSelector:@selector(setResumingHandler:)]) { progress.resumingHandler = ^{//重启Task[weakTask resume]; }; }//更具progress的进度来获取Task的进度。fractionCompleted方法在请求过程中多次执行。[progress addObserver:selfforKeyPath:NSStringFromSelector(@selector(fractionCompleted)) options:NSKeyValueObservingOptionNewcontext:NULL]; }returnself;}//上面通过对fractionCompleted方法KVO。则会调用下面的方法,从而执行manager的- (void)observeValueForKeyPath:(NSString*)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void*)context {if([object isEqual:self.downloadProgress]) {//更新下载进度Blockif(self.downloadProgressBlock) {self.downloadProgressBlock(object); } }elseif([object isEqual:self.uploadProgress]) {//更新上传进度Blocif(self.uploadProgressBlock) {self.uploadProgressBlock(object); } }}
4 在AFURLSessionManagerTaskDelegate设置Task状态改变的监听。
/**
设置指定task的`AFURLSessionManagerTaskDelegate`对象。并且添加task挂起或者重启的监听。
@param delegate 代理对象
@param task task
*/- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate forTask:(NSURLSessionTask*)task{NSParameterAssert(task);NSParameterAssert(delegate);//加锁操作[self.lock lock];//为Task设置与之代理方法关联关系。通过一个字典self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;//添加对Task开始、重启、挂起状态的通知的接收。[selfaddNotificationObserverForTask:task]; [self.lock unlock];}/**
给Task添加任务开始、重启、挂起的通知
@param task 任务
*/- (void)addNotificationObserverForTask:(NSURLSessionTask*)task { [[NSNotificationCenterdefaultCenter] addObserver:selfselector:@selector(taskDidResume:) name:AFNSURLSessionTaskDidResumeNotification object:task]; [[NSNotificationCenterdefaultCenter] addObserver:selfselector:@selector(taskDidSuspend:) name:AFNSURLSessionTaskDidSuspendNotification object:task];}
5 从下面开始,任务就正式开始执行。其实就是[downloadTask resume];执行以后开始。
/**
在网络请求正式开始以后,这个方法会在数据接收的过程中多次调用。我们可以通过这个方法获取数据下载的大小、总得大小、还有多少么有下载
@param session session
@param downloadTask 对应的Task
@param bytesWritten 已经下载的字节
@param totalBytesWritten 总的字节大小
@param totalBytesExpectedToWrite nil
*/- (void)URLSession:(NSURLSession*)session downloadTask:(NSURLSessionDownloadTask*)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWrittentotalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{//获取Task对应的`AFURLSessionManagerTaskDelegate`对象。从而可以调用对应的代理方法AFURLSessionManagerTaskDelegate *delegate = [selfdelegateForTask:downloadTask];if(delegate) {//调用`AFURLSessionManagerTaskDelegate`类中的代理方法。从而实现对于进度更新等功能。//会调用下面的那个方法[delegate URLSession:session downloadTask:downloadTask didWriteData:bytesWritten totalBytesWritten:totalBytesWritten totalBytesExpectedToWrite:totalBytesExpectedToWrite]; }if(self.downloadTaskDidWriteData) {//如果有`downloadTaskDidWriteData`Block的实现,则在这个调用Block从而实现对下载进度过程的控制。self.downloadTaskDidWriteData(session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite); }}//AFURLSessionManagerTaskDelegate里面的这个代理方法实现对进度的更新。- (void)URLSession:(NSURLSession*)session downloadTask:(NSURLSessionDownloadTask*)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWrittentotalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{//AFURLSessionManagerTaskDelegate代理方法实现对下载进度的记录self.downloadProgress.totalUnitCount = totalBytesExpectedToWrite;self.downloadProgress.completedUnitCount = totalBytesWritten;}
6 Task完成以后,会调用AFURLSessionManagerTaskDelegate对象的方法对返回的数据封装。
//AFURLSessionManagerTaskDelegate里面的这个代理方法实现对数据的具体处理。- (void)URLSession:(__unusedNSURLSession*)session task:(NSURLSessionTask*)task didCompleteWithError:(NSError*)error{//获取Task对应的manager对象__strongAFURLSessionManager *manager =self.manager;//要封装的responseObject对象。__blockidresponseObject =nil; __blockNSMutableDictionary*userInfo = [NSMutableDictionarydictionary]; userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer;//返回的数据。NSData*data =nil;if(self.mutableData) { data = [self.mutableDatacopy];//We no longer need the reference, so nil it out to gain back some memory.self.mutableData =nil; }//如果是downloadTask,则封装downloadFileURLif(self.downloadFileURL) { userInfo[AFNetworkingTaskDidCompleteAssetPathKey] =self.downloadFileURL; }elseif(data) {//如果是其他Task,则封装返回的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(), ^{//如果Task有completionHandler。则调用这个Blockif(self.completionHandler) {self.completionHandler(task.response, responseObject, error); }//发送一个指定Task结束的通知dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenterdefaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo]; }); }); }else{//正确数据封装//在一个并行的dispat_queuq_t对象里面异步处理。dispatch_async(url_session_manager_processing_queue(), ^{NSError*serializationError =nil;//封装responseBojctresponseObject = [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(), ^{//如果Task有完成Block。则调用这个Blockif(self.completionHandler) {self.completionHandler(task.response, responseObject, serializationError); }//发送通知dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenterdefaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo]; }); }); }); }}
7 移除Task对应的通知和对应的AFURLSessionManagerTaskDelegate代理对象。
- (void)removeDelegateForTask:(NSURLSessionTask*)task {NSParameterAssert(task); [self.lock lock];//移除Task对应的通知[selfremoveNotificationObserverForTask:task];//移除Task对应的`AFURLSessionManagerTaskDelegate`代理对象。[self.mutableTaskDelegatesKeyedByTaskIdentifier removeObjectForKey:@(task.taskIdentifier)]; [self.lock unlock];}//移除通知监听- (void)removeNotificationObserverForTask:(NSURLSessionTask*)task { [[NSNotificationCenterdefaultCenter] removeObserver:selfname:AFNSURLSessionTaskDidSuspendNotification object:task]; [[NSNotificationCenterdefaultCenter] removeObserver:selfname:AFNSURLSessionTaskDidResumeNotification object:task];}//`AFURLSessionManagerTaskDelegate`对象回收。- (void)dealloc { [self.downloadProgress removeObserver:selfforKeyPath:NSStringFromSelector(@selector(fractionCompleted))]; [self.uploadProgress removeObserver:selfforKeyPath:NSStringFromSelector(@selector(fractionCompleted))];}
通过上面的过程,我们发现核心流程都是围绕了NSRULSessionTask对象以及与之绑定的AFURLSessionManagerTaskDelegate对象执行的。我们通过在NSRULSessionTask对象的代理方法里面手动调用AFURLSessionManagerTaskDelegate对应的代理方法来实现对数据的处理和简化代码的作用,这个设计思路的确吊吊的。还有一些方法没有涉及到,不过大同小异,基本过程就是这样,就不一一解释了。
3.2 AFURLSessionManager一些特殊模块的说明
AFURLSeeesionManager实现了NSSecureCoding协议。让manager可以归档解档。
/**
在iOS8以及以上环境下,supportsSecureCoding必须重写并且返回true。
@return bool
*/+ (BOOL)supportsSecureCoding {returnYES;}//解档- (instancetype)initWithCoder:(NSCoder*)decoder {NSURLSessionConfiguration*configuration = [decoder decodeObjectOfClass:[NSURLSessionConfigurationclass] forKey:@"sessionConfiguration"];self= [selfinitWithSessionConfiguration:configuration];if(!self) {returnnil; }returnself;}/**
我们发现对象归档的时候,只归档了`NSURLSessionConfiguration`属性。所以说归档接档的时候所有Block设置、operation设置都会失效。
@param coder coder
*/- (void)encodeWithCoder:(NSCoder*)coder { [coder encodeObject:self.session.configuration forKey:@"sessionConfiguration"];}
同时,AFURLSessionManager也实现了NSCopying协议。通过协议的实现过程,我们发现也是只使用了NSURLSessionConfiguration属性。和归档解档一样。
#pragma mark - 实现NSCopying协议。copy的NAURLSessionManager没有复制任何与代理处理相关的Block- (instancetype)copyWithZone:(NSZone*)zone {return[[[selfclass] allocWithZone:zone] initWithSessionConfiguration:self.session.configuration];}
有的时候,我们的请求会返回302这个状态码,这个表示需要请求重定向到另一个url,我们可以下面这个代理方法里面决定对于重定向的处理,如果对completionHandler传入nil,则会把response传入重定向请求。另外,backgroundSession的Task不会调用下面这个代理方法,而是直接调用。
/**
有的时候,我们的请求会返回302这个状态码,这个表示需要请求重定向到另一个url,我们可以在这个代理方法里面绝定对于重定向的处理。
@param session session
@param task task
@param response response
@param request 重定向的request。
@param completionHandler 请求完成
*/- (void)URLSession:(NSURLSession*)session task:(NSURLSessionTask*)task willPerformHTTPRedirection:(NSHTTPURLResponse*)response newRequest:(NSURLRequest*)request completionHandler:(void(^)(NSURLRequest*))completionHandler{//重定向的request对象NSURLRequest*redirectRequest = request;//如果用户指定了taskWillPerformHTTPRedirection这个Block,我们就通过这个Block的调用返回处理完成的request对象。if(self.taskWillPerformHTTPRedirection) { redirectRequest =self.taskWillPerformHTTPRedirection(session, task, response, request); }//这个调用是必须的,执行重定向操作。if(completionHandler) { completionHandler(redirectRequest); }}
创建NSRULSessionUplaodTask的时候,在某些系统上会出现bug。AFN已经帮我们处理好:
- (NSURLSessionUploadTask*)uploadTaskWithRequest:(NSURLRequest*)request fromFile:(NSURL*)fileURL progress:(void(^)(NSProgress*uploadProgress)) uploadProgressBlock completionHandler:(void(^)(NSURLResponse*response,idresponseObject,NSError*error))completionHandler{ __blockNSURLSessionUploadTask*uploadTask =nil;//用线程安全的方式创建一个dataTask。修复iOS8下面的bug。url_session_manager_create_task_safely(^{ uploadTask = [self.session uploadTaskWithRequest:request fromFile:fileURL]; });//用于处理uploadTask在iOS7环境下面有可能创建失败的情况。如果attemptsToRecreateUploadTasksForBackgroundSessions为true。则尝试重新创建Task。如果三次都没有成功,则放弃。if(!uploadTask &&self.attemptsToRecreateUploadTasksForBackgroundSessions &&self.session.configuration.identifier) {for(NSUIntegerattempts =0; !uploadTask && attempts < AFMaximumNumberOfAttemptsToRecreateBackgroundSessionUploadTask; attempts++) { uploadTask = [self.session uploadTaskWithRequest:request fromFile:fileURL]; } }//为Task添加`AFURLSessionManagerTaskDelegate`代理方法[selfaddDelegateForUploadTask:uploadTask progress:uploadProgressBlock completionHandler:completionHandler];returnuploadTask;}
通过使用dispatch_semaphore_t来控制对异步处理返回结果的控制。非常有借鉴意义。
#pragma mark - 获取当前session对应的task列表。通过dispatch_semaphore_t来控制访问过程。- (NSArray*)tasksForKeyPath:(NSString*)keyPath { __blockNSArray*tasks =nil; dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); [self.session getTasksWithCompletionHandler:^(NSArray*dataTasks,NSArray*uploadTasks,NSArray*downloadTasks) {if([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) { tasks = dataTasks; }elseif([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) { tasks = uploadTasks; }elseif([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) { tasks = downloadTasks; }elseif([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) { tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"]; }//这里发送一个信号量,让semaphore变为1。此时表示tasks已经成功获取。dispatch_semaphore_signal(semaphore); }];//这里会一直等待信号量变为1。dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);//返回Task。通过信号量控制,避免了方法结束的时候,tasks还没有正常获取的情况。returntasks;}
4 _AFURLSessionTaskSwizzling私有类的说明
在iOS7和iOS8及以上的系统,NSRULSessionTask的具体实现是不同的。我们目前知道的不同有:
NSURLSessionTasks是一个类簇。所以我们初始化一个Task的时候,我们并不只到初始化的到底是哪个子类。
简单的通过[NSURLSessionTask class]并不会起作用。必须通过NSURLSession创建一个task对象。然后获取他所在的类。
iOS7下面,下面代码中的localDataTask对象的继承关系是__NSCFLocalDataTask->__NSCFLocalSessionTask->__NSCFURLSessionTask。
在iOS8以及以上系统。下面代码中的localDataTask对象的继承关系是__NSCFLocalDataTask->__NSCFLocalSessionTask->NSURLSessionTask。
在iOS7下面__NSCFLocalSessionTask和__NSCFURLSessionTask实现了resume和suspend方法,同时最重要的是他不调用父类的实现。但是iOS8下面,只有NSURLSessionTask实现了resume和suspend。所以在iOS7的环境下,我们需要想办法让resume和suspend调用NSURLSessionTask的具体实现。
下面的代码完美的向我们展示了一个向类添加方法,并且swizzle方法实现的过程。值得仔细琢磨。
/**
切换theClass类的`originalSelector`和`swizzledSelector`的实现
@param theClass 类
@param originalSelector 方法一
@param swizzledSelector 方法2
*/staticinlinevoidaf_swizzleSelector(Class theClass, SEL originalSelector, SEL swizzledSelector) { Method originalMethod = class_getInstanceMethod(theClass, originalSelector); Method swizzledMethod = class_getInstanceMethod(theClass, swizzledSelector); method_exchangeImplementations(originalMethod, swizzledMethod);}/**
动态给一个类添加方法
@param theClass 类
@param selector 方法名字
@param method 方法体
@return bool
*/staticinlineBOOLaf_addMethod(Class theClass, SEL selector, Method method) {returnclass_addMethod(theClass, selector, method_getImplementation(method), method_getTypeEncoding(method));}@implementation_AFURLSessionTaskSwizzling+ (void)load {if(NSClassFromString(@"NSURLSessionTask")) {NSURLSessionConfiguration*configuration = [NSURLSessionConfigurationephemeralSessionConfiguration];NSURLSession* session = [NSURLSessionsessionWithConfiguration:configuration];#pragma GCC diagnostic push#pragma GCC diagnostic ignored"-Wnonnull"//初始化一个dataTask对象NSURLSessionDataTask*localDataTask = [session dataTaskWithURL:nil];#pragma clang diagnostic pop//获取af_resume这个方法的实现。IMP originalAFResumeIMP = method_getImplementation(class_getInstanceMethod([selfclass],@selector(af_resume)));//获取dataTask的具体类Class currentClass = [localDataTaskclass];//如果父类有resume方法。则改变方法的具体实现。while(class_getInstanceMethod(currentClass,@selector(resume))) { Class superClass = [currentClass superclass];//找到类和父类的resume方法实现IMP classResumeIMP = method_getImplementation(class_getInstanceMethod(currentClass,@selector(resume))); IMP superclassResumeIMP = method_getImplementation(class_getInstanceMethod(superClass,@selector(resume)));if(classResumeIMP != superclassResumeIMP && originalAFResumeIMP != classResumeIMP) {//添加方法、然后转换方法的实现[selfswizzleResumeAndSuspendMethodForClass:currentClass]; } currentClass = [currentClass superclass]; } [localDataTask cancel]; [session finishTasksAndInvalidate]; }}/**
主要是实现了为一个类添加方法、并且转换添加方法和原来对应方法的实现。
@param theClass 要操作的类
*/+ (void)swizzleResumeAndSuspendMethodForClass:(Class)theClass { Method afResumeMethod = class_getInstanceMethod(self,@selector(af_resume)); Method afSuspendMethod = class_getInstanceMethod(self,@selector(af_suspend));//为theClass类添加一个af_resume方法。if(af_addMethod(theClass,@selector(af_resume), afResumeMethod)) {//把dataTask的resume和afresume方法的实现互换。af_swizzleSelector(theClass,@selector(resume),@selector(af_resume)); }//为theClass类添加一个af_suspend方法if(af_addMethod(theClass,@selector(af_suspend), afSuspendMethod)) {//把dataTask的suspend和af_suspend方法的实现互换。af_swizzleSelector(theClass,@selector(suspend),@selector(af_suspend)); }}- (NSURLSessionTaskState)state {NSAssert(NO,@"State method should never be called in the actual dummy class");returnNSURLSessionTaskStateCanceling;}/**
在iOS7下面,`NSURLSessionDataTask`调用resume方法其实就是执行`af_resume`的具体实现。
*/- (void)af_resume {NSAssert([selfrespondsToSelector:@selector(state)],@"Does not respond to state");NSURLSessionTaskStatestate = [selfstate];//这里其实就是调用dataTask的resume实现[selfaf_resume];if(state !=NSURLSessionTaskStateRunning) {//这里的self其实就是`NSRULSessionDataTask`对象[[NSNotificationCenterdefaultCenter] postNotificationName:AFNSURLSessionTaskDidResumeNotification object:self]; }}/**
在iOS7下面,`NSURLSessionDataTask`调用suspend方法其实就是执行`af_suspend`的具体实现。
*/- (void)af_suspend {NSAssert([selfrespondsToSelector:@selector(state)],@"Does not respond to state");NSURLSessionTaskStatestate = [selfstate];//这里其实就是调用dataTask的suspend具体实现[selfaf_suspend];if(state !=NSURLSessionTaskStateSuspended) {//这里的self其实就是`NSRULSessionDataTask`对象[[NSNotificationCenterdefaultCenter] postNotificationName:AFNSURLSessionTaskDidSuspendNotification object:self]; }}@end
5 总结
AFURLSessionManager通过对task设置一个AFURLSessionManagerTaskDelegate代理来处理繁杂的请求进度管理。从而降低了代码的负责度。是代理模式的一个很好的实践。
AFURLSessionManager通过私有类_AFURLSessionTaskSwizzling来修改iOS7和iOS8系统上面不同。是对于方法swizzle的一个成功和完整的实践。
AFURLSessionManager通过添加各种Block,让我们对请求过程有全方位的控制和处理。同时提供简洁的api,把负责的处理全部封装好。
作者:NS西北风