<簡書 — 劉小壯>git
AFNetworking
是iOS
最經常使用的網絡框架,雖然系統也有NSURLSession
,可是咱們通常不會直接用它。AFNetworking
通過了三個大版本,如今用的大多數都是3.x的版本。github
AFNetworking
經歷了下面三個階段的發展:json
NSURLConnection
的封裝。NSURLConnection
和NSURLSession
,是轉向NSURLSession
的過渡版。NSURLSession
的封裝。AFNetworking3.X
的構成很簡單,主要就四部分,除此以外還有一些基於UIKit
的Category
,但這些並非標配。數組
Manager
,主要實現都在AFURLSessionManager
中。HTTPS
相關的。在AFN3.0
中,網絡請求的manager
主要有AFHTTPSessionManager
和AFURLSessionManager
構成,兩者爲父子關係。這兩個類職責劃分很清晰,父類負責處理一些基礎的網絡請求代碼,而且接受NSURLRequest
對象,而子類則負責處理和http
協議有關的邏輯。緩存
AFN
的這套設計很便於擴展,若是之後想增長FTP
協議的處理,則基於AFURLSessionManager
建立子類便可。子類中只須要進行不多的代碼處理,建立一個NSURLRequest
對象後調用父類代碼,由父類去完成具體的請求操做。安全
AFHTTPSessionManager
類的初始化方法中並無太多實現代碼,其內部調用的都是父類AFURLSessionManager
的initWithSessionConfiguration
方法,下面是此方法內部的一些關鍵代碼。服務器
在初始化方法中包含一個參數sessionConfiguration
,若是沒有傳入的話默認是使用系統的defaultConfiguration
,咱們建立是通常都不會自定義configuration
,因此大多數都是系統的。網絡
if (!configuration) { configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; }
隨後是NSURLSession
的初始化代碼,關於NSOperationQueue
後面詳細進行講解。NSURLSession
的初始化方式有兩種,一種是使用系統的共享session
,另外一種是本身建立session
。AFN
選擇的是建立本身的session
,而且每一個請求都會建立一個獨立的session
。session
能夠經過NSURLSession
進行鏈接複用,這樣能夠避免不少握手和揮手的過程,提升網絡請求速度,蘋果容許iOS
設備上一個域名能夠有四個鏈接同時存在。可是因爲AFN
的實現是每一個請求都建立一個session
,因此就不能進行鏈接複用。多線程
因此能夠經過在外面對AFN
進行二次封裝,將AFHTTPSessionManager
複用爲單例對象,經過複用sessionManager
的方式,來進行鏈接的複用。可是這種方案對於不一樣的requestSerializer
、responseSerializer
等狀況,仍是要作特殊兼容,因此最好創建一個sessionManager
池,對於同類型的sessionManager
直接拿出來複用,不然就建立新的。
// 共享session鏈接池 [NSURLSession sharedSession]; // 建立新session,則不能使用共享session鏈接池 [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
因爲當前AFURLSessionManager
對象的全部sessionTask
請求任務,都是共享同一個回調代理的,因此AFN
爲了區分每一個sessionTask
,經過下面的可變字典,將全部taskDelegate
和task.taskIdentifier
的進行了一一對應,以便於很容易的對每一個請求task
進行操做。
self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];
在初始化方法中,能夠發現AFN
在建立session
後,調用了getTasksWithCompletionHandler
方法來獲取當前全部的task
。可是如今剛建立session
,理論上來講是不該該有task
的。但從AFN
的issues
中找到了答案issues 3499。
這是由於,在completionHandler
回調中,爲了防止進入前臺時,經過session id
恢復的task
致使一些崩潰問題,因此這裏將以前的task
進行遍歷,並將回調都置nil
。
[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]; } }];
在AFURLSessionManager
中進行task
的建立,task
的類型總共分爲三種,dataTask
、uploadTask
、downloadTask
,AFN
並無對streamTask
進行處理。
AFHTTPSessionManager
在建立GET
、POST
等請求時,本質上都是調用了下面的方法或其相似的方法,方法內部會建立一個task
對象,並調用addDelegateForDataTask
將後面的處理交給AFURLSessionManagerTaskDelegate
來完成。隨後會將task
返回給調用方,調用方獲取到task
對象後,也就是子類AFHTTPSessionManager
,會調用resume
方法開始請求。
- (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; }
除了普通請求外,upload
、download
都有相似的處理。
在addDelegateForDataTask
方法中,會調用sessionManager
的setDelegate:forTask:
方法,此方法內部將task
和taskDelegate
進行了註冊。因爲AFN
能夠經過通知讓外界監聽請求狀態,因此在此方法中還監聽了task
的resume
和suspend
事件,並在實現代碼中將事件廣播出去。
- (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler { AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] initWithTask:dataTask]; delegate.manager = self; delegate.completionHandler = completionHandler; dataTask.taskDescription = self.taskDescriptionForSessionTasks; [self setDelegate:delegate forTask:dataTask]; delegate.uploadProgressBlock = uploadProgressBlock; delegate.downloadProgressBlock = downloadProgressBlock; }
若是從AFHTTPSessionManager
的建立任務開始,按代碼邏輯跟到這裏,發現其實AFN3.0
的請求代碼真的很簡單,主要都集中在建立NSMutableURLRequest
那裏,其餘都依賴於NSURLSession
,由於確實NSURLSession
的API
封裝程度比較好,也很好使用。
AFN3.0
的做用就是對NSURLSession
的封裝性比較好,你不用去寫太多重複性的代碼,而且能夠很容易的經過block
獲得回調結果。
NSURLSession
的回調方法比較多,這裏只針對一些關鍵代碼進行講解,以及梳理總體回調邏輯,不一一列舉每一個回調方法的做用,詳細源碼各位能夠直接下載AFN
代碼查看。
在AFURLSessionManager
中,有一個AFURLSessionManagerTaskDelegate
類比較重要,這個類和sessionTask
是一一對應的,負責處理sessionTask
請求的不少邏輯,NSURLSessionDelegate
的回調基本都轉發給taskDelegate
去處理了。在NSURLSession
回調中處理了HTTPS
證書驗證、下載進度之類的,沒有太複雜的處理。
taskDelegate
的設計很不錯,能夠將代理回調任務處理對象化,也能夠給AFURLSessionManager
類瘦身。比較理想的是直接將代理設置爲taskDelegate
,可是因爲會涉及一些AFURLSessionManager
自身的處理邏輯,因此才設計爲消息傳遞的方式。
taskDelegate
的功能很簡單,主要是NSData
數據的處理,NSProgress
上傳下載進度的處理,以及通知參數的處理。在進行AFN
的下載處理時,NSData
的數據拼接、事件回調,及文件處理,都是由taskDelegate
來完成的。
下面是downloadTask
任務完成時的處理代碼,其餘回調代碼就不一一列舉了。
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location { self.downloadFileURL = nil; if (self.downloadTaskDidFinishDownloading) { self.downloadFileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location); if (self.downloadFileURL) { NSError *fileManagerError = nil; if (![[NSFileManager defaultManager] moveItemAtURL:location toURL:self.downloadFileURL error:&fileManagerError]) { [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:fileManagerError.userInfo]; } } } }
taskDelegate
中有一個很好的設計,taskDelegate
並不直接在NSURLSession
的代理方法中作進度拼接和回調。而是對於上傳和下載任務分別對應不一樣的NSProgress
,並經過KVO
來監聽fractionCompleted
屬性,而且實現cancel
、suspend
等狀態回調。任務的狀態和進度處理交給NSProgress
,在回調方法中直接拼接NSProgress
的進度,從而回調KVO
方法。
NSProgress
內部的cancel
、pause
、resume
方法,正好能夠對應到sessionTask
的方法調用。可是從代碼角度來看,AFN
好像並無進行相關的調用,但這個設計思路很好。
_uploadProgress = [[NSProgress alloc] initWithParent:nil userInfo:nil]; _downloadProgress = [[NSProgress alloc] initWithParent:nil userInfo:nil]; __weak __typeof__(task) weakTask = task; for (NSProgress *progress in @[ _uploadProgress, _downloadProgress ]) { progress.totalUnitCount = NSURLSessionTransferSizeUnknown; progress.cancellable = YES; progress.cancellationHandler = ^{ [weakTask cancel]; }; progress.pausable = YES; progress.pausingHandler = ^{ [weakTask suspend]; }; #if AF_CAN_USE_AT_AVAILABLE if (@available(iOS 9, macOS 10.11, *)) #else if ([progress respondsToSelector:@selector(setResumingHandler:)]) #endif { progress.resumingHandler = ^{ [weakTask resume]; }; } [progress addObserver:self forKeyPath:NSStringFromSelector(@selector(fractionCompleted)) options:NSKeyValueObservingOptionNew context:NULL]; }
看過源碼的話,能夠發現AFURLSessionManager
中還有一個_AFURLSessionTaskSwizzling
類,這裏咱們簡稱taskSwizzling
類。我認爲此類的設計實在是冗餘,此類的主要功能就是在+load
方法中進行一個swizzling
,將dataTask
的resume
和suspend
方法進行替換,而且在替換後的方法中發出對應的通知,並無太多實際的功能。
只不過taskSwizzling
類中仍是有一些不錯的代碼設計值得借鑑的,因爲sessionTask
存在一系列繼承鏈,因此直接對其進行swizzling
對其餘子類並不生效,由於每一個子類都有本身的實現,而寫一大堆swizzling
又沒有什麼技術含量。
在iOS7
和iOS8
上,sessionTask
的繼承關係並不同,最好進行一個統一的處理。AFN
採起的方式是建立一個dataTask
對象,並對這個對象進行swizzling
,而且遍歷其繼承鏈一直進行swizzling
,這樣保證集成繼承鏈的正確性。
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration]; NSURLSession * session = [NSURLSession sessionWithConfiguration:configuration]; #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wnonnull" NSURLSessionDataTask *localDataTask = [session dataTaskWithURL:nil]; #pragma clang diagnostic pop IMP originalAFResumeIMP = method_getImplementation(class_getInstanceMethod([self class], @selector(af_resume))); Class currentClass = [localDataTask class]; while (class_getInstanceMethod(currentClass, @selector(resume))) { Class superClass = [currentClass superclass]; 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]; } + (void)swizzleResumeAndSuspendMethodForClass:(Class)theClass { Method afResumeMethod = class_getInstanceMethod(self, @selector(af_resume)); Method afSuspendMethod = class_getInstanceMethod(self, @selector(af_suspend)); if (af_addMethod(theClass, @selector(af_resume), afResumeMethod)) { af_swizzleSelector(theClass, @selector(resume), @selector(af_resume)); } if (af_addMethod(theClass, @selector(af_suspend), afSuspendMethod)) { af_swizzleSelector(theClass, @selector(suspend), @selector(af_suspend)); } }
AFN
爲了不發生編譯器警告,採起了預編譯指令對代碼進行修飾,預編譯指令基本由三部分組成,push
、pop
、ignored
類型。Github
上有人維護了一份clang warning清單,若是想進行對應的預編譯處理能夠上去找找有沒有合適的。
#pragma clang diagnostic push #pragma clang diagnostic ignored "-Wgnu" #pragma clang diagnostic pop
NSURLSession
在iOS8
如下會併發建立多個task
,但併發設置task identifier
的時候會存在identifier
重複的問題。爲了解決這個問題,在iOS8
如下,系統將全部sessionTask
的建立都放在一個同步的串行隊列中進行,保證建立及賦值操做是串行進行的。
url_session_manager_create_task_safely(^{ dataTask = [self.session dataTaskWithRequest:request]; }); url_session_manager_create_task_safely(^{ uploadTask = [self.session uploadTaskWithRequest:request fromData:bodyData]; }); // 若是Foundation版本小於iOS8,則把block任務放在一個同步隊列中執行。這個問題是因爲在iOS8如下併發建立任務,可能會有多個相同的identifier static void url_session_manager_create_task_safely(dispatch_block_t block) { if (NSFoundationVersionNumber < NSFoundationVersionNumber_With_Fixed_5871104061079552_bug) { dispatch_sync(url_session_manager_creation_queue(), block); } else { block(); } }
一個比較有意思的是,AFN
爲了讓開發者明白爲何要加這個判斷,對iOS8
系統的判判定義成了一個宏,而且用Apple Support
的id
做爲宏定義命名,很見名知意。
#define NSFoundationVersionNumber_With_Fixed_5871104061079552_bug NSFoundationVersionNumber_iOS_8_0
AFN
在回調didCompleteWithError
方法,並處理返回數據時,會切換到其餘線程和group
去處理,處理完成後再切換到主線程並通知調用方。
AFN
提供了兩個屬性,用來設置請求結束後進行回調的dispatch queue
和dispatch group
,若是不設置的話,AFN
會有默認的實現來處理請求結束的操做。下面是group
和queue
的實現,AFN
對於返回數據的處理,採用的是併發處理。
static dispatch_queue_t url_session_manager_processing_queue() { static dispatch_queue_t af_url_session_manager_processing_queue; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ af_url_session_manager_processing_queue = dispatch_queue_create("com.alamofire.networking.session.manager.processing", DISPATCH_QUEUE_CONCURRENT); }); return af_url_session_manager_processing_queue; } static dispatch_group_t url_session_manager_completion_group() { static dispatch_group_t af_url_session_manager_completion_group; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ af_url_session_manager_completion_group = dispatch_group_create(); }); return af_url_session_manager_completion_group; }
AFN
在建立AFURLSessionManager
的operationQueue
時,將其最大併發數設置爲1。這是由於在建立NSURLSSession
時,蘋果要求網絡請求回來的數據順序執行,爲了保證代理方法的執行順序,因此須要串行的調用NSURLSSession
的代理方法。
AFHTTPSessionManager
本質上是對父類AFURLSessionManager
的封裝,主要實現都在父類中,本身內部代碼實現很簡單。在建立AFHTTPSessionManager
時會傳入一個baseURL
,以及指定requestSerializer
、responseSerializer
對象。
從代碼實現來看,AFN
的請求並非單例形式的,每一個請求都會建立一個新的請求對象。平時調用的GET
、POST
等網絡請求方法,都定義在AFHTTPSessionManager
中。AFHTTPSessionManager
內部則調用父類方法,發起響應的請求並獲取到task
對象,調用task
的resume
後返回給調用方。
- (NSURLSessionDataTask *)GET:(NSString *)URLString parameters:(id)parameters progress:(void (^)(NSProgress * _Nonnull))downloadProgress success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure { NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET" URLString:URLString parameters:parameters uploadProgress:nil downloadProgress:downloadProgress success:success failure:failure]; [dataTask resume]; return dataTask; }
AFURLRequestSerialization
負責建立NSMutableURLRequest
請求對象,並對request
進行請求的參數拼接、設置緩存策略、設置請求頭等關於請求相關的配置。
AFURLRequestSerialization
並非一個類,而是一個文件,其中包含三個requestSerializer
請求對象,分別對應着不一樣的請求序列化器。
JSON
請求。xml
格式請求。這三個類區別就在於Content-Type
不一樣,其餘基本都是同樣的。AFN
默認是HTTP
的。
在文件中定義了同名的AFURLRequestSerialization
協議,不一樣的requestSerializer
會對協議方法有不一樣的實現,下面是AFHTTPRequestSerializer
的實現代碼。其核心代碼實現也比較直觀,就是在建立requestSerializer
的時候,設置請求頭的公共參數,以及將請求參數經過NSJSONSerialization
轉換爲NSData
,並將其賦值給request
對象的httpBody
,下面是精簡後的核心代碼。
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request withParameters:(id)parameters error:(NSError *__autoreleasing *)error { NSMutableURLRequest *mutableRequest = [request mutableCopy]; [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) { if (![request valueForHTTPHeaderField:field]) { [mutableRequest setValue:value forHTTPHeaderField:field]; } }]; if (parameters) { if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) { [mutableRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; } NSData *jsonData = [NSJSONSerialization dataWithJSONObject:parameters options:self.writingOptions error:error]; [mutableRequest setHTTPBody:jsonData]; } return mutableRequest; }
若是想給網絡請求設置請求參數的話,須要經過requestSerializer
對外暴露的API
添加參數,AFN
的requestManager
並不直接對外提供設置請求頭的代碼。經過requestSerializer
能夠對請求頭進行添加和刪除、以及清空的操做。
從建立AFURLRequestSerialization
對象到最後返回NSURLRequest
對象,中間的過程並不複雜,主要是設置請求頭和拼接參數,邏輯很清晰。
AFURLRequestSerialization
有一個很重要的功能就是參數處理,AFQueryStringPair
就是負責處理這些參數的。pair
類中定義了兩個屬性,分別對應請求參數的key
、value
。除此以外,還定義了一些很是實用的C語言函數。
@interface AFQueryStringPair : NSObject @property (readwrite, nonatomic, strong) id field; @property (readwrite, nonatomic, strong) id value; - (id)initWithField:(id)field value:(id)value; - (NSString *)URLEncodedStringValue; @end
AFQueryStringFromParameters
函數負責將請求參數字典,轉成拼接在URL
後面的參數字符串,這個函數是AFQueryStringPair
類中定義的一個關鍵函數。函數內部經過AFQueryStringPairsFromDictionary
函數將參數字典,轉爲存儲pair
對象的數組並進行遍歷,遍歷後調用URLEncodedStringValue
方法對參數進行拼接,最後成爲字符串參數。
URLEncodedStringValue
方法實現很簡單,就是進行一個key
、value
的拼接,而且在中間加上「=」。
static NSString * AFQueryStringFromParameters(NSDictionary *parameters) { NSMutableArray *mutablePairs = [NSMutableArray array]; for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) { [mutablePairs addObject:[pair URLEncodedStringValue]]; } return [mutablePairs componentsJoinedByString:@"&"]; } - (NSString *)URLEncodedStringValue { if (!self.value || [self.value isEqual:[NSNull null]]) { return AFPercentEscapedStringFromString([self.field description]); } else { return [NSString stringWithFormat:@"%@=%@", AFPercentEscapedStringFromString([self.field description]), AFPercentEscapedStringFromString([self.value description])]; } }
下面是參數拼接的代碼,函數內部會將原有的參數,轉換爲AFQueryStringPair
對象的類型,但以前的層級結構不變。這句話是什麼意思呢,就是說對原有傳入的對象進行逐層遞歸調用,而且將最後一層字典的key
、value
參數,轉成pair
類型的對象,而且將嵌套有pair
對象的數組返回給調用方。
對象層級不變,但字典、集合都會被轉換爲數組結構,也就是以前傳入字典、數組、字典的嵌套結構,返回的時候就是數組、數組、pair
的結構返回。
NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) { return AFQueryStringPairsFromKeyAndValue(nil, dictionary); } NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) { NSMutableArray *mutableQueryStringComponents = [NSMutableArray array]; NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)]; if ([value isKindOfClass:[NSDictionary class]]) { NSDictionary *dictionary = value; for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]]) { id nestedValue = dictionary[nestedKey]; if (nestedValue) { [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)]; } } } else if ([value isKindOfClass:[NSArray class]]) { NSArray *array = value; for (id nestedValue in array) { [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[]", key], nestedValue)]; } } else if ([value isKindOfClass:[NSSet class]]) { NSSet *set = value; for (id obj in [set sortedArrayUsingDescriptors:@[ sortDescriptor ]]) { [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue(key, obj)]; } } else { [mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]]; } return mutableQueryStringComponents; }
AFHTTPRequestSerializer
在建立NSMutableURLRequest
時,須要爲request
設置屬性。serializer
對外提供了和request
同名的一些屬性,外界直接調用serializer
便可設置request
的屬性。
AFHTTPRequestSerializer
內部建立request
時,並非根據設置request
的屬性按個賦值,而是經過一個屬性數組AFHTTPRequestSerializerObservedKeyPaths
,將serializer
須要賦值給request
的屬性,都放在數組中。
static NSArray * AFHTTPRequestSerializerObservedKeyPaths() { static NSArray *_AFHTTPRequestSerializerObservedKeyPaths = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ _AFHTTPRequestSerializerObservedKeyPaths = @[NSStringFromSelector(@selector(allowsCellularAccess)), NSStringFromSelector(@selector(cachePolicy)), NSStringFromSelector(@selector(HTTPShouldHandleCookies)), NSStringFromSelector(@selector(HTTPShouldUsePipelining)), NSStringFromSelector(@selector(networkServiceType)), NSStringFromSelector(@selector(timeoutInterval))]; }); return _AFHTTPRequestSerializerObservedKeyPaths; }
在初始化AFHTTPRequestSerializer
時,遍歷keyPath
數組並經過KVO
的方式,監聽serializer
的賦值。若是外界對serializer
對應的屬性進行賦值,則將其添加到mutableObservedChangedKeyPaths
數組中。在建立request
對象是,遍歷mutableObservedChangedKeyPaths
數組並將值賦值給request
對象。
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) { if ([self respondsToSelector:NSSelectorFromString(keyPath)]) { [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext]; } } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(__unused id)object change:(NSDictionary *)change context:(void *)context { if (context == AFHTTPRequestSerializerObserverContext) { if ([change[NSKeyValueChangeNewKey] isEqual:[NSNull null]]) { [self.mutableObservedChangedKeyPaths removeObject:keyPath]; } else { [self.mutableObservedChangedKeyPaths addObject:keyPath]; } } } for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) { if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) { [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath]; } }
當進行POST
表單提交時,須要用到AFMultipartFormData
協議。調用POST
方法後,會回調一個遵照此協議的對象,能夠經過此對象進行表單提交操做。
[manager POST:requestURL parameters:params constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) { [formData appendPartWithFileData:params[@"front_img"] name:@"front_img" fileName:frontImgfileName mimeType:@"multipart/form-data"]; [formData appendPartWithFileData:params[@"reverse_img"] name:@"reverse_img" fileName:reverseImgfileName mimeType:@"multipart/form-data"]; [formData appendPartWithFileData:params[@"face_img"] name:@"face_img" fileName:faceImgfileName mimeType:@"multipart/form-data"]; } progress:^(NSProgress * _Nonnull uploadProgress) { // nothing } success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { // nothing } failure:nil];
進行表單提交時,能夠直接傳入文件,也能夠傳入路徑。表單提交能夠同時提交多個文件,理論上數量不受限制。
AFN
的緩存策略和NSURLCache
的緩存策略一致,而且直接使用系統的枚舉,這對iOS
開發者是很是友好的。下面是枚舉定義,忽略掉一些unimplemented
的,和一些重定向到已有枚舉的,可用的都在這。
typedef NS_ENUM(NSUInteger, NSURLRequestCachePolicy) { NSURLRequestUseProtocolCachePolicy = 0, NSURLRequestReloadIgnoringLocalCacheData = 1, NSURLRequestReturnCacheDataElseLoad = 2, NSURLRequestReturnCacheDataDontLoad = 3, NSURLRequestReloadIgnoringLocalAndRemoteCacheData = 4, NSURLRequestReloadRevalidatingCacheData = 5, };
AFURLResponseSerialization
負責處理response
相關的邏輯,其功能主要是設置acceptType
、編碼格式和處理服務器返回數據。一樣的,AFURLResponseSerialization
也有同名的協議,每一個子類都遵循代理方法並實現不一樣的返回值處理代碼。
- (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response data:(nullable NSData *)data error:(NSError * _Nullable __autoreleasing *)error;
和AFURLRequestSerialization
同樣,AFURLResponseSerialization
由一個父類和六個子類構成,子類中有一個是Mac
的,因此這裏不作分析,子類的職責只是對acceptType
作修改以及處理具體的返回數據。
NSData
二進制。JSON
返回數據,也是默認類型。XML
返回數據,由系統NSXMLParser
負責處理。XML
返回數據,也就是plist
數據。因爲服務器有時候會返回null
的狀況,系統會將其轉換爲NSNull
對象,而對NSNull
對象發送不正確的消息,就會致使崩潰。從服務器接收到返回值後,AFN
會對返回值進行一個遞歸查找,找到全部NSNull
對象並將其移除,防止出現向NSNull
對象發送消息致使的崩潰。
static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingOptions readingOptions) { if ([JSONObject isKindOfClass:[NSArray class]]) { NSMutableArray *mutableArray = [NSMutableArray arrayWithCapacity:[(NSArray *)JSONObject count]]; for (id value in (NSArray *)JSONObject) { [mutableArray addObject:AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions)]; } return (readingOptions & NSJSONReadingMutableContainers) ? mutableArray : [NSArray arrayWithArray:mutableArray]; } else if ([JSONObject isKindOfClass:[NSDictionary class]]) { NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionaryWithDictionary:JSONObject]; for (id <NSCopying> key in [(NSDictionary *)JSONObject allKeys]) { id value = (NSDictionary *)JSONObject[key]; if (!value || [value isEqual:[NSNull null]]) { [mutableDictionary removeObjectForKey:key]; } else if ([value isKindOfClass:[NSArray class]] || [value isKindOfClass:[NSDictionary class]]) { mutableDictionary[key] = AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions); } } return (readingOptions & NSJSONReadingMutableContainers) ? mutableDictionary : [NSDictionary dictionaryWithDictionary:mutableDictionary]; } return JSONObject; }
在使用NSBundle
對象時,咱們最經常使用的就是mainBundle
或者bundleWithPath
這種方式獲取bundle
,這種對於都是從app
二進制讀取的時候是沒有問題的。可是若是涉及到framework
動態庫,就不是那麼易於使用。
framework
中能夠包含資源文件,例如.bundle
文件。若是是動態庫形式的framework
(framework
也有靜態形式),其會以一個獨立二進制的形式表現,而且會分配獨立的二進制空間。在讀取bundle
的時候,就能夠考慮使用bundleForClass
的方式讀取。
bundleForClass
表示從當前類定義的二進制,所在的程序包中讀取NSBundle
文件。例如.app
就是從main bundle
中讀取,若是是framework
就從其所在的二進制中讀取。
AFN
提供了一些UIKit
的Category
,例如網絡請求發起時,網絡指示器轉菊花,則由AFNetworkActivityIndicatorManager
類負責。開啓網絡指示器很簡單,添加下面代碼便可,網絡指示器默認是關閉的。
[[AFNetworkActivityIndicatorManager sharedManager] setEnabled:YES];
這裏不對AFNetworkActivityIndicatorManager
的代碼進行過多的分析,只是調其中比較重要的點來分析,下面統稱爲indicatorManager
。
以前在_AFURLSessionTaskSwizzling
類中寫了不少代碼,就是爲了發出resume
和suspend
兩個通知,這兩個通知在indicatorManager
中就用到了。網絡指示器監聽了下面的三個通知,而且徹底由通知來驅動。
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidStart:) name:AFNetworkingTaskDidResumeNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidFinish:) name:AFNetworkingTaskDidSuspendNotification object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidFinish:) name:AFNetworkingTaskDidCompleteNotification object:nil];
若是看indicatorManager
中的源碼,你會發現爲何裏面還有timer
,徹底不須要啊,有網絡請求就轉菊花,沒網絡請求就中止不就好了嗎?
這是由於AFN
考慮,若是一個網絡請求很快的話,會致使菊花出現轉一下很快就消失的狀況,若是網絡請求比較多會屢次閃現。因此對於這個問題,indicatorManager
經過Timer
的方式實現,若是在指定的區間內網絡請求已經結束,則不在顯示菊花,若是有屢次請求則在請求之間也不進行中斷。
對於開始轉圈設置的是1.0秒,結束轉圈設置的是0.17秒。也就是當菊花開始旋轉時,須要有1.0秒的延時,這個時間足以保證以前的菊花中止轉動。結束轉圈則會在0.17秒以後進行,能夠保證菊花的旋轉至少會有0.17秒。
static NSTimeInterval const kDefaultAFNetworkActivityManagerActivationDelay = 1.0; static NSTimeInterval const kDefaultAFNetworkActivityManagerCompletionDelay = 0.17; - (void)startActivationDelayTimer { self.activationDelayTimer = [NSTimer timerWithTimeInterval:self.activationDelay target:self selector:@selector(activationDelayTimerFired) userInfo:nil repeats:NO]; [[NSRunLoop mainRunLoop] addTimer:self.activationDelayTimer forMode:NSRunLoopCommonModes]; } - (void)startCompletionDelayTimer { [self.completionDelayTimer invalidate]; self.completionDelayTimer = [NSTimer timerWithTimeInterval:self.completionDelay target:self selector:@selector(completionDelayTimerFired) userInfo:nil repeats:NO]; [[NSRunLoop mainRunLoop] addTimer:self.completionDelayTimer forMode:NSRunLoopCommonModes]; }
因爲indicatorManager
是採用通知的方式進行回調,全部的網絡請求通知都會調到這。因此當多個網絡請求到來時,會經過一個_activityCount
來進行計數,能夠將其理解爲一個隊列,這樣更容易理解。在顯示網絡指示器的時候,就是基於_activityCount
來進行判斷的,若是隊列中有請求則顯示網絡指示器,不管有多少請求。
這種設計思路比較好,在項目中不少地方均可以用到。例若有些方法須要成對進行調用,例如播放開始和暫停,若是某一個方法調用屢次就會形成bug
。這種方式就比較適合用count
的方式進行容錯,內部針對count
作一些判斷操做。
- (void)incrementActivityCount { [self willChangeValueForKey:@"activityCount"]; @synchronized(self) { _activityCount++; } [self didChangeValueForKey:@"activityCount"]; dispatch_async(dispatch_get_main_queue(), ^{ [self updateCurrentStateForNetworkActivityChange]; }); } - (void)decrementActivityCount { [self willChangeValueForKey:@"activityCount"]; @synchronized(self) { _activityCount = MAX(_activityCount - 1, 0); } [self didChangeValueForKey:@"activityCount"]; dispatch_async(dispatch_get_main_queue(), ^{ [self updateCurrentStateForNetworkActivityChange]; }); }
indicatorManager
是多線程安全的,在一些關鍵地方都經過synchronized
的方式加鎖,防止從各個線程調用過來的通知形成資源搶奪的問題。
AFN
支持https
請求,並經過AFSecurityPolicy
類來處理https
證書及驗證,但其https
請求的執行仍是交給NSURLSession
去完成的。
下面是NSURLSession
的一個代理方法,當須要進行證書驗證時,能夠重寫此方法並進行自定義的驗證處理。驗證完成後經過completionHandler
的block
來告知處理結果,而且將驗證結果disposition
和公鑰credential
傳入。
AFN
經過AFSecurityPolicy
類提供了驗證邏輯,而且在內部能夠進行證書的管理。也能夠不使用AFN
提供的驗證邏輯,重寫sessionDidReceiveAuthenticationChallenge
的block
便可自定義驗證邏輯,不走AFN
的邏輯。
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler { NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling; __block NSURLCredential *credential = nil; if (self.sessionDidReceiveAuthenticationChallenge) { disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential); } else { if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) { credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; if (credential) { disposition = NSURLSessionAuthChallengeUseCredential; } else { disposition = NSURLSessionAuthChallengePerformDefaultHandling; } } else { disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge; } } else { disposition = NSURLSessionAuthChallengePerformDefaultHandling; } } if (completionHandler) { completionHandler(disposition, credential); } }
除了進行NSURLSession
請求驗證的回調,對於每一個task
也有對應的代理方法。兩個代理方法內部實現基本同樣,區別在於對於每一個task
,AFN
提供了taskDidReceiveAuthenticationChallenge
回調block
,能夠由外界自定義證書驗證過程。
驗證結果是經過一個枚舉回調給NSURLSession
的,參數是一個NSURLSessionAuthChallengeDisposition
類型的枚舉,表示證書驗證的狀況,此枚舉包含下面幾個具體值。
使用當前證書創建SSL
鏈接,並處理後續請求
使用默認的處理方式,當前證書被忽略
驗證不經過,取消整個網絡請求
此次驗證被忽略,但不取消網絡請求
HTTPS
請求的密鑰管理等安全相關的處理,都放在Security.framework
框架中。在AFSecurityPolicy
中常常能夠看到SecTrustRef
類型的變量,其表示的就是密鑰對象,其中包含了公鑰等信息。
咱們能夠經過下面的命令獲取到公鑰,具體格式這裏不作過多介紹,詳細的能夠Google一下公鑰格式。
// 獲取公鑰命令 SecTrustCopyPublicKey(serverTrust) // 打印的公鑰(公鑰已作脫敏) <SecKeyRef algorithm id: 1, key type: RSAPublicKey, version: 4, block size: 2048 bits, exponent: {hex: 10001, decimal: 65537}, modulus: A51E89C5FFF2748A6F70C4D701D29A39723C3BE495CABC5487B05023D957CD287839F6B5E53F90B963438417547A369BBA5818D018B0E98F2449442DFBD7F405E18D5A827B6F6F0A5A5E5585C6C0F342DDE727681902021B7A7CE0947EFCFDDC4CCF8E100D94A454156FD3E457F4719E3C6B9E408CD4316B976A6C44BD91A057FEA4A115BEB1FE28E71005D2198E3B79B8942779B434A0B08F82B3D390A7B94B958BB71D9B69564C84A1B03FE22D4C4E24BBA2D5ED3F660F1EBC757EF0E52A7CF13A8C167B0E6171B2CD6678CC02EAF1E59147F53B3671C33107D9ED5238CBE33DB617931FFB44DE70043B2A618D8F43608D6F494360FFDEE83AD1DCE120D6F1, addr: 0x280396dc0>
AFSecurityPolicy
的職責比較單一,只處理公鑰和驗證的邏輯,其定義是一個單例對象。此類主要由四個屬性和一個方法構成。
// 證書驗證方式 @property (readonly, nonatomic, assign) AFSSLPinningMode SSLPinningMode; // 本地自簽名證書集合 @property (nonatomic, strong, nullable) NSSet <NSData *> *pinnedCertificates; // 是否驗證證書的合法性(是否容許自簽名證書) @property (nonatomic, assign) BOOL allowInvalidCertificates; // 是否驗證域名是否有效 @property (nonatomic, assign) BOOL validatesDomainName;
若是進行細分的話,AFSecurityPolicy
的功能基本就兩個。一個是經過CA
的方式進行驗證,另外一個就是進行SSL Pinning
自簽名驗證。evaluateServerTrust:forDomain:
是AFSecurityPolicy
最主要的方法,用來進行證書的合法性驗證。
AFSecurityPolicy
進行SSL Pinning
驗證的方式分爲如下三種,若是是None
則會執行正常CA
驗證的流程,其餘兩種都是自簽名的流程。AFN
中默認的調用是defaultPolicy
方法,其內部設置的是AFSSLPinningModeNone
模式。
正常流程,經過CA
機構頒發的公鑰,對服務器下發的證書驗證數字簽名,而且得到公鑰。
不經過CA
的流程進行驗證,而是經過本地內置的服務端證書進行驗證,驗證過程分爲兩步。首先驗證證書是否過時或失效,其次驗證本地是否包含此證書。
不進行CA
的驗證,也不驗證證書,只驗證公鑰是否有效。
對於本地自簽名證書的管理有兩種方式,一種是默認會在本地查找遍歷全部.cer
的文件,並存在一個自簽名證書的集合中。也能夠在建立AFSecurityPolicy
對象時傳入SSLPinningMode
,下面是查找本地.cer
文件的邏輯。
+ (NSSet *)certificatesInBundle:(NSBundle *)bundle { NSArray *paths = [bundle pathsForResourcesOfType:@"cer" inDirectory:@"."]; NSMutableSet *certificates = [NSMutableSet setWithCapacity:[paths count]]; for (NSString *path in paths) { NSData *certificateData = [NSData dataWithContentsOfFile:path]; [certificates addObject:certificateData]; } return [NSSet setWithSet:certificates]; }
HTTPS
在進行握手時,須要經過CA
的公鑰進行驗證,保證服務器公鑰的合法性,沒有被篡改成有問題的公鑰。若是使用CA
機構頒發的證書,不管使用NSURLSession
仍是AFNetworking
都不須要修改代碼,這些都會自動完成。若是不想使用CA
的證書驗證,例如自簽名證書在CA
證書驗證時就會失敗。
這種狀況可使用自簽名的方式進行驗證,也就是在客戶端本地內置一份證書,服務器進行四次握手時,經過保存在本地的證書和服務器進行對比,證書相同則表示驗證成功,不走CA
的驗證方式。
AFN
爲咱們提供了自簽名證書的驗證方法,經過SSLPinningMode
設置驗證方式爲自簽名,而且傳入證書集合。若是沒有傳入證書集合,則AFN
默認會遍歷整個沙盒,查找全部.cer
的證書。
進行沙盒驗證時,須要將AFSecurityPolicy
的allowInvalidCertificates
設置爲YES
,默認是NO
,表示容許無效的證書,也就是自簽名的證書。
NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"12306" ofType:@"cer"]; NSData *certData = [NSData dataWithContentsOfFile:cerPath]; NSSet *set = [NSSet setWithObject:certData]; AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate withPinnedCertificates:set]; securityPolicy.allowInvalidCertificates = YES;
AFNetworking2.x
由NSURLSession
和NSURLConnection
兩部分組成,而且分別對應不一樣的類,這裏主要介紹NSURLConnection
部分的源碼實現。
NSURLConnection
實現由下面三個類構成,從源碼構成能夠看出,不管是session
仍是connection
方案,都具備很好的擴展性。例如這裏AFHTTPRequestOperation
是基於AFURLConnectionOperation
實現的,若是須要實現FTP
協議,則能夠建立一個繼承自AFURLConnectionOperation
的AFFPTConnectionOperation
類並重寫對應方法便可。
NSOperation
,負責網絡請求的邏輯實現,每一個網絡請求就是一個Operation
對象。AFURLConnectionOperation
,處理HTTP
相關網絡請求。NSOperationQueue
,負責管理全部Operation
網絡請求。下面是AFURLConnectionOperation
的初始化方法,和AFURLSessionManager
有些不同。其內部增長了狀態的概念,以及RunloopMode
的概念,這兩個咱們後面會詳細講解。shouldUseCredentialStorage
表示是否由系統作證書驗證,後面設置了securityPolicy
,和sessionManager
同樣也是使用默認方案。
- (instancetype)initWithRequest:(NSURLRequest *)urlRequest { _state = AFOperationReadyState; self.lock = [[NSRecursiveLock alloc] init]; self.lock.name = kAFNetworkingLockName; self.runLoopModes = [NSSet setWithObject:NSRunLoopCommonModes]; self.request = urlRequest; self.shouldUseCredentialStorage = YES; self.securityPolicy = [AFSecurityPolicy defaultPolicy]; }
AFURLConnectionOperation
繼承自NSOperation
,因爲NSOperation
和網絡請求的過程很像,有開始、暫停、完成等,而且很好的支持KVO
監聽,因此AFN
將每一個網絡請求都當作一個Operation
任務。AFURLConnectionOperation
能夠設置任務優先級,也能夠經過AFHTTPRequestOperationManager
設置最大併發數,基本上NSOperationQueue
提供的功能都能用。
在AFHTTPRequestOperationManager
中定義了NSOperationQueue
,建立網絡請求任務後,都會被加入到queue
中,隨後由系統調用queue
中的operation
任務,執行operation
的start
方法發起請求。AFURLConnectionOperation
只須要在內部實現start
、pause
、resume
等父類方法便可,其餘都由系統去進行調用。這種設計能夠很好的將manager
和operation
進行解耦,兩者不用直接發生調用關係。
NSURLConnection
中,每一個網絡請求對應一個AFHTTPRequestOperation
,全部網絡請求都共用一個manager
來管理operation
。而AFHTTPSessionManager
則不一樣,每一個網絡請求對應一個manager
以及一個task
。
AFURLConnectionOperation
支持KVO
的方式,讓外界監聽網絡請求的變化,並經過重寫setState
方法,在內部加入willChangeValueForKey
觸發KVO
回調。AFN
經過AFOperationState
來管理網絡請求狀態,下面是AFN
對其的狀態定義。
當網絡請求狀態發生改變時,都會調用setState
方法進行賦值,例以下面是請求完成時的處理代碼。除此以外,當判斷AFN
請求狀態時,也是經過這個屬性做爲判斷依據的。
- (void)finish { [self.lock lock]; self.state = AFOperationFinishedState; [self.lock unlock]; }
AFURLConnectionOperation
中設計了常駐線程,而且重寫了operation
的start
等方法,網絡請求的start
、cancel
、pause
等操做,都是在常駐線程中完成的。網絡請求結束後,數據回調的處理也是在這個線程中完成的。
這是由於在哪一個線程建立NSURLConnection
對象併發出請求,則數據返回時也默認從那個線程接受數據。若是請求都是從主線程發出的,請求返回時若是屏幕正在滑動,runloopMode
爲UITrackingRunLoopMode
則不能處理返回數據。而若是把網絡請求都加到主線程的NSRunLoopCommonModes
中,在大量網絡請求返回時,處理返回數據會影響屏幕滑動FPS
。
因此爲了保證網絡請求數據能夠正常返回並被處理,而又不影響屏幕FPS
,則用一個單獨的線程來處理。若是每一個請求都對應一個線程來處理返回任務,會形成大量線程的佔用,因此用一個常駐線程來處理全部網絡請求,來保證線程資源的最小佔用。常駐線程其實是一個單例線程,而且這個單例線程被加入了一個Port
進行保活,保證線程能夠不被退出。
+ (void)networkRequestThreadEntryPoint:(id)__unused object { @autoreleasepool { [[NSThread currentThread] setName:@"AFNetworking"]; NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; [runLoop run]; } } + (NSThread *)networkRequestThread { static NSThread *_networkRequestThread = nil; static dispatch_once_t oncePredicate; dispatch_once(&oncePredicate, ^{ _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil]; [_networkRequestThread start]; }); return _networkRequestThread; }
經過AFURLConnectionOperation
發起網絡請求時,實際建立connection
對象的代碼在下面的方法中。在建立connection
對象後,並無當即發出網絡請求,而是將startImmediately
設置爲NO
。隨後會設置NSURLConnection
和NSOutputStream
的RunloopMode
,網絡請求會從單例線程的runLoopModes
中發出,這樣當網絡請求返回時,回調代碼也會在runLoopModes
中去執行。
operationDidStart
方法中會調用NSURLConnection
的scheduleInRunLoop:forMode:
方法,將網絡請求任務派發到Runloop
指定的Mode
中。我以爲給Operation
設置runLoopModes
其實意義不大,由於常駐線程基本上只會有一個Mode
,也就是NSRunloopDefaultMode
,基本上不會有其餘Mode
,因此這裏設置runLoopModes
沒什麼意義。
- (void)operationDidStart { self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO]; NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; for (NSString *runLoopMode in self.runLoopModes) { [self.connection scheduleInRunLoop:runLoop forMode:runLoopMode]; [self.outputStream scheduleInRunLoop:runLoop forMode:runLoopMode]; } [self.outputStream open]; [self.connection start]; }
AFURLConnectionOperation
是經過NSURLConnection
實現網絡請求的,這裏簡單講一下operation
中代理方法的實現。
AFN
實現了https
證書驗證的代碼,具體實現和AFURLSessionManager
基本相似,而且也是經過AFSecurityPolicy
來處理具體的證書驗證邏輯。
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
關於請求服務器數據這塊值得講一下,在NSURLConnection
接收服務器數據時,AFN
經過建立了一個outputStream
,來承載和組織具體的數據,而且在內存中進行存儲。當沒有可用空間或發生其餘錯誤時,會經過streamError
的方式進行體現。
當網絡請求結束時,會調用didFinishLoading
方法,AFN
會從outputStream
中拿出數據並賦值給responseData
,當作返回值數據使用。
- (void)connection:(NSURLConnection __unused *)connection didReceiveData:(NSData *)data { NSUInteger length = [data length]; while (YES) { NSInteger totalNumberOfBytesWritten = 0; if ([self.outputStream hasSpaceAvailable]) { const uint8_t *dataBuffer = (uint8_t *)[data bytes]; NSInteger numberOfBytesWritten = 0; while (totalNumberOfBytesWritten < (NSInteger)length) { numberOfBytesWritten = [self.outputStream write:&dataBuffer[(NSUInteger)totalNumberOfBytesWritten] maxLength:(length - (NSUInteger)totalNumberOfBytesWritten)]; if (numberOfBytesWritten == -1) { break; } totalNumberOfBytesWritten += numberOfBytesWritten; } break; } else { [self.connection cancel]; if (self.outputStream.streamError) { [self performSelector:@selector(connection:didFailWithError:) withObject:self.connection withObject:self.outputStream.streamError]; } return; } } if (self.downloadProgress) { self.downloadProgress(length, self.totalBytesRead, self.response.expectedContentLength); } } - (void)connectionDidFinishLoading:(NSURLConnection __unused *)connection { self.responseData = [self.outputStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey]; [self.outputStream close]; if (self.responseData) { self.outputStream = nil; } self.connection = nil; [self finish]; }
有outputStream
,也會有與之對應的inputStream
,inputStream
實現很簡單,就是修改NSMutableURLRequest
的HTTPBodyStream
。
- (NSInputStream *)inputStream { return self.request.HTTPBodyStream; } - (void)setInputStream:(NSInputStream *)inputStream { NSMutableURLRequest *mutableRequest = [self.request mutableCopy]; mutableRequest.HTTPBodyStream = inputStream; self.request = mutableRequest; }
在建立AFHTTPRequestOperation
時會將success
和failure
的block
傳給operation
,而且在operation
執行完成並回調completionBlock
時,執行這兩個block
代碼。可是因爲completionBlock
中直接使用了self
,致使了循環引用的問題。
- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id __nullable responseObject))success failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure { self.completionBlock = ^{ // something code ... }; }
completionBlock
的循環引用是AFN
有意而爲之的,爲的就是保持operation
的生命週期,以保證請求處理完成並接收返回的block
回調。
對於循環引用的生命週期,AFN
採起的是主動打破循環引用的方式,也就是重寫父類的completionBlock
,而且在調用block
結束後,主動將completionBlock
賦值爲nil
,從而主動打破循環引用。
- (void)setCompletionBlock:(void (^)(void))block { if (!block) { [super setCompletionBlock:nil]; } else { __weak __typeof(self)weakSelf = self; [super setCompletionBlock:^ { __strong __typeof(weakSelf)strongSelf = weakSelf; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wgnu" dispatch_group_t group = strongSelf.completionGroup ?: url_request_operation_completion_group(); dispatch_queue_t queue = strongSelf.completionQueue ?: dispatch_get_main_queue(); #pragma clang diagnostic pop dispatch_group_async(group, queue, ^{ block(); }); dispatch_group_notify(group, url_request_operation_completion_queue(), ^{ [strongSelf setCompletionBlock:nil]; }); }]; } }
AFNetworking
中還有很重要的一部分,就是Reachability
,用來作網絡狀態監控的。AFNetworking
、YYKit
、蘋果官方都提供有Reachability
的API
使用,內部實現原理基本差很少。
代碼實現也很簡單,主要依賴SystemConfiguration.framework
框架的SCNetworkReachability
,註冊一個Callback
而後等着回調就能夠。這裏講一下核心邏輯,一些細枝末節的就忽略了。
Reachability
提供了兩種初始化方法,一種是經過域名初始化的managerForDomain:
方法,傳入一個域名,基於這個域名的訪問狀況來判斷當前網絡狀態。另外一種是經過地址初始化的managerForAddress:
方法,建立一個sockaddr_in
對象,並基於這個對象來判斷網絡狀態。
+ (instancetype)managerForAddress:(const void *)address { SCNetworkReachabilityRef reachability = SCNetworkReachabilityCreateWithAddress(kCFAllocatorDefault, (const struct sockaddr *)address); AFNetworkReachabilityManager *manager = [[self alloc] initWithReachability:reachability]; CFRelease(reachability); return manager; } + (instancetype)manager { struct sockaddr_in address; bzero(&address, sizeof(address)); address.sin_len = sizeof(address); address.sin_family = AF_INET; return [self managerForAddress:&address]; }
下面startMonitoring
中是開啓網絡監測的核心代碼,主要邏輯是設置了兩個Callback
,一個是block
的一個是函數的,並添加到Runloop
中開始監控。由此能夠推測,Reachability
的代碼實現主要依賴Runloop
的事件循環,而且在事件循環中判斷網絡狀態。
當網絡發生改變時,就會回調AFNetworkReachabilityCallback
函數,回調有三個參數。target
是SCNetworkReachabilityRef
對象,flags
是網絡狀態,info
是咱們設置的block
回調參數。回調Callback
函數後,內部會經過block
以及通知的形式,對外發出回調。
- (void)startMonitoring { [self stopMonitoring]; if (!self.networkReachability) { return; } __weak __typeof(self)weakSelf = self; AFNetworkReachabilityStatusBlock callback = ^(AFNetworkReachabilityStatus status) { __strong __typeof(weakSelf)strongSelf = weakSelf; strongSelf.networkReachabilityStatus = status; if (strongSelf.networkReachabilityStatusBlock) { strongSelf.networkReachabilityStatusBlock(status); } }; SCNetworkReachabilityContext context = {0, (__bridge void *)callback, AFNetworkReachabilityRetainCallback, AFNetworkReachabilityReleaseCallback, NULL}; SCNetworkReachabilitySetCallback(self.networkReachability, AFNetworkReachabilityCallback, &context); SCNetworkReachabilityScheduleWithRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0),^{ SCNetworkReachabilityFlags flags; if (SCNetworkReachabilityGetFlags(self.networkReachability, &flags)) { AFPostReachabilityStatusChange(flags, callback); } }); } - (void)stopMonitoring { if (!self.networkReachability) { return; } SCNetworkReachabilityUnscheduleFromRunLoop(self.networkReachability, CFRunLoopGetMain(), kCFRunLoopCommonModes); } static void AFNetworkReachabilityCallback(SCNetworkReachabilityRef __unused target, SCNetworkReachabilityFlags flags, void *info) { AFPostReachabilityStatusChange(flags, (__bridge AFNetworkReachabilityStatusBlock)info); }
AFNetworking
對請求數據的序列化,以及返回數據的反序列化作了不少處理。使開發者只須要傳入一個字典便可構建請求參數,無需處理拼接到URL
後面或參數轉換爲body
二進制的細節,這些都由AFNetworking
內部處理並進行容錯,開發者只須要指定請求方式便可。
經過AFNetworking
實現https
也很方便,AFSecurityPolicy
能夠很好的管理CA
以及自簽名證書,以及處理https
請求過程當中的一些邏輯,咱們只須要告訴AFNetworking
怎麼處理便可,若是不指定處理方式則使用默認CA
證書的方式處理。
AFNetworking
對於後臺下載以及斷點續傳有很好的支持,咱們能夠在AFNetworking
的基礎上,很簡單的就完成一個下載模塊的設計。若是本身寫後臺下載和斷點續傳的代碼,工做量仍是不小的。
而且AFNetworking
在網絡庫的設計上還提供了很強的自定義性,例如指定證書、URL
緩存處理,以及下載過程當中不一樣下載階段的處理。若是沒有提供自定義處理,則使用默認處理方式。