AFNetWorking
基本上是全部iOS項目的標配。如今升級帶最新版的3.X了。得益於蘋果從NSURLConnection
升級到NSURLSession
,AFN也實現了api的簡化,同時功能卻一點沒少。咱們來看一下AFN3.X的目錄結構:html
AFNetWorking 這個文件是一個頭文件。啥也沒作,就是引入了其餘文件方便使用。git
AFURLSessionManager 這個文件是核心類,基本上經過它來實現了大部分核心功能。負責請求的創建、管理、銷燬、安全、請求重定向、請求重啓等各類功能。他主要實現了NSURLSession
和NSRULSessionTask
的封裝。github
AFHTTPSessionManager 這個文件是AFURLSessionManager
的子類。主要實現了對HTTP請求的優化。api
AFURLRequestSerialization 這個主要用於請求頭的編碼解碼、序列化、優化處理、簡化請求拼接過程等。緩存
AFURLResponseSerialization 這個主要用於網絡返回數據的序列化、編碼解碼、序列化、數據處理等。安全
AFSecurityPolicy 這個主要用於請求的認證功能。好比https的認證模式等。網絡
AFNetworkReachabilityManager 這個主要用於監聽網絡請求狀態變化功能。session
首先說明,看AFN源碼以前必定要搞清楚NSURLSession
系列的api,這樣能讓你事半功倍,具體能夠看AFNetWorking源碼之NSRULSession系列概述。在這篇文章裏,咱們主要講解AFURLSessionManager
的實現原理和封裝過程。首先咱們經過一個簡單的網絡請求看一下他的基本用法(大部分都是非必須的,這裏爲了掩飾寫出來):app
- (IBAction)clickButton:(id)sender { //經過默認配置初始化Session NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; 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) { return nil; } return request; }]; } //監聽網絡狀態 [manager.reachabilityManager setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) { NSLog(@"%ld",(long)status); }]; [manager.reachabilityManager startMonitoring]; NSURL *URL = [NSURL URLWithString:bigPic]; NSURLRequest *request = [NSURLRequest requestWithURL:URL]; NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:^(NSProgress *downloadProgress){ NSLog(@"下載進度:%lld",downloadProgress.completedUnitCount); } destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) { NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil]; NSURL *fileURL = [documentsDirectoryURL URLByAppendingPathComponent:[response suggestedFilename]]; NSLog(@"fileURL:%@",[fileURL absoluteString]); return fileURL; } completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) { self.imageView.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:filePath]]; NSLog(@"File downloaded to: %@", filePath); }]; [downloadTask resume]; }
經過這個請求,咱們發現AFURLSessionManager
要負責如下幾塊功能。異步
初始化和管理NSURLSession
,經過它來創建和管理各類Task。
初始化和管理NSRULSessionTask
,經過不一樣task來發送不一樣請求。
管理各類認證功能、安全功能、請求重定向、數據處理。
管理和組織每一個task的各類狀態管理和通知管理。不一樣task的回調處理。
幫咱們管理和處理了NSRULSession
系列api的各類代理方法。簡化了咱們的處理。
AFURLSessionManager
根據一個指定的NSURLSessionConfiguration
建立和管理一個NSURLSession
對象。而且這個對象實現了<NSURLSessionTaskDelegate>
, <NSURLSessionDataDelegate>
, <NSURLSessionDownloadDelegate>
, 和 <NSURLSessionDelegate>
這幾個協議的協議方法。同時實現NSSecureCoding
和NSCopying
來實現歸檔解檔和copy功能。
AFURLSessionManager
的初始化api這些api主要用於初始化、安全策略、網絡狀態監聽等:
interface AFURLSessionManager : NSObject <NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate, NSSecureCoding, NSCopying> //指定的初始化方法、經過他來初始化一個Manager對象。 - (instancetype)initWithSessionConfiguration:(nullable NSURLSessionConfiguration *)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 <AFURLResponseSerialization> responseSerializer; //用於指定session的安全策略。用於處理信任主機和證書認證等。默認是`defaultPolicy`。 @property (nonatomic, strong) AFSecurityPolicy *securityPolicy; //觀測網絡狀態的變化,具體能夠看個人Demo用法。 @property (readwrite, nonatomic, strong) AFNetworkReachabilityManager *reachabilityManager; @end
AFURLSessionManager
獲取Task的api這部分api主要是任務的建立、任務的分類、任務完成隊列處理、特殊狀況的任務從新建立等:
//當前session建立的全部Task,這個是下面三種task的總和。 @property (readonly, nonatomic, strong) NSArray <NSURLSessionTask *> *tasks; //當前session建立的DataTask @property (readonly, nonatomic, strong) NSArray <NSURLSessionDataTask *> *dataTasks; //當前session建立的uploadTask @property (readonly, nonatomic, strong) NSArray <NSURLSessionUploadTask *> *uploadTasks; //當前session建立的downloadTask @property (readonly, nonatomic, strong) NSArray <NSURLSessionDownloadTask *> *downloadTasks; //用於處理任務回調的GCD對象,默認是dispatch_main_queue。 @property (nonatomic, strong, nullable) dispatch_queue_t completionQueue; //用於處理任務回調的GCD的group對象,若是不初始化、則一個默認的Group被使用。 @property (nonatomic, strong, nullable) dispatch_group_t completionGroup; //在iOS7的環境下,咱們經過background模式的session建立的uploadTask有時會是nil,若是這個屬性是yes,AFN會嘗試再次建立uploadTask。 @property (nonatomic, assign) BOOL attemptsToRecreateUploadTasksForBackgroundSessions; //廢除manager對應的Session。經過傳入的參數來決定是否當即取消已經用session發出去的任務。 - (void)invalidateSessionCancelingTasks:(BOOL)cancelPendingTasks;
AFURLSessionManager
爲管理Task建立BlockAFURLSessionManager
提供了不少建立Task的api。而且提供了不少處理Task的Block。應該說着幾個api就是AFN爲咱們提供的最大價值,他把全部delegate方法細節都處理好。直接提供給咱們一些最實用的api,咱們就不用去管理session系列繁瑣的delegate方法了。
//建立一個NSURLSessionDataTask - (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler; //建立一個NSURLSessionDataTask,而且能獲取上傳或者下載進度 - (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request uploadProgress:(nullable void (^)(NSProgress *uploadProgress))uploadProgressBlock downloadProgress:(nullable void (^)(NSProgress *downloadProgress))downloadProgressBlock completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler; //建立一個上傳Task,而且指定上傳文件的路徑。 - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgressBlock completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler; ////建立一個上傳Task,而且指定上傳的數據。 - (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromData:(nullable NSData *)bodyData progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgressBlock completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler; //建立一個uploadTask,而後上傳數據 - (NSURLSessionUploadTask *)uploadTaskWithStreamedRequest:(NSURLRequest *)request progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgressBlock completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler; //新建一個download任務,destination表示的下載文件的緩存路徑 - (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgressBlock destination:(nullable NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination completionHandler:(nullable void (^)(NSURLResponse *response, NSURL * _Nullable filePath, NSError * _Nullable error))completionHandler; //繼續恢復一個download任務。resumeData參數表示的是恢復下載的時候初始化數據,好比前面已經下載好的部分數據。 - (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgressBlock destination:(nullable NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination completionHandler:(nullable void (^)(NSURLResponse *response, NSURL * _Nullable filePath, NSError * _Nullable error))completionHandler; //獲取指定Task的上傳進度 - (nullable NSProgress *)uploadProgressForTask:(NSURLSessionTask *)task; //獲取指定Task的下載進度 - (nullable NSProgress *)downloadProgressForTask:(NSURLSessionTask *)task;
注意:上面全部Task的progress都不在主線程、因此要在progress中作UI更新,都必須手動在主線程操做。
AFURLSessionManager
設置各類狀況的代理回調這些回調Block主要是用於處理網絡請求過程或者結束之後的數據處理、認證、通知、緩存等。咱們能夠經過設置這些Block來獲取或者檢測各類狀態。至關於就是鉤子函數。經過下面的這些Block,咱們基本能夠獲取請求過程當中的全部狀態以及須要作的各類處理。
//設置Session出錯或者無效的手的回調Block。這個Block主要在`NSURLSessionDelegate`代理的`URLSession:didBecomeInvalidWithError:`方法中執行。 - (void)setSessionDidBecomeInvalidBlock:(nullable void (^)(NSURLSession *session, NSError *error))block{ } //當網絡請須要的認證信息好比用戶名密碼已經發送了的時候,就能夠經過這個Block來處理。這個Block是在`NSURLSessionDelegate`代理裏面的`URLSession:didReceiveChallenge:completionHandler:`方法中被執行。注意這個是針對Session - (void)setSessionDidReceiveAuthenticationChallengeBlock:(nullable NSURLSessionAuthChallengeDisposition (^)(NSURLSession *session, NSURLAuthenticationChallenge *challenge, NSURLCredential * _Nullable __autoreleasing * _Nullable credential))block{ } ////當網絡請須要的認證信息好比用戶名密碼已經發送了的時候,就能夠經過這個Block來處理。這個Block是在`NSURLSessionTaskDelegate`代理裏面的`URLSession:task:didReceiveChallenge:completionHandler:`方法中被執行。注意這個是針對Task。 - (void)setTaskDidReceiveAuthenticationChallengeBlock:(nullable NSURLSessionAuthChallengeDisposition (^)(NSURLSession *session, NSURLSessionTask *task, NSURLAuthenticationChallenge *challenge, NSURLCredential * _Nullable __autoreleasing * _Nullable credential))block{ } //當請求須要一個新的bodystream的時候,就能夠經過這個Block來設置。這個Block在`NSURLSessionTaskDelegate` 代理協議的`URLSession:task:needNewBodyStream:`方法裏面設置。 - (void)setTaskNeedNewBodyStreamBlock:(nullable NSInputStream * (^)(NSURLSession *session, NSURLSessionTask *task))block{ } //當一個網絡請求須要重定向的時候。就會調用這個Block。這個Block是在`NSURLSessionTaskDelegate`協議的`URLSession:willPerformHTTPRedirection:newRequest:completionHandler:`方法中調用的。 - (void)setTaskWillPerformHTTPRedirectionBlock:(nullable NSURLRequest * (^)(NSURLSession *session, NSURLSessionTask *task, NSURLResponse *response, NSURLRequest *request))block{ } //能夠經過設置這個Block來獲取上傳進度。這個Block主要在`NSURLSessionTaskDelegate`協議的 `URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:`方法中調用. - (void)setTaskDidSendBodyDataBlock:(nullable void (^)(NSURLSession *session, NSURLSessionTask *task, int64_t bytesSent, int64_t totalBytesSent, int64_t totalBytesExpectedToSend))block{ } //設置一個Task完成之後執行的Block,這個Block在`NSURLSessionTaskDelegate`協議的 `URLSession:task:didCompleteWithError:`方法中執行。 - (void)setTaskDidCompleteBlock:(nullable void (^)(NSURLSession *session, NSURLSessionTask *task, NSError * _Nullable error))block{ } //當接收到網絡請求返回之後,能夠調用這個Block。這個Block是在`NSURLSessionDataDelegate`協議的 `URLSession:dataTask:didReceiveResponse:completionHandler:` - (void)setDataTaskDidReceiveResponseBlock:(nullable NSURLSessionResponseDisposition (^)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLResponse *response))block{ } //若是一個dataTask轉換爲downLoadTask之後,就能夠設置這個Block來調用。在`NSURLSessionDataDelegate` 協議的`URLSession:dataTask:didBecomeDownloadTask:`方法中調用。 - (void)setDataTaskDidBecomeDownloadTaskBlock:(nullable void (^)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLSessionDownloadTask *downloadTask))block{ } //當dataTask接收到數據之後,能夠設置調用這個Block。具體在`NSURLSessionDataDelegate`協議的`URLSession:dataTask:didReceiveData:`方法。 - (void)setDataTaskDidReceiveDataBlock:(nullable void (^)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSData *data))block{ } //設置一個Block來決定是否處理或者換成網絡請求緩存。具體在`NSURLSessionDataDelegate`協議的`URLSession:dataTask:willCacheResponse:completionHandler:`方法中。 - (void)setDataTaskWillCacheResponseBlock:(nullable NSCachedURLResponse * (^)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSCachedURLResponse *proposedResponse))block{ } //當session全部的任務都發送出去之後,就能夠經過這個Block來獲取。具體在`NSURLSessionDataDelegate`協議的 `URLSessionDidFinishEventsForBackgroundURLSession:`方法中。 - (void)setDidFinishEventsForBackgroundURLSessionBlock:(nullable void (^)(NSURLSession *session))block{ } //當一個downloadTask執行完畢之後,能夠經過這個Block來獲取下載信息,咱們能夠經過這個Block獲取下載文件的位置。具體在`NSURLSessionDownloadDelegate`協議的`URLSession:downloadTask:didFinishDownloadingToURL:`方法中被調用。 - (void)setDownloadTaskDidFinishDownloadingBlock:(nullable NSURL * _Nullable (^)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, NSURL *location))block{ } //能夠經過這個Block獲取一個downloadTask的下載進度。這個Block會在下載過程當中屢次被調用。具體是在`NSURLSessionDownloadDelegate`協議中的`URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesWritten:totalBytesExpectedToWrite:`方法中被調用。 - (void)setDownloadTaskDidWriteDataBlock:(nullable void (^)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, int64_t bytesWritten, int64_t totalBytesWritten, int64_t totalBytesExpectedToWrite))block{ } //當一個downloadTask從新開始之後,咱們能夠經過這個Block獲取fileOffSet等信息獲取已經下載的部分以及總共有多少要下載。具體是在`NSURLSessionDownloadDelegate`協議的`URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:`方法中被調用。 - (void)setDownloadTaskDidResumeBlock:(nullable void (^)(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, int64_t fileOffset, int64_t expectedTotalBytes))block{ }
除了上面的部分,AFURLSessionManager
的頭文件還提供了不少notification
的聲明。經過這些通知,咱們能夠獲取Task是否開始、是否完成、是否掛起、是否無效等各類通知。具體能夠去文件裏看。
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的類型定義。
咱們經過一個網絡請求過程來分析AFURLSessionManager.m
的實現。咱們經過initWithSessionConfiguration
方法初始化一個manager。在這個方法裏會初始化各類屬性、以及爲session屬性設置代理:
接口文件中的代碼以下:
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
實現文件中對應的處理以下:
/** 初始化方法 @return 返回一個manager對象 */ - (instancetype)init { return [self initWithSessionConfiguration:nil]; } /** 默認初始化方法、經過這個方法來作manager的具體化初始化動做 @param configuration NSURLSession的配置 @return 返回一個manager對象 */ - (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration { self = [super init]; if (!self) { return nil; } //若是用戶沒有手動指定,則使用默認的configuration來初始化 if (!configuration) { configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; } //賦值給屬性 self.sessionConfiguration = configuration; //初始化NSURLSession的task代理方法執行的隊列。 //這裏有一個很關鍵的點是task的代理執行的queque一次性只能執行一個task。這樣就避免了task的代理方法執行的混亂。 self.operationQueue = [[NSOperationQueue alloc] init]; self.operationQueue.maxConcurrentOperationCount = 1; //出絲滑NSURLSession對象,最核心的對象。 self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue: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 = [[NSMutableDictionary alloc] init]; //初始化一個鎖對象,關鍵操做加鎖。 self.lock = [[NSLock alloc] 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 *task in dataTasks) { [self addDelegateForDataTask:task uploadProgress:nil downloadProgress:nil completionHandler:nil]; } for (NSURLSessionUploadTask *uploadTask in uploadTasks) { [self addDelegateForUploadTask:uploadTask progress:nil completionHandler:nil]; } for (NSURLSessionDownloadTask *downloadTask in downloadTasks) { [self addDelegateForDownloadTask:downloadTask progress:nil destination:nil completionHandler:nil]; } }]; return self; }
請求執行,接口文件以下:
NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:^(NSProgress *downloadProgress){ NSLog(@"下載進度:%lld",downloadProgress.completedUnitCount); } destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) { NSURL *documentsDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil]; NSURL *fileURL = [documentsDirectoryURL URLByAppendingPathComponent:[response suggestedFilename]]; NSLog(@"fileURL:%@",[fileURL absoluteString]); return fileURL; } completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) { self.imageView.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:filePath]]; NSLog(@"File downloaded to: %@", filePath); }];
實現文件則調用了不少方法:
1 首先是初始化一個NSURLSessionDownLoadTask
對象
//經過session建立一個downloadTask, __block NSURLSessionDownloadTask *downloadTask = nil; //url_session_manager_create_task_safely做用是修復在iOS8下面的系統bug。 url_session_manager_create_task_safely(^{ downloadTask = [self.session downloadTaskWithRequest:request]; }); [self addDelegateForDownloadTask:downloadTask progress:downloadProgressBlock destination:destination completionHandler:completionHandler]; return downloadTask;
2 經過[self addDelegateForDownloadTask:downloadTask progress:downloadProgressBlock destination:destination completionHandler:completionHandler];
這句話來爲Task設置一個AFURLSessionManagerTaskDelegate
代理對象。從而能夠實現對進度處理、Block調用、Task完成返回數據的拼裝的功能。
//根據指定的Task,初始化一個AFURLSessionManagerTaskDelegate AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] initWithTask:downloadTask]; delegate.manager = self; //設置Task完成的回調Block delegate.completionHandler = completionHandler; if (destination) { //任務完成之後,調用destination這個Block delegate.downloadTaskDidFinishDownloading = ^NSURL * (NSURLSession * __unused session, NSURLSessionDownloadTask *task, NSURL *location) { return destination(location, task.response); }; } //指定Task與taskDescriptionForSessionTasks的關聯關係,方便後面的通知中作對應的處理。 downloadTask.taskDescription = self.taskDescriptionForSessionTasks; //添加通知 [self setDelegate:delegate forTask:downloadTask]; //設置一個下載進度的Block,以便在後面代理方法中調用。 delegate.downloadProgressBlock = downloadProgressBlock;
3 初始化一個AFURLSessionManagerTaskDelegate對象。在這個對象中對Task的請求過程進行處理和控制。
/** 初始化一個AFURLSessionManagerTaskDelegate對象 @param task 對象綁定的Task @return 返回對象 */ - (instancetype)initWithTask:(NSURLSessionTask *)task { self = [super init]; if (!self) { return nil; } //這個屬性用於存儲Task下載過程當中的數據 _mutableData = [NSMutableData data]; //存儲Task上傳和下載的進度 _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對象取消的時候,取消Task progress.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:self forKeyPath:NSStringFromSelector(@selector(fractionCompleted)) options:NSKeyValueObservingOptionNew context:NULL]; } return self; } //上面經過對fractionCompleted方法KVO。則會調用下面的方法,從而執行manager的 - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context { if ([object isEqual:self.downloadProgress]) { //更新下載進度Block if (self.downloadProgressBlock) { self.downloadProgressBlock(object); } }else if ([object isEqual:self.uploadProgress]) { //更新上傳進度Bloc if (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開始、重啓、掛起狀態的通知的接收。 [self addNotificationObserverForTask:task]; [self.lock unlock]; } /** 給Task添加任務開始、重啓、掛起的通知 @param task 任務 */ - (void)addNotificationObserverForTask:(NSURLSessionTask *)task { [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(taskDidResume:) name:AFNSURLSessionTaskDidResumeNotification object:task]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@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)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite { //獲取Task對應的`AFURLSessionManagerTaskDelegate`對象。從而能夠調用對應的代理方法 AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask: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)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{ //AFURLSessionManagerTaskDelegate代理方法實現對下載進度的記錄 self.downloadProgress.totalUnitCount = totalBytesExpectedToWrite; self.downloadProgress.completedUnitCount = totalBytesWritten; }
6 Task完成之後,會調用AFURLSessionManagerTaskDelegate
對象的方法對返回的數據封裝。
//AFURLSessionManagerTaskDelegate裏面的這個代理方法實現對數據的具體處理。 - (void)URLSession:(__unused NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { //獲取Task對應的manager對象 __strong AFURLSessionManager *manager = self.manager; //要封裝的responseObject對象。 __block id responseObject = nil; __block NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer; //返回的數據。 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; } //若是是downloadTask,則封裝downloadFileURL if (self.downloadFileURL) { userInfo[AFNetworkingTaskDidCompleteAssetPathKey] = self.downloadFileURL; } else if (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。則調用這個Block if (self.completionHandler) { self.completionHandler(task.response, responseObject, error); } //發送一個指定Task結束的通知 dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo]; }); }); } else {//正確數據封裝 //在一個並行的dispat_queuq_t對象裏面異步處理。 dispatch_async(url_session_manager_processing_queue(), ^{ NSError *serializationError = nil; //封裝responseBojct 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(), ^{ //若是Task有完成Block。則調用這個Block if (self.completionHandler) { self.completionHandler(task.response, responseObject, serializationError); } //發送通知 dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo]; }); }); }); } }
7 移除Task對應的通知和對應的AFURLSessionManagerTaskDelegate
代理對象。
- (void)removeDelegateForTask:(NSURLSessionTask *)task { NSParameterAssert(task); [self.lock lock]; //移除Task對應的通知 [self removeNotificationObserverForTask:task]; //移除Task對應的`AFURLSessionManagerTaskDelegate`代理對象。 [self.mutableTaskDelegatesKeyedByTaskIdentifier removeObjectForKey:@(task.taskIdentifier)]; [self.lock unlock]; } //移除通知監聽 - (void)removeNotificationObserverForTask:(NSURLSessionTask *)task { [[NSNotificationCenter defaultCenter] removeObserver:self name:AFNSURLSessionTaskDidSuspendNotification object:task]; [[NSNotificationCenter defaultCenter] removeObserver:self name:AFNSURLSessionTaskDidResumeNotification object:task]; } //`AFURLSessionManagerTaskDelegate`對象回收。 - (void)dealloc { [self.downloadProgress removeObserver:self forKeyPath:NSStringFromSelector(@selector(fractionCompleted))]; [self.uploadProgress removeObserver:self forKeyPath:NSStringFromSelector(@selector(fractionCompleted))]; }
經過上面的過程,咱們發現核心流程都是圍繞了NSRULSessionTask
對象以及與之綁定的AFURLSessionManagerTaskDelegate
對象執行的。咱們經過在NSRULSessionTask
對象的代理方法裏面手動調用AFURLSessionManagerTaskDelegate
對應的代理方法來實現對數據的處理和簡化代碼的做用,這個設計思路的確吊吊的。還有一些方法沒有涉及到,不過大同小異,基本過程就是這樣,就不一一解釋了。
AFURLSeeesionManager
實現了NSSecureCoding
協議。讓manager能夠歸檔解檔。
/** 在iOS8以及以上環境下,supportsSecureCoding必須重寫而且返回true。 @return bool */ + (BOOL)supportsSecureCoding { return YES; } //解檔 - (instancetype)initWithCoder:(NSCoder *)decoder { NSURLSessionConfiguration *configuration = [decoder decodeObjectOfClass:[NSURLSessionConfiguration class] forKey:@"sessionConfiguration"]; self = [self initWithSessionConfiguration:configuration]; if (!self) { return nil; } return self; } /** 咱們發現對象歸檔的時候,只歸檔了`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 [[[self class] 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, id responseObject, NSError *error))completionHandler { __block NSURLSessionUploadTask *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 (NSUInteger attempts = 0; !uploadTask && attempts < AFMaximumNumberOfAttemptsToRecreateBackgroundSessionUploadTask; attempts++) { uploadTask = [self.session uploadTaskWithRequest:request fromFile:fileURL]; } } //爲Task添加`AFURLSessionManagerTaskDelegate`代理方法 [self addDelegateForUploadTask:uploadTask progress:uploadProgressBlock completionHandler:completionHandler]; return uploadTask; }
經過使用dispatch_semaphore_t
來控制對異步處理返回結果的控制。很是有借鑑意義。
#pragma mark - 獲取當前session對應的task列表。經過dispatch_semaphore_t來控制訪問過程。 - (NSArray *)tasksForKeyPath:(NSString *)keyPath { __block NSArray *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; } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) { tasks = uploadTasks; } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) { tasks = downloadTasks; } else if ([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尚未正常獲取的狀況。 return tasks; }
在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 */ static inline void af_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 */ static inline BOOL af_addMethod(Class theClass, SEL selector, Method method) { return class_addMethod(theClass, selector, method_getImplementation(method), method_getTypeEncoding(method)); } @implementation _AFURLSessionTaskSwizzling + (void)load { if (NSClassFromString(@"NSURLSessionTask")) { NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration]; NSURLSession * session = [NSURLSession sessionWithConfiguration: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([self class], @selector(af_resume))); //獲取dataTask的具體類 Class currentClass = [localDataTask class]; //若是父類有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) { //添加方法、而後轉換方法的實現 [self swizzleResumeAndSuspendMethodForClass: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"); return NSURLSessionTaskStateCanceling; } /** 在iOS7下面,`NSURLSessionDataTask`調用resume方法其實就是執行`af_resume`的具體實現。 */ - (void)af_resume { NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state"); NSURLSessionTaskState state = [self state]; //這裏其實就是調用dataTask的resume實現 [self af_resume]; if (state != NSURLSessionTaskStateRunning) { //這裏的self其實就是`NSRULSessionDataTask`對象 [[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidResumeNotification object:self]; } } /** 在iOS7下面,`NSURLSessionDataTask`調用suspend方法其實就是執行`af_suspend`的具體實現。 */ - (void)af_suspend { NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state"); NSURLSessionTaskState state = [self state]; //這裏其實就是調用dataTask的suspend具體實現 [self af_suspend]; if (state != NSURLSessionTaskStateSuspended) { //這裏的self其實就是`NSRULSessionDataTask`對象 [[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidSuspendNotification object:self]; } } @end
AFURLSessionManager
經過對task設置一個AFURLSessionManagerTaskDelegate
代理來處理繁雜的請求進度管理。從而下降了代碼的負責度。是代理模式的一個很好的實踐。
AFURLSessionManager
經過私有類_AFURLSessionTaskSwizzling
來修改iOS7和iOS8系統上面不一樣。是對於方法swizzle的一個成功和完整的實踐。
AFURLSessionManager
經過添加各類Block,讓咱們對請求過程有全方位的控制和處理。同時提供簡潔的api,把負責的處理所有封裝好。
源碼地址iOSSourceCodeStudy,博客地址博客地址。