ios AFNetworking 3.0 原碼閱讀分析 (一)(AFURLRequestSerialization模塊)

  本文主要內容是講AFNetworking中的AFURLRequestSerialization。它主要的做用是在咱們要發送一個網絡請求的時候幫助咱們建立NSMutableURLRequest並封裝好所須要的參數到NSMutableURLRequest中。那它內部作了些什麼,提供了什麼功能,使得咱們進行網絡請求時變得如此方便、簡單。好像咱們什麼都不用管就能創建一個正確的請求體NSURLRequest,咱們只須要作的是選擇一個知足本身須要的AFURLRequestSerialization便可。接下來就會一步步揭開它神祕的面紗。(ps:貼的源碼因爲一行太長,格式提難調的,能夠打開XCode對着看)php


  概覽  html

  首先看一下在AFURLRequestSerialization.h和AFURLRequestSerialization.m中所包含的類及它們間的關係,及各部分的功能。算法

AFURLRequestSerialization協議:定義了一個以下方法,各子類會根據本身須要有本身的實現json

- (nullable NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
                               withParameters:(nullable id)parameters
                                        error:(NSError * _Nullable __autoreleasing *)error

下面寫個僞代碼說明AFHTTPRequestSerializer、AFJSONRequestSerializer、AFPropertyListRequestSerializer功能,裏面囊括了AFURLRequestSerialization全部主要功能,用戶進行網絡請求時可根據自身需求選擇這三個Serializer其中之一。這個很重要!!!數組

    if (請求方式爲:get || head || delete)
    {  //場景(1) 後面講細節的場景標記
     AFHTTPRequestSerializer、AFHTTPRequestSerializer、AFHTTPRequestSerializer
     其實都調到了AFHTTPRequestSerializer的
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request withParameters:(id)parameters error:(NSError *__autoreleasing *)error
而後把parameters拼結成URL字符串參數加到URL後面做爲請求的一部分。 }
else if (multipart表單提交) {
     //場景(3) AFHTTPRequestSerializer、AFHTTPRequestSerializer、AFHTTPRequestSerializer就會
     都調用到AFHTTPRequestSerializer的
- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method URLString:(NSString *)URLString parameters:(nullable NSDictionary <NSString *, id> *)parameters constructingBodyWithBlock:(nullable void (^)(id <AFMultipartFormData> formData))block error:(NSError * _Nullable __autoreleasing *)error; 在這裏面就會用到AFStreamingMultipartFormData,此時parameters及經過AFStreamingMultipartFormData添
     加進來的數據都會變一項項的AFHTTPBodyPart存起來,當URL系統須要上傳數據時就經過AFHTTPBodyPart取數據。 }
else //即((put||post||patch) && !multipart表單提交時) {
     //場景(2)
     AFHTTPRequestSerializer:把parameters拼接成字符串參數後(如何拼接細節後面寫)
     序列化成NSData放到HTTPRequest的HTTPBody中。
AFJSONRequestSerializer:把parameters以dataWithJSONObject方式序列化
     成NSData放到HTTPRequest的HTTPBody中。
AFPropertyListRequestSerializer:把請求parameters以dataWithPropertyList
     方式序列化成NSData放到HTTPRequest的HTTPBody中。 }

 AFMultipartFormData協議:定義了一些接口方法,容許用戶能夠用不一樣的方式添加表單的內容,如:使用文件路徑、直接用NSData、或使用inputStream等。服務器

AFStreamingMultipartFormData:遵循了AFMultipartFormData協議,把協議的方法都實現了。網絡

AFMultipartBodyStream:它起着一個重要橋樑做用,上傳表單數據時系統會先調到它,而後它會依賴AFHTTPBodyPart讀到數據,而後把數據返回給URL系統。app

AFHTTPBodyPart:每個AFHTTPBodyPart就是表明一項表單數據,由它真正讀取它內部的數據(不論是以什麼形式存在的:文件路徑,NSData,又或者NSInputStream)。dom

正是由於這些類爲咱們作了這麼多事情,因此當咱們要進行網絡請求,咱們省去了對NSURLRequest請求體的構造,接下來就是實現的細節。函數


 實現細節

  在講細節時,會按照上部分僞代碼中標的序號寫,而後在各部分流程中補充相應其它細節實現。因此接下來會出現較多源碼。

場景(1):下面是相關實現源碼,能夠看到在AFJSONRequestSerializer和AFPropertyListRequestSerializer對於AFURLRequestSerialization協議的實現都有相同的地方,以下:當請求方式爲請求方式爲:get || head || delete時調用了super而且return了,即調到了AFHTTPRequestSerializer的實現中。

- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
                               withParameters:(id)parameters
                                        error:(NSError *__autoreleasing *)error
{
    NSParameterAssert(request);

    if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {//get || head || delete return [super requestBySerializingRequest:request withParameters:parameters error:error];
    }

    ... ...
    return mutableRequest;
}

那麼咱們接下來重點關注AFHTTPRequestSerializer的實現,關鍵的地方已經加上相關注釋

#pragma mark - AFURLRequestSerialization
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
                               withParameters:(id)parameters
                                        error:(NSError *__autoreleasing *)error
{
    NSParameterAssert(request);

    NSMutableURLRequest *mutableRequest = [request mutableCopy];

    //設置http headers
    [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
        if (![request valueForHTTPHeaderField:field]) {
            [mutableRequest setValue:value forHTTPHeaderField:field];
        }
    }];

    NSString *query = nil;
    //參數不爲空
    if (parameters) {
        //用戶有自定義格式化參數的回調,自定義優先
        if (self.queryStringSerialization) {
            NSError *serializationError;
            query = self.queryStringSerialization(request, parameters, &serializationError);

            if (serializationError) {
                if (error) {
                    *error = serializationError;
                }

                return nil;
            }
        } else {
        //用戶沒有自定義格式化參數則走這裏
            switch (self.queryStringSerializationStyle) {
                //目前只有AFHTTPRequestQueryStringDefaultStyle
                case AFHTTPRequestQueryStringDefaultStyle:
                    query = AFQueryStringFromParameters(parameters); break;
            }
        }
    }

    if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
        if (query && query.length > 0) {
       //把格式化好的參數添加到URL尾部
//This property contains the query string. Any percent-encoded characters are not unescaped. If the receiver does not conform to RFC 1808, this property contains nil. For example, in the URL http://www.example.com/index.php?key1=value1&key2=value2, the query string is key1=value1&key2=value2. mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]]; } } else { // #2864: an empty string is a valid x-www-form-urlencoded payload if (!query) { query = @""; } if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) { [mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"]; } [mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]]; } return mutableRequest; }

能夠看到,AFQueryStringFromParameters個函數我是加粗了的。先講它的做用:把參數拼接成相似於key1=value1&key2=value2這樣的一個字符串。源碼以下:

NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
    NSMutableArray *mutablePairs = [NSMutableArray array];
    for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
        [mutablePairs addObject:[pair URLEncodedStringValue]];
    }
    return [mutablePairs componentsJoinedByString:@"&"];
}
AFQueryStringPair類比較簡單,整個類的定義及實現以下,就是包括了一個field和value,並提供一個方法URLEncodedStringValue用於返回拼接好後的key=value字符串。
@interface AFQueryStringPair : NSObject
@property (readwrite, nonatomic, strong) id field;
@property (readwrite, nonatomic, strong) id value;

- (instancetype)initWithField:(id)field value:(id)value;

- (NSString *)URLEncodedStringValue;
@end

@implementation AFQueryStringPair

- (instancetype)initWithField:(id)field value:(id)value {
    self = [super init];
    if (!self) {
        return nil;
    }

    self.field = field;
    self.value = value;

    return self;
}

- (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])];
    }
}
咱們能夠看到又多了個新的C函數AFPercentEscapedStringFromString,這個函數的做用是去除非法字符而且對特殊字符進行編碼。這個可自行google學習URL規範相關知識,或點這裏
接下來咱們看回
AFQueryStringPairsFromDictionary,這裏可能會有疑惑,傳進來的不是已是dictionary了嗎?爲何還要轉換成AFQueryStringPair呢?實現源碼就不貼了,
讀者能夠自行對源碼看,咱們想一下,傳進來的dictionary若是是個多層及的dictionary,即value又是個dictionary或者是array那是又怎麼樣拼接參數呢。這正是AFQueryStringPairsFromDictionary要解決的問題。下面有具體例子,對着例子看源碼會好理解一點。
//AFQueryStringPairsFromDictionary它的做用:把多層級的參數扁平化成爲一個數組,每一個數組的元素是AFQueryStringPair
//舉些例子就能明白
//@{key1:value1, key2:value2}  結果就是 [{key1:value1},{key2:vaule2}];
//@{key:{key1:vaule}} 結果就是 [{key[key1]:vaule}]
//@{key:[value]} 結果就是 [{key[]:value}]

 到此,對於場景(1)的細節都已經介紹完畢。

場景(2):這一場景是最簡單的,把數據接對應方法序列化後,填到HTTPBody中便可

 

//對於AFHTTPRequestSerializer,會以application/x-www-form-urlencoded方式提交通過拼接後的query數據
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
       [mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
 }
[mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
    
//對於AFJSONRequestSerializer,使用NSJSONSerialization直接序列化parameters
if (parameters) {
        if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
            [mutableRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
        }

        [mutableRequest setHTTPBody:[NSJSONSerialization dataWithJSONObject:parameters options:self.writingOptions error:error]];
    }
//對於AFPropertyListRequestSerializer,使用AFPropertyListRequestSerializer序列化parameters
if (parameters) { if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) { [mutableRequest setValue:@"application/x-plist" forHTTPHeaderField:@"Content-Type"]; } [mutableRequest setHTTPBody:[NSPropertyListSerialization dataWithPropertyList:parameters format:self.format options:self.writeOptions error:error]]; }

這種場景下,只是填好對應的Content-Type使用對應的方式序列化數據,把數據塞到httpbody中,而後提交給服務器便可。

  

 場景(3):這個場景是最爲複雜的,在介紹這部份以前要先了解一下基本知識。對於multipart表單提交,請求會是以下這樣的:

請求header的Content-Type必需是這樣的Content-Type: multipart/form-data; boundary=${bound}。${bound}是一個佔位符,它能夠是任意字符串。它是表單中每一part的分隔符。每一部分的內容都以--${bound}做爲開始並以\r\n結束,而且每一部分的內容都有它本身的header信息。最後會以--${bound}--做爲全部內容都結束的標記。下面就是一個multipart表單提交的格式。

--${bound}  
Content-Disposition: form-data; name="name"  
HTTP.pdf  
--${bound}  
Content-Disposition: form-data; name="name2"; filename="xxx" 
data
--${bound} Content-Disposition: form-data; name="uploaddata" uploaddata --${bound}--

對應於AFNetworking,${bound}就是

static NSString * AFCreateMultipartFormBoundary() {
    return [NSString stringWithFormat:@"Boundary+%08X%08X", arc4random(), arc4random()];
}

這個boundary越複雜越好,避免出現重複。

OK,有了以上基礎知識,能夠繼續往下進行分析。與前兩個場景不同的是,對於multipart表單的請求,首先咱們看它的對外接口:

- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method
                                              URLString:(NSString *)URLString
                                             parameters:(NSDictionary *)parameters
                              constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block
                                                  error:(NSError *__autoreleasing *)error
{
    NSParameterAssert(method);
    NSParameterAssert(![method isEqualToString:@"GET"] && ![method isEqualToString:@"HEAD"]);
    NSMutableURLRequest *mutableRequest = [self requestWithMethod:method URLString:URLString parameters:nil error:error];
    __block AFStreamingMultipartFormData *formData = [[AFStreamingMultipartFormData alloc] initWithURLRequest:mutableRequest stringEncoding:NSUTF8StringEncoding];
    if (parameters) {
        for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
          ... ...

       if (data) { //把param數據加到AFMultipartBodyStream中 [formData appendPartWithFormData:data name:[pair.field description]]; } } } //經過block回調,給外部構造表單的每個part,而且也會被添加到AFMultipartBodyStream中,具體看AFStreamingMultipartFormData的全部append前綴方法 if (block) { block(formData); } return [formData requestByFinalizingMultipartFormData]; }

這裏看加粗的requestByFinalizingMultipartFormData方法

- (NSMutableURLRequest *)requestByFinalizingMultipartFormData {
    if ([self.bodyStream isEmpty]) {
        return self.request;
    }
    
    // Reset the initial and final boundaries to ensure correct Content-Length
    //給第一part和最後一part數據加上標記,後面算法加boundary是會依賴這兩個標記
    [self.bodyStream setInitialAndFinalBoundaries];
    [self.request setHTTPBodyStream:self.bodyStream]; //設置Content-Type如剛基本知識所說的。
    [self.request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", self.boundary] forHTTPHeaderField:@"Content-Type"];
    //設置Content總長度
    [self.request setValue:[NSString stringWithFormat:@"%llu", [self.bodyStream contentLength]] forHTTPHeaderField:@"Content-Length"];

    return self.request;
}

繼續這句 [self.request setHTTPBodyStream:self.bodyStream],這句是關鍵代碼,當URL系統須要開始、中止、獲取數據時,就會調用到被設置的bodyStream的如下幾個方法:

- (void)open;
- (void)close;
- (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)length; //其中這個是最鍵的,這就是一開始說的重要橋樑的接口,URL系統讀取數據時就會調用到它。

 而後AFMultipartBodyStream中的read:maxLength:方法內部實現際又須要它的每一個AFHTTPBodyPart的read:maxLength:方法讀取正的數據。正如前面所說,每個AFHTTPBodyPart都會以--${bound}做爲開始並以\r\n結束,AFNetworking做者就使用了一個狀態機的模式去讀取一個AFHTTPBodyPart的內容。總共會有四狀態

typedef enum {
    AFEncapsulationBoundaryPhase = 1,
    AFHeaderPhase                = 2,
    AFBodyPhase                  = 3,
    AFFinalBoundaryPhase         = 4,
} AFHTTPBodyPartReadPhase;

爲了比較直觀的理解它這個狀態機是怎麼樣驅動的,接下來先看了流程圖,再看源碼就會好理解一點。

下面就是它的源碼,每次循環最多隻能讀取到AFHTTPBodyPart的一個狀態內的內容,就是靠着AFMultipartBodyStream中的while循環去驅動AFHTTPBodyPart是狀態機去讀取內容。
//AFMultipartBodyStream的read:maxLength:
//
由系統的回調調到AFMultipartBodyStream的read,取數據實際再從AFHTTPBodyPart去拿,AFMultipartBodyStream做用只中間橋樑 - (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)length { if ([self streamStatus] == NSStreamStatusClosed) { return 0; } NSInteger totalNumberOfBytesRead = 0; while ((NSUInteger)totalNumberOfBytesRead < MIN(length, self.numberOfBytesInPacket)) { //判斷讀滿了length沒有 if (!self.currentHTTPBodyPart || ![self.currentHTTPBodyPart hasBytesAvailable]) { //當前的part爲空或不可讀取數 if (!(self.currentHTTPBodyPart = [self.HTTPBodyPartEnumerator nextObject])) { //已經讀到最後一部份就退出了 break; } } else { //可讀,計算還能夠讀取到buf中的最大長度maxLength,和buf的什麼位置開始寫進去即&buffer[totalNumberOfBytesRead] NSUInteger maxLength = MIN(length, self.numberOfBytesInPacket) - (NSUInteger)totalNumberOfBytesRead; //從當前可讀的HTTPBodyPart讀取數據 NSInteger numberOfBytesRead = [self.currentHTTPBodyPart read:&buffer[totalNumberOfBytesRead] maxLength:maxLength]; //-1表示讀取出錯了 if (numberOfBytesRead == -1) { self.streamError = self.currentHTTPBodyPart.inputStream.streamError; break; } else { //讀取成功後當前已經讀的要加上實際讀的長度numberOfBytesRead,而後再回到循環判斷讀滿了length沒有 totalNumberOfBytesRead += numberOfBytesRead; if (self.delay > 0.0f) { [NSThread sleepForTimeInterval:self.delay]; } } } } return totalNumberOfBytesRead; }

//AFHTTPBodyPart的read:length:
- (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)length { NSInteger totalNumberOfBytesRead = 0; //若是是本身是第一個AFHTTPBodyPart讀取狀態是讀取AFMultipartFormInitialBoundary,不然AFMultipartFormEncapsulationBoundary if (_phase == AFEncapsulationBoundaryPhase) { NSData *encapsulationBoundaryData = [([self hasInitialBoundary] ? AFMultipartFormInitialBoundary(self.boundary) : AFMultipartFormEncapsulationBoundary(self.boundary)) dataUsingEncoding:self.stringEncoding]; totalNumberOfBytesRead += [self readData:encapsulationBoundaryData intoBuffer:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)]; } //讀header信息 if (_phase == AFHeaderPhase) { NSData *headersData = [[self stringForHeaders] dataUsingEncoding:self.stringEncoding]; totalNumberOfBytesRead += [self readData:headersData intoBuffer:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)]; } //讀真正內容 if (_phase == AFBodyPhase) { NSInteger numberOfBytesRead = 0; numberOfBytesRead = [self.inputStream read:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)]; if (numberOfBytesRead == -1) { return -1; } else { totalNumberOfBytesRead += numberOfBytesRead; if ([self.inputStream streamStatus] >= NSStreamStatusAtEnd) { [self transitionToNextPhase]; } } } //讀結束,若是本身是最後一個AFHTTPBodyPart則讀取AFMultipartFormFinalBoundary,不然創建個空的NSData去讀,至關於沒讀任何東西 if (_phase == AFFinalBoundaryPhase) { NSData *closingBoundaryData = ([self hasFinalBoundary] ? [AFMultipartFormFinalBoundary(self.boundary) dataUsingEncoding:self.stringEncoding] : [NSData data]); totalNumberOfBytesRead += [self readData:closingBoundaryData intoBuffer:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)]; } return totalNumberOfBytesRead; }

 能夠看到我有兩行分加粗的,裏面實現就不貼了,比較簡單,說明一下transitionToNextPhase就是切換AFHTTPBodyPart到下一狀態,除讀取真正數據外其它狀態要切換到下一狀態都是在加粗的接口readData:intoBuffer:maxLength:中調用的。multipart表單的實現原理已經分析完畢。


 

   到這裏整個AFURLRequestSerialization主要功能內容已經分析完畢。下一篇將會分析。。。

相關文章
相關標籤/搜索