AFNeiworking 3.0源碼分析 - AFURLRequestSerialization

AFURLRequestSerialization模塊主要做的兩樣事情:
1.創(chuàng)建普通NSMutableURLRequest請求對象
2.創(chuàng)建multipart NSMutableURLRequest請求對象
此外還有比如:
處理查詢的 URL 參數(shù)

AFURLRequestSerialization是一個(gè)協(xié)議,它定義了一個(gè)方法:

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

AFHTTPRequestSerializer及其子類遵循這個(gè)協(xié)議。

現(xiàn)在從AFHTTPRequestSerializer這個(gè)類入手分析。

1.創(chuàng)建普通NSMutableURLRequest請求

//創(chuàng)建一般的NSMutableURLRequest對象,設(shè)置HTTPMethod、請求屬性、HTTPHeader和處理參數(shù)
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
                                 URLString:(NSString *)URLString
                                parameters:(id)parameters
                                     error:(NSError *__autoreleasing *)error
{
    NSParameterAssert(method);
    NSParameterAssert(URLString);

    NSURL *url = [NSURL URLWithString:URLString];

    NSParameterAssert(url);
    //創(chuàng)建URLRequest、設(shè)置請求的方法
    NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
    mutableRequest.HTTPMethod = method;
    //通過mutableObservedChangedKeyPaths設(shè)置NSMutableURLRequest請求屬性
    for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
        if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
            //用KVC的方式,給request設(shè)置屬性值
            [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
        }
    }
    //設(shè)置http header和參數(shù)(拼接到url還是放到http body中)
    mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];

    return mutableRequest;
}

這個(gè)方法做了三件事:
1.設(shè)置request請求類型mutableRequest.HTTPMethod = method;

2.設(shè)置request的一些屬性。
2.1這里用到了AFHTTPRequestSerializerObservedKeyPaths()c函數(shù)。

//單例。觀察者keyPath集合。需要觀察的request屬性:allowsCellularAccess、cachePolicy、HTTPShouldHandleCookies、HTTPShouldUsePipelining、networkServiceType、timeoutInterval
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;
}

這個(gè)函數(shù)創(chuàng)建了一個(gè)數(shù)組單例,里面裝的都是NSURLRequest的屬性。
2.2mutableObservedChangedKeyPaths是AFHTTPRequestSerializer類的一個(gè)屬性,它在-init方法中進(jìn)行了初始化。另外在-init方法中還對上面設(shè)置的6個(gè)與NSURLRequest相關(guān)的屬性添加觀察者(KVO):

    self.mutableObservedChangedKeyPaths = [NSMutableSet set];
    for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
        if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
            //為請求的屬性添加觀察者
            /*
             observer: 觀察者對象. 其必須實(shí)現(xiàn)方法observeValueForKeyPath:ofObject:change:context:.
             keyPath: 被觀察的屬性,其不能為nil.
             options: 設(shè)定通知觀察者時(shí)傳遞的屬性值,新值、舊值,通常設(shè)置為NSKeyValueObservingOptionNew。
             context: 一些其他的需要傳遞給觀察者的上下文信息,通常設(shè)置為nil
             */
            [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
        }
    }

KVO觸發(fā)的方法,mutableObservedChangedKeyPaths用于記錄這些屬性的變化(由我們自己設(shè)置request的屬性值):

//觀察者接收通知,通過實(shí)現(xiàn)下面的方法,完成對屬性改變的響應(yīng)。將新的屬性存儲在一個(gè)名為 mutableObservedChangedKeyPaths的集合中
//change: 屬性值,根據(jù)- addObserver: forKeyPath: options: context:的Options設(shè)置,給出對應(yīng)的屬性值
- (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];
        }
    }
}

這些被監(jiān)聽的屬性值改變時(shí)是這樣通知他們的觀察者對象的:

/*
 willChangeValueForKey通知觀察到的對象,給定屬性的值即將更改。在手動(dòng)實(shí)現(xiàn)KVO時(shí),使用此方法通知觀察對象,鍵值即將更改。
 值更改后,必須使用相同的參數(shù)調(diào)用相應(yīng)的didChangeValueForKey:
 */
- (void)setAllowsCellularAccess:(BOOL)allowsCellularAccess {
    [self willChangeValueForKey:NSStringFromSelector(@selector(allowsCellularAccess))];
    _allowsCellularAccess = allowsCellularAccess;
    [self didChangeValueForKey:NSStringFromSelector(@selector(allowsCellularAccess))];
}

2.3最后用KVC給request設(shè)置這些屬性值。
[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];

3.對網(wǎng)絡(luò)請求參數(shù)進(jìn)行編碼

- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
                               withParameters:(id)parameters
                                        error:(NSError *__autoreleasing *)error
{
    NSParameterAssert(request);
    NSMutableURLRequest *mutableRequest = [request mutableCopy];
    //設(shè)置請求頭 不會(huì)覆蓋原有的header
    [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
        if (![request valueForHTTPHeaderField:field]) {
            [mutableRequest setValue:value forHTTPHeaderField:field];
        }
    }];

    NSString *query = nil;//格式化的請求參數(shù)
    if (parameters) {
        //如果有自定義block
        if (self.queryStringSerialization) {
            NSError *serializationError;
            //用自定義block來格式化請求參數(shù)
            query = self.queryStringSerialization(request, parameters, &serializationError);

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

                return nil;
            }
        } else {
            switch (self.queryStringSerializationStyle) {
                case AFHTTPRequestQueryStringDefaultStyle:
                    //調(diào)用 AFQueryStringFromParameters 將參數(shù)轉(zhuǎn)換為查詢參數(shù)
                    query = AFQueryStringFromParameters(parameters);
                    break;
            }
        }
    }
    //將參數(shù) parameters 添加到 URL 或者 HTTP body 中
    //GET HEAD DELETE,參數(shù)拼接到url
    if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
        if (query && query.length > 0) {
            mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];//根據(jù)是否已有查詢字符串進(jìn)行拼接?已有就用‘&’,沒有就用‘?’
        }
    }
    //參數(shù)添加到httpbody中 ,比如POST PUT
    else {
        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;
}

3.1設(shè)置請求頭。從self.HTTPRequestHeaders中拿到header,賦值到請求的request中去,如果原先的header已經(jīng)存在就不進(jìn)行設(shè)置。
3.2對網(wǎng)絡(luò)請求參數(shù)進(jìn)行編碼。
如果有自定的block來格式化(轉(zhuǎn)碼)請求參數(shù)就用自定義block。

if (self.queryStringSerialization) {
            NSError *serializationError;
            //用自定義block來格式化請求參數(shù)
            query = self.queryStringSerialization(request, parameters, &serializationError);

如果沒有自定義block來處理就使用AF的轉(zhuǎn)碼方式:

//把dictionary參數(shù)轉(zhuǎn)換、拼接成字符串參數(shù)
/*
 NSDictionary *info = @{@"account":@"zhangsan",@"password":@"123456"};
AFQueryStringFromParameters(info)的結(jié)果是:account=zhangsan&password=123456 (沒有百分比編碼)
 
  NSDictionary *info = @{@"student":@{@"name":@"zhangsan",@"age":@"15"}};
 AFQueryStringFromParameters(info)的結(jié)果是:student[name]=zhangsan&student[age]=15 (沒有百分比編碼)
 */
NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
    NSMutableArray *mutablePairs = [NSMutableArray array];
    for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
        [mutablePairs addObject:[pair URLEncodedStringValue]];
    }
    //拆分?jǐn)?shù)組返回的參數(shù)字符串
    return [mutablePairs componentsJoinedByString:@"&"];
}

//網(wǎng)絡(luò)請求參數(shù)拼接處理入口。
NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) {
    return AFQueryStringPairsFromKeyAndValue(nil, dictionary);
}

//遞歸處理value。如果當(dāng)前的 value 是一個(gè)集合類型的話,那么它就會(huì)不斷地遞歸調(diào)用自己。
NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) {
    NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];
    //排序。根據(jù)需要排序的對象的description來進(jìn)行升序排列,
    //description返回的是NSString,compare:使用的是NSString的compare:方法
    NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)];

    if ([value isKindOfClass:[NSDictionary class]]) {
        NSDictionary *dictionary = value;
        // Sort dictionary keys to ensure consistent ordering in query string, which is important when deserializing potentially ambiguous sequences, such as an array of dictionaries
        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;
}

主要是根據(jù)value的類型來用AFQueryStringPairsFromKeyAndValue這個(gè)函數(shù)遞歸處理value參數(shù),直到解析的類型不是array\dictionary\set。

  • 這里涉及到一個(gè)類AFQueryStringPair
//參數(shù)轉(zhuǎn)化的中間模型
 @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;
}

 //百分號編碼后,用"="拼接field value值
 - (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])];
    }
}
@end

AFQueryStringPair這個(gè)類相當(dāng)于是一個(gè)參數(shù)轉(zhuǎn)化的中間模型,在AFQueryStringPairsFromKeyAndValue函數(shù)遞歸處理的最后:

[mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]];

就是這樣把一對field-value值保存起來。再通過-URLEncodedStringValue方法對field-value百分比編碼、"="拼接。
舉個(gè)例子理解一下這個(gè)參數(shù)格式化:

 NSDictionary *info = @{@"account":@"zhangsan",@"password":@"123456"};
AFQueryStringFromParameters(info)的結(jié)果是:account=zhangsan&password=123456 (沒有百分比編碼)
 
  NSDictionary *info = @{@"student":@{@"name":@"zhangsan",@"age":@"15"}};
 AFQueryStringFromParameters(info)的結(jié)果是:student[name]=zhangsan&student[age]=15 (沒有百分比編碼)
  • 關(guān)于參數(shù)百分比編碼:
    根據(jù)RFC 3986的規(guī)定:URL百分比編碼的保留字段分為:
    1.':' '#' '[' ']' '@' '?' '/'
    2.'!' '$' '&' ''' '(' ')' '*' '+' ',' ';' '='
    在對查詢字段百分比編碼時(shí),'?'和'/'可以不用編碼,其他的都要進(jìn)行編碼。下面這段代碼結(jié)合注釋也很好理解,就不過多展開了。
//對字符串進(jìn)行百分比編碼
NSString * AFPercentEscapedStringFromString(NSString *string) {
    //過濾需要編碼的字符
    static NSString * const kAFCharactersGeneralDelimitersToEncode = @":#[]@"; // does not include "?" or "/" due to RFC 3986 - Section 3.4
    static NSString * const kAFCharactersSubDelimitersToEncode = @"!$&'()*+,;=";
    //?和/不需要被編碼,所以除了?和/之外的字符要從URLQueryAllowedCharacterSet中剔除
    NSMutableCharacterSet * allowedCharacterSet = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy];
    [allowedCharacterSet removeCharactersInString:[kAFCharactersGeneralDelimitersToEncode stringByAppendingString:kAFCharactersSubDelimitersToEncode]];

//    為了處理類似emoji這樣的字符串,rangeOfComposedCharacterSequencesForRange 使用了while循環(huán)來處理,也就是把字符串按照batchSize分割處理完再拼回。
    static NSUInteger const batchSize = 50;
    NSUInteger index = 0;
    NSMutableString *escaped = @"".mutableCopy;

    while (index < string.length) {
        NSUInteger length = MIN(string.length - index, batchSize);
        NSRange range = NSMakeRange(index, length);

        // To avoid breaking up character sequences such as ????????
        //對emoji這類特殊字符的處理。分開一個(gè)字符串時(shí)保證我們不會(huì)分開被稱為代理對的東西。
        range = [string rangeOfComposedCharacterSequencesForRange:range];

        NSString *substring = [string substringWithRange:range];
        NSString *encoded = [substring stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];//編碼
        [escaped appendString:encoded];

        index += range.length;
    }
    return escaped;
}

3.3根據(jù)請求類型,將參數(shù)字符串添加到 URL 或者 HTTP body 中
如果是GET、HEAD、DELETE,則把請求參數(shù)拼接到url后面的。而POST、PUT是把請求參數(shù)拼接到http body。

2.創(chuàng)建multipart NSMutableURLRequest請求對象

這一部分主要是對上傳文件做的一些封裝。Multipart是HTTP協(xié)議為Web表單新增的上傳文件的協(xié)議,Content-Type的類型擴(kuò)充了multipart/form-data用以支持向服務(wù)器發(fā)送二進(jìn)制數(shù)據(jù)。它基于HTTP POST的方法,數(shù)據(jù)同樣是放在body,跟普通POST方法的區(qū)別是數(shù)據(jù)不是key=value形式。更多關(guān)于multipart/form-data請求請戳:HTTP協(xié)議之multipart/form-data請求分析

請求體HTTP Body的格式大致如下:

--boundary //上邊界 //“boundary”是一個(gè)邊界,沒有實(shí)際的意義,可以用任意字符串來替代
Content-Disposition: form-data; name=xxx; filename=xxx
Content-Type: application/octet-stream
(空一行)
文件內(nèi)容的二進(jìn)制數(shù)據(jù)
--boundary-- //下邊界

請求體內(nèi)容分為四個(gè)部分:
1.上邊界
2.頭部,告訴服務(wù)器要做數(shù)據(jù)上傳,包含:
a. 服務(wù)器的接收字段name=xxx。xxx是負(fù)責(zé)上傳文件腳本中的 字段名,開發(fā)的時(shí)候,可以咨詢后端程序員,不需要自己設(shè)定。
b. 文件在服務(wù)器中保存的名稱filename=xxx。xxx可以自己指定,不一定和本地原本的文件名相同
c. 上傳文件的數(shù)據(jù)類型 application/octet-stream
3.上傳文件的數(shù)據(jù)部分(二進(jìn)制數(shù)據(jù))
4.下邊界部分,嚴(yán)格按照字符串格式來設(shè)置.

上邊界部分和下邊界部分的字符串,最后都要轉(zhuǎn)換成二進(jìn)制數(shù)據(jù),和文件部分的二進(jìn)制數(shù)據(jù)拼接在一起,作為請求體發(fā)送給服務(wù)器.
NSURLConnection筆記-上傳文件

要構(gòu)造Multipart里的數(shù)據(jù)有三種方式:

最簡單的方式就是直接拼數(shù)據(jù),要發(fā)送一個(gè)文件,就直接把文件所有內(nèi)容讀取出來,再按上述協(xié)議加上頭部和分隔符,拼接好數(shù)據(jù)后扔給NSURLRequest的body就可以發(fā)送了,很簡單。但這樣做是不可用的,因?yàn)槲募赡芎艽?,這樣拼數(shù)據(jù)把整個(gè)文件讀進(jìn)內(nèi)存,很可能把內(nèi)存撐爆了。

第二種方法是不把文件讀出來,不在內(nèi)存拼,而是新建一個(gè)臨時(shí)文件,在這個(gè)文件上拼接數(shù)據(jù),再把文件地址扔給NSURLRequest的bodyStream,這樣上傳的時(shí)候是分片讀取這個(gè)文件,不會(huì)撐爆內(nèi)存,但這樣每次上傳都需要新建個(gè)臨時(shí)文件,對這個(gè)臨時(shí)文件的管理也挺麻煩的。

第三種方法是構(gòu)建自己的數(shù)據(jù)結(jié)構(gòu),只保存要上傳的文件地址,邊上傳邊拼數(shù)據(jù),上傳是分片的,拼數(shù)據(jù)也是分片的,拼到文件實(shí)體部分時(shí)直接從原來的文件分片讀取。這方法沒上述兩種的問題,只是實(shí)現(xiàn)起來也沒上述兩種簡單,AFNetworking就是實(shí)現(xiàn)這第三種方法,而且還更進(jìn)一步,除了文件,還可以添加多個(gè)其他不同類型的數(shù)據(jù),包括NSData,和InputStream。

在Multipart這一部分代碼比較長,涉及到幾個(gè)類和協(xié)議,這里先把它們的關(guān)系圖放出來:


2.1AFHTTPBodyPart

AFHTTPBodyPart實(shí)際上做的是對Multipart請求體各部分(初始邊界、頭部、內(nèi)容數(shù)據(jù)實(shí)體、結(jié)束邊界)做拼接和讀取的封裝。

NSData \ FileUrl \ NSInputStream 類型的數(shù)據(jù)在AFHTTPBodyPart中都轉(zhuǎn)換成NSInputStream。

//根據(jù)body的數(shù)據(jù)類型,NSData\NSURL\NSInputStream轉(zhuǎn)換成輸入流并返回
//inputStream值保存了數(shù)據(jù)實(shí)體,沒有分隔符和頭部
- (NSInputStream *)inputStream {
    if (!_inputStream) {
        if ([self.body isKindOfClass:[NSData class]]) {
            _inputStream = [NSInputStream inputStreamWithData:self.body];
        } else if ([self.body isKindOfClass:[NSURL class]]) {
            _inputStream = [NSInputStream inputStreamWithURL:self.body];
        } else if ([self.body isKindOfClass:[NSInputStream class]]) {
            _inputStream = self.body;
        } else {
            _inputStream = [NSInputStream inputStreamWithData:[NSData data]];
        }
    }
    return _inputStream;
}

_inputStream只保存了數(shù)據(jù)實(shí)體(body),不包含上下邊界和頭部信息。

AFHTTPBodyPart讀取數(shù)據(jù)是邊讀邊拼接的,用一個(gè)狀態(tài)機(jī)來確定現(xiàn)在數(shù)據(jù)讀到哪一部分,依次往后傳遞進(jìn)行狀態(tài)切換。要注意的是,在讀取數(shù)據(jù)實(shí)體(body)部分是用流(NSInputStream)來處理的,讀之前打開流,讀完之后關(guān)閉流然后進(jìn)入下一階段:

//用狀態(tài)機(jī)切換
- (BOOL)transitionToNextPhase {
    //主線程執(zhí)行本方法
    if (![[NSThread currentThread] isMainThread]) {
        dispatch_sync(dispatch_get_main_queue(), ^{
            [self transitionToNextPhase];
        });
        return YES;
    }

    switch (_phase) {
        //讀取完初始邊界
        case AFEncapsulationBoundaryPhase:
            _phase = AFHeaderPhase;
            break;
        //讀取完頭部,準(zhǔn)備讀取body,打開流 準(zhǔn)備接受數(shù)據(jù)
        case AFHeaderPhase:
            [self.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
            [self.inputStream open];
            _phase = AFBodyPhase;
            break;
        //讀取完body,關(guān)閉流
        case AFBodyPhase:
            [self.inputStream close];
            _phase = AFFinalBoundaryPhase;
            break;
        //讀取完結(jié)束邊界
        case AFFinalBoundaryPhase:
        default:
            _phase = AFEncapsulationBoundaryPhase;
            break;
    }
    //重置
    _phaseReadOffset = 0;

    return YES;
}

結(jié)合狀態(tài)機(jī),讀取數(shù)據(jù)是分塊進(jìn)行的,拼接數(shù)據(jù)也是分塊的,邊讀邊拼接。并且使用totalNumberOfBytesRead的局部變量來保存已經(jīng)讀取的字節(jié)數(shù),以此來定位要讀的數(shù)據(jù)位置:

//把請求體讀到buffer中。邊讀取邊拼接數(shù)據(jù)
- (NSInteger)read:(uint8_t *)buffer
        maxLength:(NSUInteger)length
{
    NSInteger totalNumberOfBytesRead = 0;

    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)];
    }

    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;

        //讀取給定緩沖區(qū)中給定的字節(jié)數(shù)。返回的結(jié)果:正數(shù)表示讀取的字節(jié)數(shù)。0表示達(dá)到緩沖區(qū)的結(jié)尾。-1表示操作失敗;
        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];
            }
        }
    }

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

- (NSInteger)readData:(NSData *)data
           intoBuffer:(uint8_t *)buffer
            maxLength:(NSUInteger)length
{
    NSRange range = NSMakeRange((NSUInteger)_phaseReadOffset, MIN([data length] - ((NSUInteger)_phaseReadOffset), length));
    [data getBytes:buffer range:range];

    _phaseReadOffset += range.length;//記錄當(dāng)前階段已被讀取的字節(jié)數(shù)

    if (((NSUInteger)_phaseReadOffset) >= [data length]) {
        [self transitionToNextPhase];
    }

    return (NSInteger)range.length;
}

通過閱讀上面這兩個(gè)方法,很容易猜測,- read: maxLength:這個(gè)方法會(huì)在其他的代碼中的某個(gè)循環(huán)中被調(diào)用(主要是數(shù)據(jù)實(shí)體部分的讀取拼接是分塊進(jìn)行而不是一次性的)。

2.2AFMultipartBodyStream

AFMultipartBodyStream繼承NSInputStream ,遵循NSStreamDelegate協(xié)議。

AFMultipartBodyStream封裝了整個(gè)multipart數(shù)據(jù)的讀取。它有一個(gè)NSSArray類型的HTTPBodyParts屬性,用來保存每一個(gè)AFHTTPBodyPart對象,所以很直觀地就想到了是對多文件上傳的封裝。

對整個(gè)multipart數(shù)據(jù)的讀取,主要是根據(jù)讀取的位置確定當(dāng)前讀的是哪個(gè)AFHTTPBodyPart,然后調(diào)用AFHTTPBodyPart- read: maxLength:讀取、拼接數(shù)據(jù),最后記錄讀取的每一個(gè)AFHTTPBodyPart的數(shù)據(jù)長度總和。AFMultipartBodyStream重寫了NSInputStream的- read: maxLength:方法:

//重寫方法
- (NSInteger)read:(uint8_t *)buffer
        maxLength:(NSUInteger)length
{
    if ([self streamStatus] == NSStreamStatusClosed) {
        return 0;
    }

    NSInteger totalNumberOfBytesRead = 0;
    //self.numberOfBytesInPacket用于3G網(wǎng)絡(luò)請求優(yōu)化,指定每次讀取的數(shù)據(jù)包大小,建議值kAFUploadStream3GSuggestedPacketSize
    //遍歷讀取數(shù)據(jù)
    while ((NSUInteger)totalNumberOfBytesRead < MIN(length, self.numberOfBytesInPacket)) {
        //self.currentHTTPBodyPart不存在,或者沒有可讀的字節(jié)(已經(jīng)讀完)
        if (!self.currentHTTPBodyPart || ![self.currentHTTPBodyPart hasBytesAvailable]) {
            //看看還有沒有下一個(gè)。把下一個(gè)請求體賦值給當(dāng)前請求體,如果下一個(gè)是nil就退出循環(huán)
            if (!(self.currentHTTPBodyPart = [self.HTTPBodyPartEnumerator nextObject])) {
                break;
            }
        } else {
            //剩余數(shù)據(jù)長度?
            //這里maxLength是進(jìn)入AFHTTPBodyPart讀取的maxLength
            NSUInteger maxLength = MIN(length, self.numberOfBytesInPacket) - (NSUInteger)totalNumberOfBytesRead;
            //讀到buffer中
            NSInteger numberOfBytesRead = [self.currentHTTPBodyPart read:&buffer[totalNumberOfBytesRead] maxLength:maxLength];
            if (numberOfBytesRead == -1) {
                self.streamError = self.currentHTTPBodyPart.inputStream.streamError;
                break;
            } else {
                totalNumberOfBytesRead += numberOfBytesRead;
                //延時(shí)用于3G網(wǎng)絡(luò)請求優(yōu)化,讀取數(shù)據(jù)延時(shí),建議值kAFUploadStream3GSuggestedDelay
                if (self.delay > 0.0f) {
                    [NSThread sleepForTimeInterval:self.delay];
                }
            }
        }
    }

    return totalNumberOfBytesRead;
}

對初始邊界和結(jié)束邊界進(jìn)行設(shè)置,比如多文件上傳時(shí)設(shè)置第一個(gè)文件的初始邊界,和最后一個(gè)文件的結(jié)束邊界。

除此之外,它還對多文件上傳的初始邊界和結(jié)束邊界進(jìn)行設(shè)置。
對于多文件上傳的請求體格式:(以多文件+普通文本為例)

多文件+普通文本 上傳的請求體格式如下:

--boundary\r\n           // 第一個(gè)文件參數(shù)//上邊界,不過也可以寫成這樣:\r\n--boundary\r\n 
Content-Disposition: form-data; name=xxx; filename=xxx\r\n
Content-Type:image/jpeg\r\n\r\n        
(空一行)        
上傳文件的二進(jìn)制數(shù)據(jù)部分    
\r\n--boundary\r\n    // 第二個(gè)文件參數(shù)//上邊界 //文件一的下邊界可略,在這句之前插入文件一的下邊界\r\n--boundary--也可以
Content-Disposition: form-data; name=xxx; filename=xxx\r\n
Content-Type:text/plain\r\n\r\n
(空一行)                
上傳文件的二進(jìn)制數(shù)據(jù)部分  
\r\n--boundary\r\n    //普通文本參數(shù) //上邊界
Content-Disposition: form-data; name="xxx"\r\n\r\n    //name是服務(wù)器的接收字段,不需要自己制定
(空一行)     
普通文本二進(jìn)制數(shù)據(jù)     
\r\n--boundary--       // 下邊界

在兩個(gè)文件之間不需要把上一個(gè)文件的結(jié)束邊界也拼接上去,\r\n--boundary\r\n暫且叫做“中間邊界”吧。知道這一協(xié)議格式之后,那么下面這段代碼也很好理解了:

//初始邊界和結(jié)束邊界的設(shè)置。多文件上傳時(shí)設(shè)置第一個(gè)文件的上邊界,和最后一個(gè)文件的下邊界
- (void)setInitialAndFinalBoundaries {
    if ([self.HTTPBodyParts count] > 0) {
        for (AFHTTPBodyPart *bodyPart in self.HTTPBodyParts) {
            bodyPart.hasInitialBoundary = NO;
            bodyPart.hasFinalBoundary = NO;
        }
        [[self.HTTPBodyParts firstObject] setHasInitialBoundary:YES];
        [[self.HTTPBodyParts lastObject] setHasFinalBoundary:YES];
    }
}

由于AFMultipartBodyStream繼承NSInputStream ,遵循NSStreamDelegate協(xié)議,所以這個(gè)類里還重寫了很多NSStream的方法:

#pragma mark - NSInputStream
//重寫方法
- (BOOL)getBuffer:(__unused uint8_t **)buffer
           length:(__unused NSUInteger *)len
{
    return NO;
}

//判斷數(shù)據(jù)是否已經(jīng)讀完了,open狀態(tài)就是還有數(shù)據(jù)
- (BOOL)hasBytesAvailable {
    return [self streamStatus] == NSStreamStatusOpen;
}

#pragma mark - NSStream

- (void)open {
    if (self.streamStatus == NSStreamStatusOpen) {
        return;
    }
    self.streamStatus = NSStreamStatusOpen;
    [self setInitialAndFinalBoundaries];
    self.HTTPBodyPartEnumerator = [self.HTTPBodyParts objectEnumerator];
}

- (void)close {
    self.streamStatus = NSStreamStatusClosed;
}

- (id)propertyForKey:(__unused NSString *)key {
    return nil;
}

- (BOOL)setProperty:(__unused id)property
             forKey:(__unused NSString *)key
{
    return NO;
}

//設(shè)置runloop為了讓NSStreamDelegate收到stream狀態(tài)改變回調(diào)。不過這里NSURLRequest沒有用到delegate處理狀態(tài)改變就寫成空實(shí)現(xiàn)了。
- (void)scheduleInRunLoop:(__unused NSRunLoop *)aRunLoop
                  forMode:(__unused NSString *)mode
{}

- (void)removeFromRunLoop:(__unused NSRunLoop *)aRunLoop
                  forMode:(__unused NSString *)mode
{}

2.3AFStreamingMultipartFormData

AFStreamingMultipartFormData遵循AFMultipartFormData協(xié)議。是對AFMultipartBodyStream更上一層的封裝。

AFStreamingMultipartFormData管理了一個(gè)AFMultipartBodyStream類型的屬性bodyStream。調(diào)用AFStreamingMultipartFormData對象的幾種append方法就可以添加 FileURL/NSData/NSInputStream幾種不同類型的數(shù)據(jù),AFStreamingMultipartFormData內(nèi)部把這些數(shù)據(jù)轉(zhuǎn)換成一個(gè)個(gè)AFHTTPBodyPart,并添加到AFMultipartBodyStream里(用AFMultipartBodyStream的HTTPBodyParts數(shù)組把它們一個(gè)個(gè)保存起來)。最后把AFMultipartBodyStream賦給原來NSMutableURLRequest的bodyStream:

//通過本地文件url獲取數(shù)據(jù)
- (BOOL)appendPartWithFileURL:(NSURL *)fileURL
                         name:(NSString *)name
                     fileName:(NSString *)fileName
                     mimeType:(NSString *)mimeType
                        error:(NSError * __autoreleasing *)error
{
    NSParameterAssert(fileURL);
    NSParameterAssert(name);
    NSParameterAssert(fileName);
    NSParameterAssert(mimeType);

    //url不是fileurl
    if (![fileURL isFileURL]) {
        NSDictionary *userInfo = @{NSLocalizedFailureReasonErrorKey: NSLocalizedStringFromTable(@"Expected URL to be a file URL", @"AFNetworking", nil)};
        if (error) {
            *error = [[NSError alloc] initWithDomain:AFURLRequestSerializationErrorDomain code:NSURLErrorBadURL userInfo:userInfo];
        }
        return NO;
    }
    //路徑不可達(dá)
    else if ([fileURL checkResourceIsReachableAndReturnError:error] == NO) {
        NSDictionary *userInfo = @{NSLocalizedFailureReasonErrorKey: NSLocalizedStringFromTable(@"File URL not reachable.", @"AFNetworking", nil)};
        if (error) {
            *error = [[NSError alloc] initWithDomain:AFURLRequestSerializationErrorDomain code:NSURLErrorBadURL userInfo:userInfo];
        }
        return NO;
    }
    //獲取本地文件屬性。獲取不到就不添加
    NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[fileURL path] error:error];
    if (!fileAttributes) {
        return NO;
    }
    //設(shè)置 http請求體的header
    NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary];
    [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"; filename=\"%@\"", name, fileName] forKey:@"Content-Disposition"];
    [mutableHeaders setValue:mimeType forKey:@"Content-Type"];

    //生成AFHTTPBodyPart對象,拼接到AFMultipartBodyStream對象數(shù)組中
    AFHTTPBodyPart *bodyPart = [[AFHTTPBodyPart alloc] init];
    bodyPart.stringEncoding = self.stringEncoding;
    bodyPart.headers = mutableHeaders;
    bodyPart.boundary = self.boundary;
    bodyPart.body = fileURL;
    bodyPart.bodyContentLength = [fileAttributes[NSFileSize] unsignedLongLongValue];//獲取文件大小
    [self.bodyStream appendHTTPBodyPart:bodyPart];

    return YES;
}
//把數(shù)據(jù)跟請求建立聯(lián)系的核心方法
//數(shù)據(jù)最終通過setHTTPBodyStream:傳遞給request
- (NSMutableURLRequest *)requestByFinalizingMultipartFormData {
    if ([self.bodyStream isEmpty]) {
        return self.request;
    }

    // Reset the initial and final boundaries to ensure correct Content-Length
    [self.bodyStream setInitialAndFinalBoundaries];
    //將輸入流作為請求體
    [self.request setHTTPBodyStream:self.bodyStream];
    //設(shè)置請求頭
    [self.request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", self.boundary] forHTTPHeaderField:@"Content-Type"];
    [self.request setValue:[NSString stringWithFormat:@"%llu", [self.bodyStream contentLength]] forHTTPHeaderField:@"Content-Length"];

    return self.request;
}

NSURLSession發(fā)送請求時(shí)會(huì)讀取這個(gè)bodyStream,在讀取數(shù)據(jù)是會(huì)調(diào)用bodyStream的- read: maxLength:方法,也即AFMultipartBodyStream重寫的- read: maxLength:方法,不斷讀取之前append的AFHTTPBodyPart數(shù)據(jù)直到讀完。

2.4創(chuàng)建multipart NSMutableURLRequest請求對象

//multipart傳數(shù)據(jù)
//GET和HEAD不能用multipart傳數(shù)據(jù),一般都是用POST
- (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) {
        //把請求參數(shù)也放在multipart里
        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) {
                [formData appendPartWithFormData:data name:[pair.field description]];
            }
        }
    }

    //執(zhí)行對外暴露的block接口。
//比如可以在block里拼接其他一些文件數(shù)據(jù)。調(diào)用AFStreamingMultipartFormData的幾個(gè)append方法
    if (block) {
        block(formData);
    }
    //把stream跟request建立聯(lián)系的核心方法
    //數(shù)據(jù)最終通過setHTTPBodyStream:傳遞給request
    return [formData requestByFinalizingMultipartFormData];
}

2.5其他

AFMultipartBodyStream中有以下這么幾個(gè)方法看得不太懂,不知道為什么要這樣寫:

#pragma mark - Undocumented CFReadStream Bridged Methods
- (void)_scheduleInCFRunLoop:(__unused CFRunLoopRef)aRunLoop                     
                     forMode:(__unused CFStringRef)aMode
{}

- (void)_unscheduleFromCFRunLoop:(__unused CFRunLoopRef)aRunLoop
                         forMode:(__unused CFStringRef)aMode
{}

- (BOOL)_setCFClientFlags:(__unused CFOptionFlags)inFlags                
                 callback:(__unused CFReadStreamClientCallBack)inCallback                  
                  context:(__unused CFStreamClientContext *)inContext {    
    return NO;
}

AFNetworking2.0源碼解析<二> 中提到:

NSURLRequest的setHTTPBodyStream接受的是一個(gè)NSInputStream*參數(shù),那我們要自定義inputStream的話,創(chuàng)建一個(gè)NSInputStream的子類傳給它是不是就可以了?實(shí)際上不行,這樣做后用NSURLRequest發(fā)出請求會(huì)導(dǎo)致crash,提示[xx _scheduleInCFRunLoop:forMode:]: unrecognized selector。
這是因?yàn)镹SURLRequest實(shí)際上接受的不是NSInputStream對象,而是CoreFoundation的CFReadStreamRef對象,因?yàn)镃FReadStreamRef和NSInputStream是toll-free bridged,可以自由轉(zhuǎn)換,但CFReadStreamRef會(huì)用到CFStreamScheduleWithRunLoop這個(gè)方法,當(dāng)它調(diào)用到這個(gè)方法時(shí),object-c的toll-free bridging機(jī)制會(huì)調(diào)用object-c對象NSInputStream的相應(yīng)函數(shù),這里就調(diào)用到了_scheduleInCFRunLoop:forMode:,若不實(shí)現(xiàn)這個(gè)方法就會(huì)crash。

3.其他

AFJSONRequestSerializer和AFPropertyListRequestSerializer這兩個(gè)AFHTTPRequestSerializer的子類的實(shí)現(xiàn)都比較簡單,主要是對這個(gè)協(xié)議方法進(jìn)行重寫。具體代碼閱讀都沒什么難度,就不展開講了。

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

詳細(xì)源碼注釋請戳:https://github.com/huixinHu/AFNetworking-

參考文章:
AFNetworking到底做了什么
AFNetworking2.0源碼解析<二>
http://www.cnblogs.com/chenxianming/p/5674652.html
通讀AFN②--AFN的上傳和下載功能分析、SessionTask及相應(yīng)的session代理方法的使用細(xì)節(jié)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容