【原】AFNetworking源碼閱讀(四)

【原】AFNetworking源碼閱讀(四)

本文轉載請註明出處 —— polobymulberry-博客園html

1. 前言


上一篇還遺留了不少問題,包括AFURLSessionManagerTaskDelegate類所實現的NSURLSession相關的代理方法,甚至連dataTask、uploadTask、downloadTask這幾個基本概念也沒說。這一篇就是爲了集中消滅這些遺留問題。ios

2. AFURLSessionManagerTaskDelegate的代理方法


此處實現的仍然是NSURLSession相關的代理方法,由於上一篇中已經詳細介紹過了,因此對應的相關方法介紹就不贅述,直接介紹方法實現。git

2.1 NSURLSessionTaskDelegate

2.1.1 - URLSession:task:didCompleteWithError:

該函數在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
}

2.2 NSURLSessionDataDelegate

2.2.1 - URLSession:dataTask:didReceiveData:

該函數在AFURLSessionManager中的- URLSession:dataTask:didReceiveData:被調用安全

函數實現:網絡

- (void)URLSession:(__unused NSURLSession *)session
          dataTask:(__unused NSURLSessionDataTask *)dataTask
    didReceiveData:(NSData *)data
{
    // 將每次得到的新數據附在mutableData上,來組成最終得到的全部數據
    [self.mutableData appendData:data];
}

2.3 NSURLSessionDownloadDelegate

2.3.1 - URLSession:downloadTask:didFinishDownloadingToURL:(必須實現)

該函數在AFURLSessionManager中的- URLSession:downloadTask:didFinishDownloadingToURL:被調用
函數實現:session

和AFURLSessionManager中的實現相似,這裏就不贅述了。app

3. 進一步討論session task


首先簡單介紹下session task,如下語句引用自從 NSURLConnection 到 NSURLSessionasync


NSURLsessionTask 是一個抽象類,其下有 3 個實體子類能夠直接使用:NSURLSessionDataTaskNSURLSessionUploadTaskNSURLSessionDownloadTask。這 3 個子類封裝了現代程序三個最基本的網絡任務:獲取數據,好比 JSON 或者 XML,上傳文件和下載文件。

NSURLSessionTask class diagram

當一個 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構建方法:

  • uploadTaskWithRequest:fromFile: 根據fileURL建立request

對應AFNetworking中的uploadTaskWithRequest:fromFile:progress:completionHandler:方法,關於這個方法,裏面使用到了attemptsToRecreateUploadTasksForBackgroundSessions變量,這個是用於建立後臺task時使用的。由於在iOS7中,有時候建立後臺task會失敗,Apple建議若是建立失敗了,就從新嘗試建立。此處嘗試的次數最大爲AFMaximumNumberOfAttemptsToRecreateBackgroundSessionUploadTask,默認爲3。至於其中使用到的addDelegateForUploadTask:地實現基本同addDelegateForDataTask:實現。

詳見源碼。

  • uploadTaskWithRequest:fromData: 根據須要上傳的NSData建立request

對應AFNetworking中的uploadTaskWithRequest:fromData:progress:completionHandler:方法。

詳見源碼。

  • uploadTaskWithStreamedRequest: 使用該函數必需要實現URLSession:task:needNewBodyStream:來給上傳任務提供數據

對應AFNetworking中的uploadTaskWithRequest:fromData:progress:completionHandler:方法。

詳見源碼。

2. 系統提供的downloadTask構建方法:

  • downloadTaskWithRequest: 不贅述

對應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);
        };
    }
    // ......
}
  • downloadTaskWithResumeData: 用於斷點續傳,resumeData就是上一篇文章中提到的用於提供斷點續傳的信息。

對應AFNetworking中的downloadTaskWithResumeData:progress:destination:completionHandler:方法。

4. _AFURLSessionTaskSwizzling


這個類在#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];
    }
}

5. AFURLSessionManager剩餘部分-NSSecureCoding和NSCopying


5.1 NSSecureCoding

關於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;
}

5.2 NSCopying

沒啥好說的,就是先構建一個AFURLSessionManager空間,並使用原先session的configuration來初始化空間內容。

- (instancetype)copyWithZone:(NSZone *)zone {
    return [[[self class] allocWithZone:zone] initWithSessionConfiguration:self.session.configuration];
}

講到這,基本上AFURLSessionManager這個文件的內容已經東一點西一點講完了。下面,咱們再來跳到AFHTTPSessionManager這個文件中,看看還有哪些內容沒有講完。

6. AFHTTPSessionManager剩餘部分


6.1 - [AFHTTPSessionManager POST:parameters:constructingBodyWithBlock:progress:success:failure:]

這個帶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構造時使用。

6.2 NSSecureCoding

// 對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:就不贅述了。

6.3 NSCopying

// 深拷貝,遞歸地拷貝下去
- (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的目的,不是由於這兩個函數有什麼難度在裏面,而是爲了時刻提醒本身,還要記得這兩個協議,學會使用它們。後面除非這兩個協議有特殊處理,就不討論了。

7. AFURLSessionManagerTests和AFHTTPSessionManagerTests


主要仍是利用https://httpbin.org/提供的各類藉口進行測試,好比重定向使用/redirect/1測試,狀態碼返回204使用/status/204測試等等。本文不想過多介紹httpbin網站內容,你們感興趣,自行研究。另外test中有不少相似函數使用的例子能夠做爲參考,好比POST等等函數的使用方法,因此仍是值得看看的,這裏我就不費口舌了。

8. 總結


這一篇比較零散,主要是給AFURLSessionManager和AFHTTPSessionManager兩個文件擦屁股的。因此有些問題請結合以前的文章一塊兒來看。下面幾篇就比較單純了,好比request序列化,response序列化、安全策略和網絡狀態管理這幾個模塊能夠按獨立部分來學習。

9. 參考文章


相關文章
相關標籤/搜索