AFNetworking是iOS/OS開發中經常使用的一個第三方網絡庫,能夠說它是目前最流行的網絡庫,但其代碼結構其實並不複雜,也能夠說很是簡潔優美。在AFNetworking中,大量使用的線程安全的開發技巧,讀此源碼也是一次很好的多線程學習機會。本篇博客從主要結構和網絡請求的主流程進行分享,解析了AFNetworking的設計思路與工做原理,後面還有其中提供的UI擴展包的接口應用總結。javascript
每次讀優秀的代碼都是一次深入的學習,每一次模仿,都是創造的開始!java
平時咱們在使用AFNetworking框架時,大多隻使用其中的請求管理功能。其實,這個有名的框架中還提供了許多其餘的工具,除了能夠方便的進行網絡安全驗證,請求數據與回執數據的序列化,網絡狀態茶臺等基礎應用外,還提供了UIKit工具包,其中提供有經常使用組件的擴展,圖片下載器和緩存器等。json
對於AFNetworking框架的核心,無非AFURLSesstionManager類,這個類是基於系統的NSURLSesstion回話類進行的管理者包裝,下圖是AF框架一個總體的結構。設計模式
把握這個結構,咱們再來學習AF框架將變得十分容易上手,打開AFURLSesstionManager類,你會發現它有1200多行代碼,可是AFURLSesstionManager類真正的實現確實從500多行開始的,以前的代碼是內部的代理處理類,就像在MVVM模式中,咱們老是喜歡將控制器的邏輯放入View-Model中同樣,AFURLSesstionManager實例也會將通知,回調等操做交給這個代理實例處理。緩存
首先先來看AFURLSesstionManager類的初始化方法:安全
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration { self = [super init]; if (!self) { return nil; } if (!configuration) { //使用默認 configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; } self.sessionConfiguration = configuration; //代理方法執行作在的操做隊列 self.operationQueue = [[NSOperationQueue alloc] init]; self.operationQueue.maxConcurrentOperationCount = 1; //建立sesstion會話 self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue]; //默認將返回數據按照json進行解析 self.responseSerializer = [AFJSONResponseSerializer serializer]; //使用默認的安全驗證 self.securityPolicy = [AFSecurityPolicy defaultPolicy]; #if !TARGET_OS_WATCH //使用默認的網絡狀態監測 self.reachabilityManager = [AFNetworkReachabilityManager sharedManager]; #endif //每個請求任務都對應有一個delegate 存在這裏面 self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init]; //初始化鎖 self.lock = [[NSLock alloc] init]; self.lock.name = AFURLSessionManagerLockName; //下面的作法是爲了防止 被修改了sesstion的系統實現有初始任務 [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; }
用AFURLSesstionManager建立請求任務有三類,固然也對應NSURLSesstion中的數據請求任務,上傳任務和下載任務。咱們以數據請求任務爲例,核心方法以下:服務器
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler { __block NSURLSessionDataTask *dataTask = nil; url_session_manager_create_task_safely(^{ //建立任務 dataTask = [self.session dataTaskWithRequest:request]; }); //進行代理設置 [self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler]; return dataTask; }
上傳任務和下載任務的建立源碼和上面大同小異,只是建立出的任務類型不一樣,它們都要進行下一步代理的設置,還以數據請求任務的代理設置爲例,源碼以下:網絡
- (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler { //建立內部代理 AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] init]; delegate.manager = self; delegate.completionHandler = completionHandler; dataTask.taskDescription = self.taskDescriptionForSessionTasks; //設置代理 [self setDelegate:delegate forTask:dataTask]; delegate.uploadProgressBlock = uploadProgressBlock; delegate.downloadProgressBlock = downloadProgressBlock; }
須要注意,原生的NSURLSesstionTask的代理其實依然是AFURLSesstionManager類自身,這裏Manager做爲中介來進行方法的傳遞(它也會本身處理一些回調,與開發者相關的回調纔會轉給內部代理處理)。session
上面的流程就是AFURLSesstionManager建立的任務的主流程了,須要注意,它只建立出任務並不會執行,須要開發者手動調用resume才能激活任務。下面的流程就是系統的NSURLSesstionTaskDelagate相關的回調了:多線程
//接收到URL重定向時回調 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest *))completionHandler { NSURLRequest *redirectRequest = request; //這裏會執行用戶自定義的重定向block if (self.taskWillPerformHTTPRedirection) { redirectRequest = self.taskWillPerformHTTPRedirection(session, task, response, request); } //繼續重定向後的請求 if (completionHandler) { completionHandler(redirectRequest); } } //接收到安全認證挑戰的回調 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler { NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling; __block NSURLCredential *credential = nil; //若是開發者有提供block 交開發者處理 不然用默認的安全驗證方案處理 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); } } //已經發送數據後調用 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend { int64_t totalUnitCount = totalBytesExpectedToSend; if(totalUnitCount == NSURLSessionTransferSizeUnknown) { NSString *contentLength = [task.originalRequest valueForHTTPHeaderField:@"Content-Length"]; if(contentLength) { totalUnitCount = (int64_t) [contentLength longLongValue]; } } if (self.taskDidSendBodyData) { self.taskDidSendBodyData(session, task, bytesSent, totalBytesSent, totalUnitCount); } } //請求任務完成時回調 - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { //拿到內部代理 交給它處理 AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task]; // delegate may be nil when completing a task in the background if (delegate) { [delegate URLSession:session task:task didCompleteWithError:error]; [self removeDelegateForTask:task]; } if (self.taskDidComplete) { self.taskDidComplete(session, task, error); } } //接收到返回數據時回調 - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler { NSURLSessionResponseDisposition disposition = NSURLSessionResponseAllow; if (self.dataTaskDidReceiveResponse) { disposition = self.dataTaskDidReceiveResponse(session, dataTask, response); } if (completionHandler) { completionHandler(disposition); } } //此請求將要變動成下載任務時回調 - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask { //從新設置內部代理 AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask]; if (delegate) { [self removeDelegateForTask:dataTask]; [self setDelegate:delegate forTask:downloadTask]; } if (self.dataTaskDidBecomeDownloadTask) { self.dataTaskDidBecomeDownloadTask(session, dataTask, downloadTask); } } //開始獲取數據時調用 - (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); } } //將要緩存回執數據時調用 - (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); } } //下載任務結束後回調 - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location { AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask]; if (self.downloadTaskDidFinishDownloading) { NSURL *fileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location); if (fileURL) { delegate.downloadFileURL = fileURL; NSError *error = nil; //數據位置移動 [[NSFileManager defaultManager] moveItemAtURL:location toURL:fileURL error:&error]; if (error) { [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:error.userInfo]; } return; } } //內部代理進行後續處理 if (delegate) { [delegate URLSession:session downloadTask:downloadTask didFinishDownloadingToURL:location]; } }
到此,AFURLSesstionManager類的任務基本完成,頭文件中的接口更多提供了上述回調的設置還有些通知的發送。下面咱們要來看下內部代理AFURLSesstionManagerTaskDelegate類的做用,須要注意AFURLSesstionManagerTaskDelegate是一個類,並非協議,其除了處理上面介紹的Manager轉發過來的回到外,還會進行請求進度的控制。其配置方法和一些監聽這裏再也不過多介紹,主要來看其對Manager轉發過來的回到的處理:
//接收到數據後 將數據進行拼接 - (void)URLSession:(__unused NSURLSession *)session dataTask:(__unused NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { [self.mutableData appendData:data]; } //請求完成後 - (void)URLSession:(__unused NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wgnu" __strong AFURLSessionManager *manager = self.manager; __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; } //下載信息配置 if (self.downloadFileURL) { userInfo[AFNetworkingTaskDidCompleteAssetPathKey] = self.downloadFileURL; } else if (data) { userInfo[AFNetworkingTaskDidCompleteResponseDataKey] = data; } //錯誤信息配置 if (error) { userInfo[AFNetworkingTaskDidCompleteErrorKey] = error; dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{ if (self.completionHandler) { self.completionHandler(task.response, responseObject, error); } dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo]; }); }); } else { dispatch_async(url_session_manager_processing_queue(), ^{ NSError *serializationError = nil; //用回執數據序列化類進行數據處理 responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError]; if (self.downloadFileURL) { responseObject = self.downloadFileURL; } if (responseObject) { userInfo[AFNetworkingTaskDidCompleteSerializedResponseKey] = responseObject; } if (serializationError) { userInfo[AFNetworkingTaskDidCompleteErrorKey] = serializationError; } dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{ if (self.completionHandler) { self.completionHandler(task.response, responseObject, serializationError); } dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo]; }); }); }); } #pragma clang diagnostic pop } //下載處理 - (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location { NSError *fileManagerError = nil; self.downloadFileURL = nil; if (self.downloadTaskDidFinishDownloading) { self.downloadFileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location); if (self.downloadFileURL) { [[NSFileManager defaultManager] moveItemAtURL:location toURL:self.downloadFileURL error:&fileManagerError]; if (fileManagerError) { [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:fileManagerError.userInfo]; } } } }
瞭解了AFURLSesstionManager,學習AFHTTPSesstionManager將十分輕鬆,這個類只是前者面向應用的一層封裝。咱們能夠先從它的接口看起,這也是開發者最熟悉和經常使用的部分。
@interface AFHTTPSessionManager : AFURLSessionManager <NSSecureCoding, NSCopying //基礎URL @property (readonly, nonatomic, strong, nullable) NSURL *baseURL; //請求序列化對象 @property (nonatomic, strong) AFHTTPRequestSerializer <AFURLRequestSerialization> * requestSerializer; //回執序列化對象 @property (nonatomic, strong) AFHTTPResponseSerializer <AFURLResponseSerialization> * responseSerializer; //建立一個默認的Manager實例 注意不是單例 + (instancetype)manager; //初始化方法 - (instancetype)initWithBaseURL:(nullable NSURL *)url; - (instancetype)initWithBaseURL:(nullable NSURL *)url sessionConfiguration:(nullable NSURLSessionConfiguration *)configuration NS_DESIGNATED_INITIALIZER; //進行get請求 - (nullable NSURLSessionDataTask *)GET:(NSString *)URLString parameters:(nullable id)parameters success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE; - (nullable NSURLSessionDataTask *)GET:(NSString *)URLString parameters:(nullable id)parameters progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgress success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure; //進行HEAD請求 - (nullable NSURLSessionDataTask *)HEAD:(NSString *)URLString parameters:(nullable id)parameters success:(nullable void (^)(NSURLSessionDataTask *task))success failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure; //進行POST請求 - (nullable NSURLSessionDataTask *)POST:(NSString *)URLString parameters:(nullable id)parameters success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE; - (nullable NSURLSessionDataTask *)POST:(NSString *)URLString parameters:(nullable id)parameters progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure; - (nullable NSURLSessionDataTask *)POST:(NSString *)URLString parameters:(nullable id)parameters constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure DEPRECATED_ATTRIBUTE; - (nullable NSURLSessionDataTask *)POST:(NSString *)URLString parameters:(nullable id)parameters constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure; //進行PUT請求 - (nullable NSURLSessionDataTask *)PUT:(NSString *)URLString parameters:(nullable id)parameters success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure; //進行PATCH請求 - (nullable NSURLSessionDataTask *)PATCH:(NSString *)URLString parameters:(nullable id)parameters success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure; //進行DELETE請求 - (nullable NSURLSessionDataTask *)DELETE:(NSString *)URLString parameters:(nullable id)parameters success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
上面全部的請求,一旦建立就會自動resume,開發則不須要額外開啓。
AFURLRequestSerialization是一個協議,它的做用其實就是來將請求進行配置,其中只定義了一個接口:
@protocol AFURLRequestSerialization <NSObject, NSSecureCoding, NSCopying> //將請求和參數進行配置後返回 - (nullable NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request withParameters:(nullable id)parameters error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW; @end
實現這個接口的類主要是AFHTTPRequestSerizlizaer類,其還有兩個子類,分別爲AFJSONRequestSerizlizaer和AFPropertyListRequestSerializer類。
在使用AFNetworking進行網絡請求時,若是你有過抓包,你必定會發現,在發送的普通HTTP請求的HEAD中默認包含了許多信息,其實這些都是AFHTTPRequestSerizlizaer類作的,他默認會向請求頭中添加Accept-Language,User-Agent以及其餘可配置的頭部信息(緩存,Cookie,超時時間,用戶名密碼等)。在進行請求參數配置的時候,AFHTTPRequestSerizlizaer會根據請求方法來選擇配置到url後面或者添加到請求body中(HEAD,DELETE,GET會追加URL,其餘添加body)。AFJSONRequestSerizlizaer的做用與AFHTTPRequestSerizlizaer一致,不一樣的是會將請求頭中的Content-Type設置爲application/json而且將參數格式化成JSON數據放置在請求體中,AFPropertyListRequestSerializer類則是將Content-Type設置爲application/x-plist,而且將參數格式化成Plist數據放入請求體。
AFNetworking進行網絡請求有一個十分方便的地方在於它能夠直接將返回數據進行解析。其中AFHTTPResponseSerializer是最基礎的解析類,它只會根據返回頭信息來校驗返回數據的有效性,整理後直接將原數據返回。AFJSONResponseSerializer類用來解析返回數據爲JSON數據的回執,用這個類進行解析時,返回頭信息中的MIMEType必須爲application/json,text/json或text/javascript。AFXMLParserResponseSerializer類用來解析XML數據,其會返回一個XML解析器,使用它時,返回頭信息中的MIMEType必須爲application/xml或text/xml。AFXMLDocumentResponseSerializer類將返回數據解析成XML文檔。AFPropertyListResponseSerializer用來將返回數據解析成Plist數據。AFImageResponseSerializer類用來將返回數據解析成UIImage圖片,其支持的MIMEType類型爲image/tiff,image/jpeg,image/gif,image/png,image/ico,image/x-icon,image/bmp,image/x-bmp,image/x-xbitmap,image/x-win-bitmap。
除了上面列出的這些類外,還有一個AFCompoundResponseSerializer類,這個類示例中能夠配置多個ResponseSerializer實例,解析的時候會進行遍歷嘗試找到能夠解析的模式,這種模式也叫混合解析模式。
AFAutoPurgingImageCache類是AF框架中提供的圖片緩存器,須要注意,它並非一個持久化的緩存工具,只作臨時性的緩存。其中封裝了自動清緩存和按時間命中的邏輯。
每個AFAutoPurgingImageCache類實例中都有一個緩存池,緩存池有兩個臨界值,最大容量與指望容量。當實際使用的內存超過最大容量時,緩存池會自動清理到指望容量。在緩存池中,存放的其實是AFCacheImage對象,這個內部類對UIImage進行了包裝,以下:
@interface AFCachedImage : NSObject //關聯的UIImage @property (nonatomic, strong) UIImage *image; //id @property (nonatomic, strong) NSString *identifier; //圖片大小 @property (nonatomic, assign) UInt64 totalBytes; //最後使用時間 @property (nonatomic, strong) NSDate *lastAccessDate; //這個屬性沒有使用到 @property (nonatomic, assign) UInt64 currentMemoryUsage; @end
清緩存的邏輯十分簡單,這裏就不作代碼解析,流程是每次進行圖片緩存時,判斷是否超出緩存池最大容量,若是超出,將AFCacheImage對象按照lastAccessDate屬性進行排序後進行按順序刪除直到到達指望容量。當收到系統的內存警告時,也會喚起清除內存操做。
AFImageDownloader類專門用來下載圖片,其但單獨的線程的進行圖片的下載處理,而且內置了多任務掛起等待和圖片數據緩存的特性。AFImageDownloder類的核心有兩個線程:
@property (nonatomic, strong) dispatch_queue_t synchronizationQueue; @property (nonatomic, strong) dispatch_queue_t responseQueue;
其中synchronizationQueue是一個同步線程,用來建立與開始下載任務,也能夠理解這個串行線程爲這個下載器類的主要代碼執行所在的線程,responseQueue是一個並行線程,其用來當請求完成後處理數據。默認狀況下,下載器能夠同時下載4張圖片,若是圖片的請求大於4,多出的請求會被暫時掛起,等待其餘請求完成在進行激活。其核心流程以下圖所示:
如上圖所示,AFImageDownloader類中有大量的操做任務池和修改激活任務數的操做,爲了保證數據的安全,這也就是爲什麼AFImageDownloader的主題操做要在其自建的串行線程中執行。核心方法代碼解析以下:
- (nullable AFImageDownloadReceipt *)downloadImageForURLRequest:(NSURLRequest *)request withReceiptID:(nonnull NSUUID *)receiptID success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *responseObject))success failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure { __block NSURLSessionDataTask *task = nil; //在串行線程中執行 dispatch_sync(self.synchronizationQueue, ^{ //取當前圖片的url做爲標識 NSString *URLIdentifier = request.URL.absoluteString; //檢查url的可用性 if (URLIdentifier == nil) { if (failure) { NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorBadURL userInfo:nil]; dispatch_async(dispatch_get_main_queue(), ^{ failure(request, nil, error); }); } return; } //檢查任務池中是否已經有此任務 AFImageDownloaderMergedTask *existingMergedTask = self.mergedTasks[URLIdentifier]; if (existingMergedTask != nil) { //已經存在此任務 則追加回調 以後返回 這樣作的目的是 前後兩次對相同圖片的請求 能夠只進行一次請求,而且執行不一樣的兩次回調 AFImageDownloaderResponseHandler *handler = [[AFImageDownloaderResponseHandler alloc] initWithUUID:receiptID success:success failure:failure]; [existingMergedTask addResponseHandler:handler]; task = existingMergedTask.task; return; } //進行緩存的檢查 switch (request.cachePolicy) { case NSURLRequestUseProtocolCachePolicy: case NSURLRequestReturnCacheDataElseLoad: case NSURLRequestReturnCacheDataDontLoad: { UIImage *cachedImage = [self.imageCache imageforRequest:request withAdditionalIdentifier:nil]; if (cachedImage != nil) { if (success) { dispatch_async(dispatch_get_main_queue(), ^{ success(request, nil, cachedImage); }); } return; } break; } default: break; } //建立數據請求任務 NSUUID *mergedTaskIdentifier = [NSUUID UUID]; NSURLSessionDataTask *createdTask; __weak __typeof__(self) weakSelf = self; createdTask = [self.sessionManager dataTaskWithRequest:request completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) { dispatch_async(self.responseQueue, ^{ __strong __typeof__(weakSelf) strongSelf = weakSelf; AFImageDownloaderMergedTask *mergedTask = self.mergedTasks[URLIdentifier]; if ([mergedTask.identifier isEqual:mergedTaskIdentifier]) { mergedTask = [strongSelf safelyRemoveMergedTaskWithURLIdentifier:URLIdentifier]; if (error) { for (AFImageDownloaderResponseHandler *handler in mergedTask.responseHandlers) { if (handler.failureBlock) { dispatch_async(dispatch_get_main_queue(), ^{ handler.failureBlock(request, (NSHTTPURLResponse*)response, error); }); } } } else { [strongSelf.imageCache addImage:responseObject forRequest:request withAdditionalIdentifier:nil]; for (AFImageDownloaderResponseHandler *handler in mergedTask.responseHandlers) { if (handler.successBlock) { dispatch_async(dispatch_get_main_queue(), ^{ handler.successBlock(request, (NSHTTPURLResponse*)response, responseObject); }); } } } } [strongSelf safelyDecrementActiveTaskCount]; [strongSelf safelyStartNextTaskIfNecessary]; }); }]; //建立處理回調 AFImageDownloaderResponseHandler *handler = [[AFImageDownloaderResponseHandler alloc] initWithUUID:receiptID success:success failure:failure]; //建立圖片任務 追加回調 AFImageDownloaderMergedTask *mergedTask = [[AFImageDownloaderMergedTask alloc] initWithURLIdentifier:URLIdentifier identifier:mergedTaskIdentifier task:createdTask]; [mergedTask addResponseHandler:handler]; self.mergedTasks[URLIdentifier] = mergedTask; //進行激活或掛起數據請求任務 if ([self isActiveRequestCountBelowMaximumLimit]) { [self startMergedTask:mergedTask]; } else { [self enqueueMergedTask:mergedTask]; } task = mergedTask.task; }); if (task) { //將回執返回 用來取消任務 return [[AFImageDownloadReceipt alloc] initWithReceiptID:receiptID task:task]; } else { return nil; } }
UIImageView+AFNetworking類別與UIButton+AFNetworking類別是AF中提供了兩個加載網絡圖片的工具類別。這兩個類別都爲其實例對象關聯了一個圖片下載器,開發者能夠自定義這個下載器也可使用默認提供的,例如:
+ (AFImageDownloader *)sharedImageDownloader { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wgnu" return objc_getAssociatedObject(self, @selector(sharedImageDownloader)) ?: [AFImageDownloader defaultInstance]; #pragma clang diagnostic pop } + (void)setSharedImageDownloader:(AFImageDownloader *)imageDownloader { objc_setAssociatedObject(self, @selector(sharedImageDownloader), imageDownloader, OBJC_ASSOCIATION_RETAIN_NONATOMIC); }
雖然頭文件中提供了多個設置圖片的接口方法,但實際核心方法只有一個,其餘方法都是基於這個核心方法時候封裝的遍歷方法,以UIImageView類別爲例,UIButton類別也相似,如圖:
進行網絡圖片設置的過程以下圖:
核心方法代碼分析:
- (void)setImageWithURLRequest:(NSURLRequest *)urlRequest placeholderImage:(UIImage *)placeholderImage success:(void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *image))success failure:(void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure { //檢查請求是否合法 if ([urlRequest URL] == nil) { [self cancelImageDownloadTask]; self.image = placeholderImage; return; } //檢查此請求是否正在進行中 if ([self isActiveTaskURLEqualToURLRequest:urlRequest]){ return; } //取消當前UI組件的下載任務 [self cancelImageDownloadTask]; //拿到圖片下載器 AFImageDownloader *downloader = [[self class] sharedImageDownloader]; //拿到緩存器 id <AFImageRequestCache> imageCache = downloader.imageCache; //檢查緩存圖片是否存在 UIImage *cachedImage = [imageCache imageforRequest:urlRequest withAdditionalIdentifier:nil]; if (cachedImage) { if (success) { success(urlRequest, nil, cachedImage); } else { self.image = cachedImage; } [self clearActiveDownloadInformation]; } else { if (placeholderImage) { self.image = placeholderImage; } __weak __typeof(self)weakSelf = self; //取一個隨機的UUID做爲標誌 NSUUID *downloadID = [NSUUID UUID]; //任務控制對象 AFImageDownloadReceipt *receipt; receipt = [downloader downloadImageForURLRequest:urlRequest withReceiptID:downloadID success:^(NSURLRequest * _Nonnull request, NSHTTPURLResponse * _Nullable response, UIImage * _Nonnull responseObject) { __strong __typeof(weakSelf)strongSelf = weakSelf; //檢查當前的任務id和請求的任務id是否一致 if ([strongSelf.af_activeImageDownloadReceipt.receiptID isEqual:downloadID]) { if (success) { success(request, response, responseObject); } else if(responseObject) { strongSelf.image = responseObject; } //清空任務信息 [strongSelf clearActiveDownloadInformation]; } } failure:^(NSURLRequest * _Nonnull request, NSHTTPURLResponse * _Nullable response, NSError * _Nonnull error) { __strong __typeof(weakSelf)strongSelf = weakSelf; if ([strongSelf.af_activeImageDownloadReceipt.receiptID isEqual:downloadID]) { if (failure) { failure(request, response, error); } [strongSelf clearActiveDownloadInformation]; } }]; //存儲任務控制對象 self.af_activeImageDownloadReceipt = receipt; } }
AFNetworkActivityIndicatorManager類用來管理系統在狀態欄上的指示器顯示或隱藏。系統狀態欄上的指示器提供的接口很是簡單,只有一個:
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
上面方法的參數決定指示器是否顯示。AFNetworkActivityIndicatorManager從兩個方向來管理這個指示器,能夠開發者手動設置,同時他也會對全部AFNetworking發出的請求狀態進行監聽,來自動適應決定是否顯示指示器。
之前我在設計全局Loading時,一般直接爲他暴漏顯隱兩個接口,當處理多個並行請求的時候就很尷尬了,由於你沒法保證Loading在最後完成的請求結束後再隱藏。 AFNetworkActivityIndicatorManager採用了觸發器的設計模式(其實有些像引用計數),請求來對觸發器進行加或減的操做,觸發器決定是否觸發顯示指示器。後面的應用解析中會有具體的接口解釋。
在AFNetworking中提供了管理類AFNetworkReachabilityManager類,這個類專門用於網絡狀態的檢測。其代碼量不多,接口設計也十分簡單。AFNetworkReachabilityManager類解析以下:
//返回默認的單例對象 + (instancetype)sharedManager; //建立一個新的管理對象 + (instancetype)manager; + (instancetype)managerForDomain:(NSString *)domain; + (instancetype)managerForAddress:(const void *)address; - (instancetype)initWithReachability:(SCNetworkReachabilityRef)reachability; //獲取當前的網絡狀態 /* typedef NS_ENUM(NSInteger, AFNetworkReachabilityStatus) { AFNetworkReachabilityStatusUnknown = -1, //未知 AFNetworkReachabilityStatusNotReachable = 0, //未鏈接 AFNetworkReachabilityStatusReachableViaWWAN = 1, //移動網路 AFNetworkReachabilityStatusReachableViaWiFi = 2, //WIFI }; */ @property (readonly, nonatomic, assign) AFNetworkReachabilityStatus networkReachabilityStatus; //獲取當前網絡是否鏈接 @property (readonly, nonatomic, assign, getter = isReachable) BOOL reachable; //獲取當前是不是移動網絡 @property (readonly, nonatomic, assign, getter = isReachableViaWWAN) BOOL reachableViaWWAN; //獲取當前是不是WIFI網絡 @property (readonly, nonatomic, assign, getter = isReachableViaWiFi) BOOL reachableViaWiFi; //開啓網絡狀態監聽 當網絡狀態變化時會有回調 - (void)startMonitoring; //中止進行網絡狀態監聽 - (void)stopMonitoring; //設置網絡狀態發生變化後的回調 - (void)setReachabilityStatusChangeBlock:(nullable void (^)(AFNetworkReachabilityStatus status))block; //格式化的網絡狀態字符串 - (NSString *)localizedNetworkReachabilityStatusString; //若是開啓了網絡狀態監聽 則網絡狀態發生變化時也會 發這個通知 FOUNDATION_EXPORT NSString * const AFNetworkingReachabilityDidChangeNotification; //通知中userinfo字典中對應網絡狀態的鍵名 FOUNDATION_EXPORT NSString * const AFNetworkingReachabilityNotificationStatusItem;
這個類別是AFNetworking中UI部分所提供了一個工具,用來建立遠程圖片按鈕。
//設置共享的下載器對象 用來進行網絡圖片的下載 + (void)setSharedImageDownloader:(AFImageDownloader *)imageDownloader; //獲取共享的下載器對象 + (AFImageDownloader *)sharedImageDownloader; //下面這些方法用來設置按鈕圖片 - (void)setImageForState:(UIControlState)state withURL:(NSURL *)url; - (void)setImageForState:(UIControlState)state withURL:(NSURL *)url placeholderImage:(nullable UIImage *)placeholderImage; - (void)setImageForState:(UIControlState)state withURLRequest:(NSURLRequest *)urlRequest placeholderImage:(nullable UIImage *)placeholderImage success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *image))success failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure; //下面這些方法用來設置按鈕背景圖片 - (void)setBackgroundImageForState:(UIControlState)state withURL:(NSURL *)url; - (void)setBackgroundImageForState:(UIControlState)state withURL:(NSURL *)url placeholderImage:(nullable UIImage *)placeholderImage; - (void)setBackgroundImageForState:(UIControlState)state withURLRequest:(NSURLRequest *)urlRequest placeholderImage:(nullable UIImage *)placeholderImage success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *image))success failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure; //取消按鈕圖片下載任務 - (void)cancelImageDownloadTaskForState:(UIControlState)state; //取消按鈕背景圖片下載任務 - (void)cancelBackgroundImageDownloadTaskForState:(UIControlState)state;
這個類別是AFNetworking框架提供了UIImageView加載異步圖片的方案(更多時候可能會用SDWebImage)。
//設置共享的圖片下載器 + (void)setSharedImageDownloader:(AFImageDownloader *)imageDownloader; //獲取共享的圖片下載器 + (AFImageDownloader *)sharedImageDownloader; //設置圖片 - (void)setImageWithURL:(NSURL *)url; - (void)setImageWithURL:(NSURL *)url placeholderImage:(nullable UIImage *)placeholderImage; - (void)setImageWithURLRequest:(NSURLRequest *)urlRequest placeholderImage:(nullable UIImage *)placeholderImage success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *image))success failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure; //取消圖片下載任務 - (void)cancelImageDownloadTask;
AFNetworking的UI工具包中提供了AFNetworkActivityIndicatorManager類,這個管理類用來對iOS設備狀態欄上活動指示器的顯示隱藏進行管理。其提供的接口十分簡單,解析以下:
//設置是否有效 /* 若是設置爲YES,則能夠手動進行控制器的控制 若是設置爲NO,則控制器只會根據網絡通知來絕對是否顯示 */ @property (nonatomic, assign, getter = isEnabled) BOOL enabled; //指示器當前是否顯示 @property (readonly, nonatomic, assign, getter=isNetworkActivityIndicatorVisible) BOOL networkActivityIndicatorVisible; //指示器的延時顯示時間 當多久後若是網絡請求尚未完成 再進行顯示 @property (nonatomic, assign) NSTimeInterval activationDelay; //設置當請求結束多久後 活動指示器隱藏 默認爲0.17s @property (nonatomic, assign) NSTimeInterval completionDelay; //獲取單例對象 + (instancetype)sharedManager; //手動對觸發器加1 //當計數大於0 則顯示指示器 - (void)incrementActivityCount; //手動對觸發器減1 //當計數不大於0 則隱藏指示器 - (void)decrementActivityCount; //設置指示器狀態改變的回調 - (void)setNetworkingActivityActionWithBlock:(nullable void (^)(BOOL networkActivityIndicatorVisible))block;
這兩個類別能夠將一個請求任務綁定到活動指示器或原生刷新組件上,任務的完成與否來決定控件是否展現動畫。
//UIActivityIndicatorView+AFNetworking //將任務綁定到活動指示器上 - (void)setAnimatingWithStateOfTask:(nullable NSURLSessionTask *)task; //UIRefresh+AFNetworing //將任務綁定到刷新組件上 - (void)setRefreshingWithStateOfTask:(NSURLSessionTask *)task;
這個類別能夠將上傳和下載任務綁定到進度條組件上。
//將上傳任務綁定 - (void)setProgressWithUploadProgressOfTask:(NSURLSessionUploadTask *)task animated:(BOOL)animated; //將下載任務綁定 - (void)setProgressWithDownloadProgressOfTask:(NSURLSessionDownloadTask *)task animated:(BOOL)animated;
這個類別是對UIWebView的一種擴展(因爲WebKit,這個類別不多會用到了),其主要做用是將WebView的直接加載改成先下載本地數據,而後進行本地數據的加載,並能夠提供一個進度。
//這個方法是下面方法的封裝 - (void)loadRequest:(NSURLRequest *)request progress:(NSProgress * _Nullable __autoreleasing * _Nullable)progress success:(nullable NSString * (^)(NSHTTPURLResponse *response, NSString *HTML))success failure:(nullable void (^)(NSError *error))failure; //進行網頁數據請求,完成後會將網頁數據返回 而且自動的進行加載 - (void)loadRequest:(NSURLRequest *)request MIMEType:(nullable NSString *)MIMEType textEncodingName:(nullable NSString *)textEncodingName progress:(NSProgress * _Nullable __autoreleasing * _Nullable)progress success:(nullable NSData * (^)(NSHTTPURLResponse *response, NSData *data))success failure:(nullable void (^)(NSError *error))failure;
AFImageDownloader是AF框架中提供的圖片下載管理類。其內部封裝了一個AFImageDownloadReceipt回執類,這個類實例用來進行正在下載圖片任務的取消。AFImageDownloader解析以下:
//圖片緩存器 @property (nonatomic, strong, nullable) id <AFImageRequestCache> imageCache; //請求會話管理類 @property (nonatomic, strong) AFHTTPSessionManager *sessionManager; //設置下載器屬性 /* 下載器能夠設置同時下載圖片的最大數量 若是超出數量的請求會被暫時掛起 typedef NS_ENUM(NSInteger, AFImageDownloadPrioritization) { AFImageDownloadPrioritizationFIFO,//被掛起的請求遵照先掛起的先開始 AFImageDownloadPrioritizationLIFO//被掛起的請求遵照後掛起的先開始 }; */ @property (nonatomic, assign) AFImageDownloadPrioritization downloadPrioritizaton; //獲取單例對象 + (instancetype)defaultInstance; //獲取默認的url緩存器 + (NSURLCache *)defaultURLCache; //使用初始化方法新建對象 - (instancetype)initWithSessionManager:(AFHTTPSessionManager *)sessionManager downloadPrioritization:(AFImageDownloadPrioritization)downloadPrioritization maximumActiveDownloads:(NSInteger)maximumActiveDownloads imageCache:(nullable id <AFImageRequestCache>)imageCache; //進行圖片請求 隨機生成uuid - (nullable AFImageDownloadReceipt *)downloadImageForURLRequest:(NSURLRequest *)request success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *responseObject))success failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure; //和上面方法同樣,使用自定義的uuid - (nullable AFImageDownloadReceipt *)downloadImageForURLRequest:(NSURLRequest *)request withReceiptID:(NSUUID *)receiptID success:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, UIImage *responseObject))success failure:(nullable void (^)(NSURLRequest *request, NSHTTPURLResponse * _Nullable response, NSError *error))failure;
AF框架中UIKit工具包裏的ADAutoPurgingImageCache類用來進行圖片緩存,其還封裝了自動清緩存的邏輯,緩存的命中則是徹底按照使用時間前後爲標準。類接口解析以下:
//圖片緩存協議 @protocol AFImageCache <NSObject> //根據id來緩存圖片 - (void)addImage:(UIImage *)image withIdentifier:(NSString *)identifier; //根據id來刪除圖片 - (BOOL)removeImageWithIdentifier:(NSString *)identifier; //刪除全部圖片緩存 - (BOOL)removeAllImages; //根據id來取緩存 - (nullable UIImage *)imageWithIdentifier:(NSString *)identifier; @end //網絡圖片緩存協議 @protocol AFImageRequestCache <AFImageCache> //緩存某個url的圖片 用url+id的方式 - (void)addImage:(UIImage *)image forRequest:(NSURLRequest *)request withAdditionalIdentifier:(nullable NSString *)identifier; //移除某個緩存圖片 - (BOOL)removeImageforRequest:(NSURLRequest *)request withAdditionalIdentifier:(nullable NSString *)identifier; //獲取緩存 - (nullable UIImage *)imageforRequest:(NSURLRequest *)request withAdditionalIdentifier:(nullable NSString *)identifier; @end //緩存器實例類 @interface AFAutoPurgingImageCache : NSObject <AFImageRequestCache> //緩存空間大小 默認爲100M @property (nonatomic, assign) UInt64 memoryCapacity; //當緩存超過最大限制時進行清緩存 清緩存後的緩存底線大小設置 默認60M @property (nonatomic, assign) UInt64 preferredMemoryUsageAfterPurge; //已用空間大小 @property (nonatomic, assign, readonly) UInt64 memoryUsage; //初始化方法 - (instancetype)init; - (instancetype)initWithMemoryCapacity:(UInt64)memoryCapacity preferredMemoryCapacity:(UInt64)preferredMemoryCapacity; @end
每次讀優秀的代碼都是一次深入的學習,每一次模仿,都是創造的開始!
——QQ 316045346 歡迎交流