本文轉載請註明出處 —— polobymulberry-博客園html
上一篇還遺留了不少問題,包括AFURLSessionManagerTaskDelegate類所實現的NSURLSession相關的代理方法,甚至連dataTask、uploadTask、downloadTask這幾個基本概念也沒說。這一篇就是爲了集中消滅這些遺留問題。ios
此處實現的仍然是NSURLSession相關的代理方法,由於上一篇中已經詳細介紹過了,因此對應的相關方法介紹就不贅述,直接介紹方法實現。git
該函數在AFURLSessionManager中的- URLSession:task:didCompleteWithError:被調用github
函數實現:web
- (void)URLSession:(__unused NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { // 保存clang診斷的上下文,相似OpenGL狀態機,和後面的pop配對使用 #pragma clang diagnostic push // 使用?:符號,注意x ? x : y == x ?: y,以前博客中要是有理解錯誤,以此爲準 #pragma clang diagnostic ignored "-Wgnu" __strong AFURLSessionManager *manager = self.manager; __block id responseObject = nil; // 由於NSNotification這個類中自己有userInfo屬性,可做爲響應函數的參數 // 不過我在AFNetworking源碼中還未發現使用userInfo做爲參數的作法,可能須要用戶本身實現 /**
* userInfo中的key值例舉以下: * AFNetworkingTaskDidCompleteResponseDataKey session 存儲task獲取到的原始response數據,與序列化後的response有所不一樣 * AFNetworkingTaskDidCompleteSerializedResponseKey 存儲通過序列化(serialized)後的response * AFNetworkingTaskDidCompleteResponseSerializerKey 保存序列化response的序列化器(serializer) * AFNetworkingTaskDidCompleteAssetPathKey 存儲下載任務後,數據文件存放在磁盤上的位置 * AFNetworkingTaskDidCompleteErrorKey 錯誤信息 */ __block NSMutableDictionary *userInfo = [NSMutableDictionary dictionary]; // serializer userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer; //具體能夠查看#issue 2672。這裏主要是針對大文件的時候,性能提高會很明顯 NSData *data = nil; if (self.mutableData) { // 要先判斷是否爲nil data = [self.mutableData copy]; //此處再也不須要mutableData了 self.mutableData = nil; } if (self.downloadFileURL) { userInfo[AFNetworkingTaskDidCompleteAssetPathKey] = self.downloadFileURL; } else if (data) { userInfo[AFNetworkingTaskDidCompleteResponseDataKey] = data; } // 若是task出錯了,處理error信息 // 因此對應的觀察者在處理error的時候,好比能夠先判斷userInfo[AFNetworkingTaskDidCompleteErrorKey]是否有值,有值的話,就說明是要處理error if (error) { userInfo[AFNetworkingTaskDidCompleteErrorKey] = error; // 這裏用group方式來運行task完成方法,表示當前全部的task任務完成,纔會通知執行其餘操做 // 若是沒有實現自定義的completionGroup和completionQueue,那麼就使用AFNetworking提供的私有的dispatch_group_t和提供的dispatch_get_main_queue內容 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; // 根據對應的task和data將response data解析成可用的數據格式,好比JSON serializer就將data解析成JSON格式 responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError]; // 注意若是有downloadFileURL,意味着data存放在了磁盤上了,因此此處responseObject保存的是data存放位置,供後面completionHandler處理。沒有downloadFileURL,就直接使用內存中的解析後的data數據 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 }
該函數在AFURLSessionManager中的- URLSession:dataTask:didReceiveData:被調用安全
函數實現:網絡
- (void)URLSession:(__unused NSURLSession *)session dataTask:(__unused NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { // 將每次得到的新數據附在mutableData上,來組成最終得到的全部數據 [self.mutableData appendData:data]; }
該函數在AFURLSessionManager中的- URLSession:downloadTask:didFinishDownloadingToURL:被調用
函數實現:session
和AFURLSessionManager中的實現相似,這裏就不贅述了。app
首先簡單介紹下session task,如下語句引用自從 NSURLConnection 到 NSURLSessionasync
NSURLsessionTask
是一個抽象類,其下有 3 個實體子類能夠直接使用:NSURLSessionDataTask
、NSURLSessionUploadTask
、NSURLSessionDownloadTask
。這 3 個子類封裝了現代程序三個最基本的網絡任務:獲取數據,好比 JSON 或者 XML,上傳文件和下載文件。
當一個 NSURLSessionDataTask
完成時,它會帶有相關聯的數據,而一個 NSURLSessionDownloadTask
任務結束時,它會帶回已下載文件的一個臨時的文件路徑(還記得前面的location吧)。由於通常來講,服務端對於一個上傳任務的響應也會有相關數據返回,因此NSURLSessionUploadTask
繼承自 NSURLSessionDataTask
。
以前討論dataTask比較多,對於uploadTask和downloadTask說起較少。好比咱們以前只說了- [AFURLSessionManager dataTaskWithRequest:uploadProgress:downloadProgress:completionHandler:]。其實還有相似的uploadTaskWithRequest:和downloadTaskWithRequest:等方法。
不知道你們看到這裏會不會跟我同樣有疑問——已經有了dataTask了,爲何還要實現一個uploadTask?咱們從二者提供的對應task 生成的方法能看出一點端倪。好比使用dataTask來進行上傳任務的時候,須要指定HTTPMethod爲POST或PUT,而且提供的數據(NSData)得賦值給request.HTTPBody。而使用uploadTask來進行上傳任務的時候,只須要使用- uploadTaskWithRequest:fromData:或- uploadTaskWithRequest:fromFile:之類的方法,其中參數的話只須要根提供數據(NSData)或者數據的磁盤位置(NSURL*fileURL)就能夠構造出一個上傳的session task了,簡化了操做。
至於uploadTaskWithRequest:和downloadTaskWithRequest:等方法實現上本質和dataTaskWithRequest:並無多大區別,這裏對於相同的地方就不贅述了,主要提幾點不一樣的地方,而這幾點不一樣的地方根本在於系統提供了不一樣session task生成方法:
1. 系統提供的uploadTask構建方法:
對應AFNetworking中的uploadTaskWithRequest:fromFile:progress:completionHandler:方法,關於這個方法,裏面使用到了attemptsToRecreateUploadTasksForBackgroundSessions變量,這個是用於建立後臺task時使用的。由於在iOS7中,有時候建立後臺task會失敗,Apple建議若是建立失敗了,就從新嘗試建立。此處嘗試的次數最大爲AFMaximumNumberOfAttemptsToRecreateBackgroundSessionUploadTask,默認爲3。至於其中使用到的addDelegateForUploadTask:地實現基本同addDelegateForDataTask:實現。
詳見源碼。
對應AFNetworking中的uploadTaskWithRequest:fromData:progress:completionHandler:方法。
詳見源碼。
對應AFNetworking中的uploadTaskWithRequest:fromData:progress:completionHandler:方法。
詳見源碼。
2. 系統提供的downloadTask構建方法:
對應AFNetworking中的downloadTaskWithRequest:progress:destination:completionHandler:方法,注意此處多了一個destination。destination是一個block:
(nullable NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
該block表示下載後的文件最後如何放置,返回的是一個NSURL*變量。具體使用請看addDelegateForDownloadTask:
- (void)addDelegateForDownloadTask:(NSURLSessionDownloadTask *)downloadTask progress:(void (^)(NSProgress *downloadProgress)) downloadProgressBlock destination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler { // ...... if (destination) { // 會調用setDownloadTaskDidFinishDownloadingBlock:方法,生成最終下載文件放置位置 delegate.downloadTaskDidFinishDownloading = ^NSURL * (NSURLSession * __unused session, NSURLSessionDownloadTask *task, NSURL *location) { return destination(location, task.response); }; } // ...... }
對應AFNetworking中的downloadTaskWithResumeData:progress:destination:completionHandler:方法。
這個類在#issues 1477上reopen了屢次,討論仍是很激烈的。討論的起由是app會莫名crash,主要緣由是AFNetworking對NSURLSessionTask中的state進行了KVO操做。一開始人們removeObserver這個state,可是會形成AFNetworkActivityIndicatorManager功能(其中會觀察state)削弱。另外後來iOS8上也出現了一樣crash現象,貌似iOS7和iOS8在NSURLSessionTask有些不一樣。最後仍是有個大神用swizzling方法才解決了這個問題。
還記得【原】AFNetworking源碼閱讀(三)中咱們提到了若是想使用AFNetworkingTaskDidResumeNotification來通知各類UI控件當前網絡任務狀態爲resume,那麼就得調用taskDidResume:函數,而想要調用taskDidResume:函數就得調用af_resume函數。以前咱們提到過,af_resume和系統的resume進行了method swizzling。因此調用af_resume其實就是調用resume。
不過你有沒發現除了後面Test中的方法出現了_AFURLSessionTaskSwizzling,其餘地方都沒出現該類的使用,那method swizzling是在哪初始化的的呢,換句話說,af_resume和resume是在哪調換的?這個問題我想了很久,最後才明白,都是本身學藝不精啊。下面補充一個知識點:
知識點:load的調用時機
load方法會在加載類的時候就被調用,也就是iOS應用啓動的時候就會加載全部的類,就會調用每一個類的+load方法。
而咱們的_AFURLSessionTaskSwizzling重寫了load方法,而且在其中調用了swizzleResumeAndSuspendMethodForClass:來進行method swizzling。下面咱們先看看swizzleResumeAndSuspendMethodForClass:這個方法:
+ (void)swizzleResumeAndSuspendMethodForClass:(Class)theClass { // 由於af_resume和af_suspend都是類的實例方法,因此使用class_getInstanceMethod獲取這兩個方法 Method afResumeMethod = class_getInstanceMethod(self, @selector(af_resume)); Method afSuspendMethod = class_getInstanceMethod(self, @selector(af_suspend)); // 給theClass添加一個名爲af_resume的方法,使用@selector(af_resume)獲取方法名,使用afResumeMethod做爲方法實現 if (af_addMethod(theClass, @selector(af_resume), afResumeMethod)) { // 交換resume和af_resume的方法實現 af_swizzleSelector(theClass, @selector(resume), @selector(af_resume)); } // 同上 if (af_addMethod(theClass, @selector(af_suspend), afSuspendMethod)) { af_swizzleSelector(theClass, @selector(suspend), @selector(af_suspend)); } }
上述方法調用了大量私有的方法,下面一一解釋:
// 根據兩個方法名稱交換兩個方法,內部實現是先根據函數名獲取到對應方法實現 // 再調用method_exchangeImplementations交換兩個方法 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); } // 給theClass添加名爲selector,對應實現爲method的方法 static inline BOOL af_addMethod(Class theClass, SEL selector, Method method) { // 內部實現使用的是class_addMethod方法,注意method_getTypeEncoding是爲了得到該方法的參數和返回類型 return class_addMethod(theClass, selector, method_getImplementation(method), method_getTypeEncoding(method)); }
- (NSURLSessionTaskState)state { NSAssert(NO, @"State method should never be called in the actual dummy class"); // 初始狀態是NSURLSessionTaskStateCanceling; return NSURLSessionTaskStateCanceling; } - (void)af_resume { NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state"); NSURLSessionTaskState state = [self state]; [self af_resume]; // 由於通過method swizzling後,此處的af_resume其實就是以前的resume,因此此處調用af_resume就是調用系統的resume。可是在程序中咱們仍是得使用resume,由於其實際調用的是af_resume // 若是以前是其餘狀態,就變回resume狀態,此處會通知調用taskDidResume if (state != NSURLSessionTaskStateRunning) { [[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidResumeNotification object:self]; } } // 同上 - (void)af_suspend { NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state"); NSURLSessionTaskState state = [self state]; [self af_suspend]; if (state != NSURLSessionTaskStateSuspended) { [[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidSuspendNotification object:self]; } }
解釋完上面的函數後,最終回到咱們的load函數:
+ (void)load { /** WARNING: 高能預警 https://github.com/AFNetworking/AFNetworking/pull/2702 */ // 擔憂之後iOS中不存在NSURLSessionTask if (NSClassFromString(@"NSURLSessionTask")) { /** iOS 7和iOS 8在NSURLSessionTask實現上有些許不一樣,這使得下面的代碼實現略顯trick 關於這個問題,你們作了不少Unit Test,足以證實這個方法是可行的 目前咱們所知的: - NSURLSessionTasks是一組class的統稱,若是你僅僅使用提供的API來獲取NSURLSessionTask的class,並不必定返回的是你想要的那個(獲取NSURLSessionTask的class目的是爲了獲取其resume方法) - 簡單地使用[NSURLSessionTask class]並不起做用。你須要新建一個NSURLSession,並根據建立的session再構建出一個NSURLSessionTask對象才行。 - iOS 7上,localDataTask(下面代碼構造出的NSURLSessionDataTask類型的變量,爲了獲取對應Class)的類型是 __NSCFLocalDataTask,__NSCFLocalDataTask繼承自__NSCFLocalSessionTask,__NSCFLocalSessionTask繼承自__NSCFURLSessionTask。 - iOS 8上,localDataTask的類型爲__NSCFLocalDataTask,__NSCFLocalDataTask繼承自__NSCFLocalSessionTask,__NSCFLocalSessionTask繼承自NSURLSessionTask - iOS 7上,__NSCFLocalSessionTask和__NSCFURLSessionTask是僅有的兩個實現了resume和suspend方法的類,另外__NSCFLocalSessionTask中的resume和suspend並無調用其父類(即__NSCFURLSessionTask)方法,這也意味着兩個類的方法都須要進行method swizzling。 - iOS 8上,NSURLSessionTask是惟一實現了resume和suspend方法的類。這也意味着其是惟一須要進行method swizzling的類 - 由於NSURLSessionTask並非在每一個iOS版本中都存在,因此把這些放在此處(即load函數中),好比給一個dummy class添加swizzled方法都會變得很方便,管理起來也方便。 一些假設前提: - 目前iOS中resume和suspend的方法實現中並無調用對應的父類方法。若是往後iOS改變了這種作法,咱們還須要從新處理 - 沒有哪一個後臺task會重寫resume和suspend函數 */ // 1) 首先構建一個NSURLSession對象session,再經過session構建出一個_NSCFLocalDataTask變量 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 // 2) 獲取到af_resume實現的指針 IMP originalAFResumeIMP = method_getImplementation(class_getInstanceMethod([self class], @selector(af_resume))); Class currentClass = [localDataTask class]; // 3) 檢查當前class是否實現了resume。若是實現了,繼續第4步。 while (class_getInstanceMethod(currentClass, @selector(resume))) { // 4) 獲取到當前class的父類(superClass) Class superClass = [currentClass superclass]; // 5) 獲取到當前class對於resume實現的指針 IMP classResumeIMP = method_getImplementation(class_getInstanceMethod(currentClass, @selector(resume))); // 6) 獲取到父類對於resume實現的指針 IMP superclassResumeIMP = method_getImplementation(class_getInstanceMethod(superClass, @selector(resume))); // 7) 若是當前class對於resume的實現和父類不同(相似iOS7上的狀況),而且當前class的resume實現和af_resume不同,才進行method swizzling。 if (classResumeIMP != superclassResumeIMP && originalAFResumeIMP != classResumeIMP) { [self swizzleResumeAndSuspendMethodForClass:currentClass]; } // 8) 設置當前操做的class爲其父類class,重複步驟3~8 currentClass = [currentClass superclass]; } [localDataTask cancel]; [session finishTasksAndInvalidate]; } }
關於NSSecureCoding的講解請參考使用NSSecureCoding協議進行編解碼。
由於要支持secure coding,因此要在supportsSecureCoding返回YES。
AFURLSessionManager保存的信息是其NSURLSessionConfiguration變量,而後根據獲取到的configuration構建出AFURLSessionManager對象,節省了存儲空間。
+ (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; }
沒啥好說的,就是先構建一個AFURLSessionManager空間,並使用原先session的configuration來初始化空間內容。
- (instancetype)copyWithZone:(NSZone *)zone { return [[[self class] allocWithZone:zone] initWithSessionConfiguration:self.session.configuration]; }
講到這,基本上AFURLSessionManager這個文件的內容已經東一點西一點講完了。下面,咱們再來跳到AFHTTPSessionManager這個文件中,看看還有哪些內容沒有講完。
這個帶constructingBody的POST方法主要是爲了解決Multipart協議的問題。
知識點:Multipart協議介紹 —— 詳見HTTP協議之multipart/form-data請求分析,或者你看這篇文章https://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.2(必定要先看完)
這裏簡述一下:Multipart是HTTP協議爲web表單新增的上傳文件的協議,協議文檔是rfc1867,它基於HTTP的POST方法,數據一樣是放在body上,跟普通POST方法的區別是數據不是key=value形式,key=value形式難以表示文件實體,爲此Multipart協議添加了分隔符(即boundary的概念),有本身的格式結構,舉個例子:
--${bound} // 該bound表示pdf的文件名 Content-Disposition: form-data; name="Filename" HTTP.pdf
--${bound} // 該bound表示pdf的文件內容 Content-Disposition: form-data; name="file000"; filename="HTTP協議詳解.pdf" Content-Type: application/octet-stream %PDF-1.5 file content %%EOF
--${bound} // 該bound表示字符串 Content-Disposition: form-data; name="Upload" Submit Query
--${bound}—// 表示body結束了
舉例:好比上面那個例子,咱們若是想使用multipart形式調用,應該使用怎樣的調用方法?
先說結論:
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; [manager POST:@"postURLString" parameters:@{@"Filename":@"HTTP.pdf"} constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) { [formData appendPartWithFileData:[pdf文件具體內容(NSData *)] name:@"file000" fileName:@"HTTP協議詳解.pdf" mimeType:@"application/octet-stream"]; [formData appendPartWithFormData:[@"Submit Query" dataUsingEncoding:NSUTF8StringEncoding] name:@"Upload"]; } progress:nil success:nil failure:nil];
有些函數,好比appendPartWithFileData:和appendPartWithFormData:這些函數,你們對照上面的例子,也大概能猜出來大概用途了,具體實現後面會詳解。
而此處帶constructingBodyWithBlock的POST方法與- [AFHTTPSessionManager POST:parameters:progress:success:failure:]明顯的區別在於構建request的時候,使用的是multipartFormRequestWithMethod:以及構建NSURLSessionDataTask的時候使用的是uploadTaskWithStreamedRequest:。由於uploadTaskWithStreamedRequest:函數在上面已經提到過了。這裏就主要說一下multipartFormRequestWithMethod:實現。
multipartFormRequestWithMethod:URLString:parameters:constructingBodyWithBlock:error:除了須要使用普通的request構造函數requestWithMethod:URLString:parameters:error:來構造request,還須要根據multipart獨有的屬性來修飾這個request,其中最關鍵的就是要構造http body部分。下面我挑出了其中比較關鍵的代碼進行分析:
// 使用initWithURLRequest:stringEncoding:來初始化一個AFStreamingMultipartFormData變量 // 每一個AFStreamMultipartFormData其實都是對應一個上面舉的那個例子,主要是爲了構建bodyStream __block AFStreamingMultipartFormData *formData = [[AFStreamingMultipartFormData alloc] initWithURLRequest:mutableRequest stringEncoding:NSUTF8StringEncoding]; // 處理parameters,好比上面的@"Filename":"HTTP.pdf",首先構建一個AFQueryStringPair,其中field爲"Filename",value爲"HTTP.pdf"// 而後會根據對應value的類型,構建出一個NSData變量。好比此處的value是一個NSString,因此調用 //data = [[pair.value description] dataUsingEncoding:self.stringEncoding];將NSString->NSData
if (parameters) { for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) { NSData *data = nil; if ([pair.value isKindOfClass:[NSData class]]) { data = pair.value; } else if ([pair.value isEqual:[NSNull null]]) { data = [NSData data]; } else { data = [[pair.value description] dataUsingEncoding:self.stringEncoding]; } if (data) { // bodyStream構造最主要的部分就在這了(雖而後面requestByFinalizingMultipartFormData函數還會稍微處理一下)
// 根據data和name構建Request的header和body,後面詳解 [formData appendPartWithFormData:data name:[pair.field description]]; } } } // 參考上面的例子,其實仍是往formData中添加數據 if (block) { block(formData); } // 作最終的處理,好比設置一下MultipartRequest的bodyStream或者其特有的content-type等等,後面也會詳解 return [formData requestByFinalizingMultipartFormData];
至於AFStreamMultipartFormData類,appendPartWithFormData:和requestByFinalizingMultipartFormData等等函數,我大概看了下內容仍是比較多的,準備在下一篇中介紹AFURLRequestSerialization時詳細介紹。此處咱們只須要知道這裏構建了一個Multipart Request給uploadTask構造時使用。
// 對baseURL,session.configuration,requestSerializer,responseSerializer,securityPolicy進行編碼 - (void)encodeWithCoder:(NSCoder *)coder { // AFHTTPSessionManager的父類爲AFURLSessionManager,因此先調用父類方法 [super encodeWithCoder:coder]; // 由於configuration是一個對象,因此要考慮是否實現了NSCoding [coder encodeObject:self.baseURL forKey:NSStringFromSelector(@selector(baseURL))]; if ([self.session.configuration conformsToProtocol:@protocol(NSCoding)]) { [coder encodeObject:self.session.configuration forKey:@"sessionConfiguration"]; } else { [coder encodeObject:self.session.configuration.identifier forKey:@"identifier"]; } [coder encodeObject:self.requestSerializer forKey:NSStringFromSelector(@selector(requestSerializer))]; [coder encodeObject:self.responseSerializer forKey:NSStringFromSelector(@selector(responseSerializer))]; [coder encodeObject:self.securityPolicy forKey:NSStringFromSelector(@selector(securityPolicy))]; }
對於initWithCoder:就不贅述了。
// 深拷貝,遞歸地拷貝下去 - (instancetype)copyWithZone:(NSZone *)zone { AFHTTPSessionManager *HTTPClient = [[[self class] allocWithZone:zone] initWithBaseURL:self.baseURL sessionConfiguration:self.session.configuration]; HTTPClient.requestSerializer = [self.requestSerializer copyWithZone:zone]; HTTPClient.responseSerializer = [self.responseSerializer copyWithZone:zone]; HTTPClient.securityPolicy = [self.securityPolicy copyWithZone:zone]; return HTTPClient; }
寫NSSecureCoding和NSCopying的目的,不是由於這兩個函數有什麼難度在裏面,而是爲了時刻提醒本身,還要記得這兩個協議,學會使用它們。後面除非這兩個協議有特殊處理,就不討論了。
主要仍是利用https://httpbin.org/提供的各類藉口進行測試,好比重定向使用/redirect/1測試,狀態碼返回204使用/status/204測試等等。本文不想過多介紹httpbin網站內容,你們感興趣,自行研究。另外test中有不少相似函數使用的例子能夠做爲參考,好比POST等等函數的使用方法,因此仍是值得看看的,這裏我就不費口舌了。
這一篇比較零散,主要是給AFURLSessionManager和AFHTTPSessionManager兩個文件擦屁股的。因此有些問題請結合以前的文章一塊兒來看。下面幾篇就比較單純了,好比request序列化,response序列化、安全策略和網絡狀態管理這幾個模塊能夠按獨立部分來學習。