源碼閱讀:AFNetworking(二)——AFURLRequestSerialization

該文章閱讀的AFNetworking的版本爲3.2.0。html

AFHTTPRequestSerializer這個類是用來構建NSMutableURLRequest,主要作了請求數據序列化,也就是利用傳遞來的HTTP請求方法(如:GET)method、請求URLURLString和請求參數parameters來實例化NSMutableURLRequest類的對象requestgit

1.兩個全局方法

1.1 對傳入的字符串進行百分號編碼

FOUNDATION_EXPORT NSString * AFPercentEscapedStringFromString(NSString *string);
複製代碼
NSString * AFPercentEscapedStringFromString(NSString *string) {
    // 在RFC3986的第3.4節中指出,在對查詢字段百分號編碼時,保留字符中的「?」和「/」能夠不用編碼,其餘的都要進行編碼。
    static NSString * const kAFCharactersGeneralDelimitersToEncode = @":#[]@"; // does not include "?" or "/" due to RFC 3986 - Section 3.4
    static NSString * const kAFCharactersSubDelimitersToEncode = @"!$&'()*+,;=";

    // 獲取URL查詢字段容許字符,並從中刪除除「?」和「/」以外的保留字符
    NSMutableCharacterSet * allowedCharacterSet = [[NSCharacterSet URLQueryAllowedCharacterSet] mutableCopy];
    [allowedCharacterSet removeCharactersInString:[kAFCharactersGeneralDelimitersToEncode stringByAppendingString:kAFCharactersSubDelimitersToEncode]];

	// FIXME: https://github.com/AFNetworking/AFNetworking/pull/3028
    // return [string stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];

    // 每50個字符一組進行百分號編碼
    static NSUInteger const batchSize = 50;

    NSUInteger index = 0;
    NSMutableString *escaped = @"".mutableCopy;

    while (index < string.length) {
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wgnu"
        NSUInteger length = MIN(string.length - index, batchSize);
#pragma GCC diagnostic pop
        NSRange range = NSMakeRange(index, length);

        // 每個中文或者英文在NSString中的length均爲1,可是一個Emoji的length的長度爲2或者4,這是爲了不截斷Emoji表情產生亂碼
        // To avoid breaking up character sequences such as 👴🏻👮🏽
        range = [string rangeOfComposedCharacterSequencesForRange:range];

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

        index += range.length;
    }

	return escaped;
}
複製代碼

1.2 對傳入的請求參數進行默認編碼

FOUNDATION_EXPORT NSString * AFQueryStringFromParameters(NSDictionary *parameters);
複製代碼
NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
    // 把傳入的字典轉成元素爲AFQueryStringPair對象的數組,而後遍歷數組將AFQueryStringPair對象轉成通過百分號編碼的「key=value」類型NSString對象,最後用「&」拼接成一個字符串
    NSMutableArray *mutablePairs = [NSMutableArray array];
    for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
        [mutablePairs addObject:[pair URLEncodedStringValue]];
    }

    return [mutablePairs componentsJoinedByString:@"&"];
}
複製代碼

以上方法調用瞭如下方法並把parameters做爲參數傳遞過去github

NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) {
    // 第一個參數key傳了nil,第二個參數value傳了以上方法傳過來的字典
    return AFQueryStringPairsFromKeyAndValue(nil, dictionary);
}
複製代碼

下面這個方法就是對字典進行處理,轉成元素爲AFQueryStringPair對象的數組json

NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) {
    NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];

    // 設置排序描述爲按照對象的description屬性的字母升序排列
    NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)];

    // 若是參數value傳入的是NSDictionary
    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
        // 將字典的key按照首字母升序排列後進行遍歷
        for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
            // 若是遍歷出的key所對應的value不爲空,就遞歸調用本方法,若是有key值則傳(key[nestedKey], nestedValue),不然傳(nestedKey, nestedValue)
            id nestedValue = dictionary[nestedKey];
            if (nestedValue) {
                [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)];
            }
        }
    // 若是參數value傳入的是NSArray
    } else if ([value isKindOfClass:[NSArray class]]) {
        // 聲明變量保存傳入的數組
        NSArray *array = value;
        // 遍歷數組
        for (id nestedValue in array) {
            // 遞歸調用本方法,若是有key值則傳遞(key[], nestedValue),不然傳((null)[], nestedValue)
            [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[]", key], nestedValue)];
        }
    // 若是參數value傳入的是NSSet
    } else if ([value isKindOfClass:[NSSet class]]) {
        // 聲明變量保存傳入的集合
        NSSet *set = value;
        // 將集合的元素按照首字母升序排列後進行遍歷
        for (id obj in [set sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
             // 遞歸調用本方法,若是有key值則傳(key, obj),不然傳((null), obj)
            [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue(key, obj)];
        }
    // 若是參數value傳入的不是集合對象
    } else {
        // 利用傳入的參數key和value實例化AFQueryStringPair對象並添加到mutableQueryStringComponents數組中
        [mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]];
    }

    // 返回由字典對象轉化元素爲AFQueryStringPair對象組成的數組
    return mutableQueryStringComponents;
}
複製代碼

2.兩個協議

2.1. AFURLRequestSerialization協議

這個協議定義了一個方法,用來將參parameters數拼接到NSURLRequest對象中。其中類AFHTTPRequestSerializerAFJSONRequestSerializerAFPropertyListRequestSerializer都遵照這個協議數組

@protocol AFURLRequestSerialization <NSObject, NSSecureCoding, NSCopying>

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

@end
複製代碼

2.2. AFMultipartFormData協議

這個協議定義了一系列方法用於在- multipartFormRequestWithMethod:parameters:constructingBodyWithBlock:error:方法中的代碼塊中爲formData添加數據緩存

/**
 將指定路徑下數據添加到表單中  
 */
- (BOOL)appendPartWithFileURL:(NSURL *)fileURL
                         name:(NSString *)name
                        error:(NSError * _Nullable __autoreleasing *)error;

/**
 將指定路徑下數據添加到表單中,並指定文件類型
 */
- (BOOL)appendPartWithFileURL:(NSURL *)fileURL
                         name:(NSString *)name
                     fileName:(NSString *)fileName
                     mimeType:(NSString *)mimeType
                        error:(NSError * _Nullable __autoreleasing *)error;

/**
 將指定輸入流中的數據添加到表單中
 */
- (void)appendPartWithInputStream:(nullable NSInputStream *)inputStream
                             name:(NSString *)name
                         fileName:(NSString *)fileName
                           length:(int64_t)length
                         mimeType:(NSString *)mimeType;

/**
 將指定NSData對象添加到表單中,並指定文件類型
 */
- (void)appendPartWithFileData:(NSData *)data
                          name:(NSString *)name
                      fileName:(NSString *)fileName
                      mimeType:(NSString *)mimeType;

/**
 將指定NSData對象添加到表單中
 */

- (void)appendPartWithFormData:(NSData *)data
                          name:(NSString *)name;

/**
 將指定的請求頭和請求體添加到表單中
 */
- (void)appendPartWithHeaders:(nullable NSDictionary <NSString *, NSString *> *)headers
                         body:(NSData *)body;

/**
 經過設置請求的帶寬和延遲時間來提升在弱網環境下上傳數據的成功率
 */
- (void)throttleBandwidthWithPacketSize:(NSUInteger)numberOfBytes
                                  delay:(NSTimeInterval)delay;
複製代碼

3.一個枚舉

緊接着能夠看到一個枚舉,定義了請求查詢字段的編碼方式,不過目前只定義了一種默認方式bash

typedef NS_ENUM(NSUInteger, AFHTTPRequestQueryStringSerializationStyle) {
    AFHTTPRequestQueryStringDefaultStyle = 0,
};
複製代碼

4.三個類

能夠在.h文件中看到一共有三個類,分別是AFHTTPRequestSerializer和它的兩個子類AFJSONRequestSerializerAFPropertyListRequestSerializer,先來看一下AFHTTPRequestSerializer這個類服務器

4.1 AFHTTPRequestSerializer類

先在.h文件中看一下對外暴漏的接口部分cookie

4.1.1 接口部分

4.1.1.1 屬性

/**
 字符串編碼方式,默認爲NSUTF8StringEncoding
 */
@property (nonatomic, assign) NSStringEncoding stringEncoding;

/**
 是否容許使用蜂窩網,默認爲是
 */
@property (nonatomic, assign) BOOL allowsCellularAccess;

/**
 請求的緩存策略
 */
@property (nonatomic, assign) NSURLRequestCachePolicy cachePolicy;

/**
 是否將cookies添加到request的header中一同發送給服務器,默認爲是
 */
@property (nonatomic, assign) BOOL HTTPShouldHandleCookies;

/**
 是否使用管線化,便是否要等到收到前一個請求的響應後才能發送後一個請求,管線化能夠一個發送一組請求,沒必要等待,默認爲否
 */
@property (nonatomic, assign) BOOL HTTPShouldUsePipelining;

/**
 網絡服務類型,系統會根據設置的類型自動優化
 */
@property (nonatomic, assign) NSURLRequestNetworkServiceType networkServiceType;

/**
 超時時長,默認爲60秒
 */
@property (nonatomic, assign) NSTimeInterval timeoutInterval;

/**
 請求頭信息
 */
@property (readonly, nonatomic, strong) NSDictionary <NSString *, NSString *> *HTTPRequestHeaders;
複製代碼

4.1.1.2 方法

/**
 實例化默認設置對象的方法
 */
+ (instancetype)serializer;

/**
 設置請求頭的字段和值,若是值爲nil就移除該字段
 */
- (void)setValue:(nullable NSString *)value
forHTTPHeaderField:(NSString *)field;

/**
 獲取請求頭指定字段的值
 */
- (nullable NSString *)valueForHTTPHeaderField:(NSString *)field;

/**
 利用帳號和密碼爲請求頭的「Authorization」字段賦值
 */
- (void)setAuthorizationHeaderFieldWithUsername:(NSString *)username
                                       password:(NSString *)password;

/**
 從請求頭中清除「Authorization」字段的值
 */
- (void)clearAuthorizationHeader;

/**
 要把查詢字符串編碼拼接到URL後面的HTTP請求方法集合,默認爲GET、HEAD和DELETE
 */
@property (nonatomic, strong) NSSet <NSString *> *HTTPMethodsEncodingParametersInURI;

/**
 設置查詢字符串的編碼方法,目前AFNetworking只實現了一種,即百分號編碼
 */
- (void)setQueryStringSerializationWithStyle:(AFHTTPRequestQueryStringSerializationStyle)style;

/**
 設置自定義的查詢字符串編碼方法,只須要在block中實現編碼便可
 */
- (void)setQueryStringSerializationWithBlock:(nullable NSString * (^)(NSURLRequest *request, id parameters, NSError * __autoreleasing *error))block;

/**
 利用傳入的HTTP請求方法、請求URL和請求參數三個參數生成NSMutableURLRequest對象。當HTTP請求方法爲GET、HEAD或DELETE時,參數會拼接到URL後面,不然,就添加到請求體中
 */
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
                                 URLString:(NSString *)URLString
                                parameters:(nullable id)parameters
                                     error:(NSError * _Nullable __autoreleasing *)error;

/**
 利用傳入的HTTP請求方法、請求URL和請求參數三個參數生成multipart/form-dat請求的NSMutableURLRequest對象。
 */
- (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;

/**
移除掉原request中的HTTPBodyStream,並異步寫到指定路徑下,並返回NSMutableURLRequest對象
 */
- (NSMutableURLRequest *)requestWithMultipartFormRequest:(NSURLRequest *)request
                             writingStreamContentsToFile:(NSURL *)fileURL
                                       completionHandler:(nullable void (^)(NSError * _Nullable error))handler;

@end
複製代碼

看完了接口部分,再進入.m文件中看一下私有實現網絡

4.1.2 私有實現部分

4.1.2.1 私有全局方法

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對象的屬性,並保存在一個數組中返回。

4.1.2.2 私有全局靜態變量

static void *AFHTTPRequestSerializerObserverContext = &AFHTTPRequestSerializerObserverContext;
複製代碼

該變量用於識別觀察者的身份

4.1.2.3 類擴展

@interface AFHTTPRequestSerializer ()
// 用來保存須要觀察的用戶自定義的AFHTTPRequestSerializer對象的屬性
@property (readwrite, nonatomic, strong) NSMutableSet *mutableObservedChangedKeyPaths;
// 用來保存請求頭信息
@property (readwrite, nonatomic, strong) NSMutableDictionary *mutableHTTPRequestHeaders;
// 用來保存查詢字段編碼類型
@property (readwrite, nonatomic, assign) AFHTTPRequestQueryStringSerializationStyle queryStringSerializationStyle;
// 用來保存用戶自定義的查詢字段編碼方式代碼塊
@property (readwrite, nonatomic, copy) AFQueryStringSerializationBlock queryStringSerialization;
@end
複製代碼

4.1.2.4 實現

4.1.2.4.1 生命週期相關方法
+ (instancetype)serializer {
    // 就是正常的實例化方法
    return [[self alloc] init];
}

- (instancetype)init {
    self = [super init];
    if (!self) {
        return nil;
    }

    // 初始化字符串編碼方式爲NSUTF8StringEncoding
    self.stringEncoding = NSUTF8StringEncoding;

    // 初始化請求頭
    self.mutableHTTPRequestHeaders = [NSMutableDictionary dictionary];

    // 獲取前五個用戶偏好的語言並賦值給請求頭Accept-Language字段
    NSMutableArray *acceptLanguagesComponents = [NSMutableArray array];
    [[NSLocale preferredLanguages] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        float q = 1.0f - (idx * 0.1f);
        [acceptLanguagesComponents addObject:[NSString stringWithFormat:@"%@;q=%0.1g", obj, q]];
        *stop = q <= 0.5f;
    }];
    [self setValue:[acceptLanguagesComponents componentsJoinedByString:@", "] forHTTPHeaderField:@"Accept-Language"];

    // 獲取項目名稱(若是沒有則獲取BundleID)、應用Version版本號(若是沒有則獲取應用Build版本號)、設備類型、系統版本號和屏幕縮放比並賦值給請求頭User-Agent字段
    NSString *userAgent = nil;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
#if TARGET_OS_IOS
    // User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
    userAgent = [NSString stringWithFormat:@"%@/%@ (%@; iOS %@; Scale/%0.2f)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[UIDevice currentDevice] model], [[UIDevice currentDevice] systemVersion], [[UIScreen mainScreen] scale]];
#elif TARGET_OS_WATCH
    // User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
    userAgent = [NSString stringWithFormat:@"%@/%@ (%@; watchOS %@; Scale/%0.2f)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[WKInterfaceDevice currentDevice] model], [[WKInterfaceDevice currentDevice] systemVersion], [[WKInterfaceDevice currentDevice] screenScale]];
#elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED)
    userAgent = [NSString stringWithFormat:@"%@/%@ (Mac OS X %@)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[NSProcessInfo processInfo] operatingSystemVersionString]];
#endif
#pragma clang diagnostic pop
    if (userAgent) {
        // 若是不能進行無損ASCII編碼,即不是隻有普通的字符或ASCII碼
        if (![userAgent canBeConvertedToEncoding:NSASCIIStringEncoding]) {
            NSMutableString *mutableUserAgent = [userAgent mutableCopy];
            // 若是移除全部非ASCII值範圍的全部字符,移除後再次賦值
            if (CFStringTransform((__bridge CFMutableStringRef)(mutableUserAgent), NULL, (__bridge CFStringRef)@"Any-Latin; Latin-ASCII; [:^ASCII:] Remove", false)) {
                userAgent = mutableUserAgent;
            }
        }
        [self setValue:userAgent forHTTPHeaderField:@"User-Agent"];
    }

    // HTTP Method Definitions; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
    // 初始化須要把查詢字符串編碼拼接到URL後面的HTTP請求方法集合爲GET、HEAD和DELETE方法
    self.HTTPMethodsEncodingParametersInURI = [NSSet setWithObjects:@"GET", @"HEAD", @"DELETE", nil];

    // 初始化要觀察的自定義的AFHTTPRequestSerializer屬性集合
    self.mutableObservedChangedKeyPaths = [NSMutableSet set];
    // 遍歷AFHTTPRequestSerializer須要添加觀察的屬性,添加觀察者,並設置上下文爲AFHTTPRequestSerializerObserverContext用於標識
    for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
        if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
            [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
        }
    }

    return self;
}

- (void)dealloc {
    // 遍歷AFHTTPRequestSerializer須要添加觀察的屬性,移除觀察者
    for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
        if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
            [self removeObserver:self forKeyPath:keyPath context:AFHTTPRequestSerializerObserverContext];
        }
    }
}
複製代碼
4.1.2.4.2 手動實現KVO
- (void)setAllowsCellularAccess:(BOOL)allowsCellularAccess {
    [self willChangeValueForKey:NSStringFromSelector(@selector(allowsCellularAccess))];
    _allowsCellularAccess = allowsCellularAccess;
    [self didChangeValueForKey:NSStringFromSelector(@selector(allowsCellularAccess))];
}

- (void)setCachePolicy:(NSURLRequestCachePolicy)cachePolicy {
    [self willChangeValueForKey:NSStringFromSelector(@selector(cachePolicy))];
    _cachePolicy = cachePolicy;
    [self didChangeValueForKey:NSStringFromSelector(@selector(cachePolicy))];
}

- (void)setHTTPShouldHandleCookies:(BOOL)HTTPShouldHandleCookies {
    [self willChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldHandleCookies))];
    _HTTPShouldHandleCookies = HTTPShouldHandleCookies;
    [self didChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldHandleCookies))];
}

- (void)setHTTPShouldUsePipelining:(BOOL)HTTPShouldUsePipelining {
    [self willChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldUsePipelining))];
    _HTTPShouldUsePipelining = HTTPShouldUsePipelining;
    [self didChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldUsePipelining))];
}

- (void)setNetworkServiceType:(NSURLRequestNetworkServiceType)networkServiceType {
    [self willChangeValueForKey:NSStringFromSelector(@selector(networkServiceType))];
    _networkServiceType = networkServiceType;
    [self didChangeValueForKey:NSStringFromSelector(@selector(networkServiceType))];
}

- (void)setTimeoutInterval:(NSTimeInterval)timeoutInterval {
    [self willChangeValueForKey:NSStringFromSelector(@selector(timeoutInterval))];
    _timeoutInterval = timeoutInterval;
    [self didChangeValueForKey:NSStringFromSelector(@selector(timeoutInterval))];
}
複製代碼
4.1.2.4.3 自定義公共屬性的setter和getter
- (NSDictionary *)HTTPRequestHeaders {
    // 返回私有屬性mutableHTTPRequestHeaders
    return [NSDictionary dictionaryWithDictionary:self.mutableHTTPRequestHeaders];
}

- (void)setValue:(NSString *)value
forHTTPHeaderField:(NSString *)field
{
     // 爲私有屬性mutableHTTPRequestHeaders賦值
	[self.mutableHTTPRequestHeaders setValue:value forKey:field];
}

- (NSString *)valueForHTTPHeaderField:(NSString *)field {
    // 獲取私有屬性mutableHTTPRequestHeaders指定key的值
    return [self.mutableHTTPRequestHeaders valueForKey:field];
}

- (void)setAuthorizationHeaderFieldWithUsername:(NSString *)username
                                       password:(NSString *)password
{
    // 先把帳戶和密碼拼接成一個字符串後轉爲UTF8格式的NSData對象,再經過base64編碼成字符串賦值給請求頭的Authorization字段
    NSData *basicAuthCredentials = [[NSString stringWithFormat:@"%@:%@", username, password] dataUsingEncoding:NSUTF8StringEncoding];
    NSString *base64AuthCredentials = [basicAuthCredentials base64EncodedStringWithOptions:(NSDataBase64EncodingOptions)0];
    [self setValue:[NSString stringWithFormat:@"Basic %@", base64AuthCredentials] forHTTPHeaderField:@"Authorization"];
}

- (void)clearAuthorizationHeader {
     // 從請求頭中移除Authorization字段
	[self.mutableHTTPRequestHeaders removeObjectForKey:@"Authorization"];
}

- (void)setQueryStringSerializationWithStyle:(AFHTTPRequestQueryStringSerializationStyle)style {
    // 若是設置了編碼格式就把自定義編碼代碼塊置nil
    self.queryStringSerializationStyle = style;
    self.queryStringSerialization = nil;
}

- (void)setQueryStringSerializationWithBlock:(NSString *(^)(NSURLRequest *, id, NSError *__autoreleasing *))block {
    // 這是爲了用戶在設置代碼塊時有智能提示,能夠直接回車敲出
    self.queryStringSerialization = block;
}
複製代碼
4.1.2.4.4 公共方法的實現
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
                                 URLString:(NSString *)URLString
                                parameters:(id)parameters
                                     error:(NSError *__autoreleasing *)error
{
    // 在debug模式下缺乏對應參數會crash
    NSParameterAssert(method);
    NSParameterAssert(URLString);

    // 利用傳入的路徑生成NSURL對象
    NSURL *url = [NSURL URLWithString:URLString];

    // 判斷url是否生成
    NSParameterAssert(url);

    // 利用生成的NSURL對象生成NSMutableURLRequest對象,並設置請求方式
    NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
    mutableRequest.HTTPMethod = method;

    // 遍歷AFHTTPRequestSerializer須要添加觀察的屬性
    for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
        // 若是遍歷出的屬性是用戶自定義的屬性
        if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
            // 將屬性對應的值賦值給NSMutableURLRequest對象相對應的屬性
            [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
        }
    }

    // 將傳入的參數parameters處理後添加到mutableRequest中
    mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];

    // 返回mutableRequest
    return mutableRequest;
}

- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method
                                              URLString:(NSString *)URLString
                                             parameters:(NSDictionary *)parameters
                              constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block
                                                  error:(NSError *__autoreleasing *)error
{
    // 沒有傳請求方法就crash
    NSParameterAssert(method);
    // 請求方法是GET或HEAD就crash
    NSParameterAssert(![method isEqualToString:@"GET"] && ![method isEqualToString:@"HEAD"]);
    
    // 調用上個公共方法生成NSMutableURLRequest對象
    NSMutableURLRequest *mutableRequest = [self requestWithMethod:method URLString:URLString parameters:nil error:error];

    // 利用NSMutableURLRequest對象生成AFStreamingMultipartFormData對象formData
    __block AFStreamingMultipartFormData *formData = [[AFStreamingMultipartFormData alloc] initWithURLRequest:mutableRequest stringEncoding:NSUTF8StringEncoding];

    // 若是傳遞了參數
    if (parameters) {
        // 將傳入的字典參數轉爲元素是AFQueryStringPair對象的數組,並進行遍歷
        for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
            // 將對象pair的value屬性轉爲NSData對象,並拼到formData對象中
            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]];
            }
        }
    }

    // 調用代碼塊拼接想要上傳的數據
    if (block) {
        block(formData);
    }

    // 構建multipart/form-data請求獨有的請求頭
    return [formData requestByFinalizingMultipartFormData];
}

- (NSMutableURLRequest *)requestWithMultipartFormRequest:(NSURLRequest *)request
                             writingStreamContentsToFile:(NSURL *)fileURL
                                       completionHandler:(void (^)(NSError *error))handler
{
    // request對象的HTTPBodyStream屬性爲nil則crash
    NSParameterAssert(request.HTTPBodyStream);
    // fileURL不是合法的文件路徑則crash
    NSParameterAssert([fileURL isFileURL]);

    // 生成輸入流和輸出流
    NSInputStream *inputStream = request.HTTPBodyStream;
    NSOutputStream *outputStream = [[NSOutputStream alloc] initWithURL:fileURL append:NO];
    __block NSError *error = nil;

    // 全局併發隊列異步執行寫入操做
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 把輸入輸出流添加到默認模式的當前運行循環中
        [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
        [outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

        // 打開輸入輸出流
        [inputStream open];
        [outputStream open];

        // 若是輸入輸出流還有可操做字節
        while ([inputStream hasBytesAvailable] && [outputStream hasSpaceAvailable]) {
            uint8_t buffer[1024];

            // 每次從輸入流中讀取最大1024bytes大小的數據存入buffer中,若是出錯則跳出循環
            NSInteger bytesRead = [inputStream read:buffer maxLength:1024];
            if (inputStream.streamError || bytesRead < 0) {
                error = inputStream.streamError;
                break;
            }

            // 將從輸入流中讀取出的數據寫入到輸出流中,若是出錯則跳出循環
            NSInteger bytesWritten = [outputStream write:buffer maxLength:(NSUInteger)bytesRead];
            if (outputStream.streamError || bytesWritten < 0) {
                error = outputStream.streamError;
                break;
            }

            // 若是讀寫完則跳出循環
            if (bytesRead == 0 && bytesWritten == 0) {
                break;
            }
        }

        // 關閉輸入輸出流
        [outputStream close];
        [inputStream close];

        // 若是傳入了回調代碼塊則在主隊列異步回調
        if (handler) {
            dispatch_async(dispatch_get_main_queue(), ^{
                handler(error);
            });
        }
    });

    // 把原mutableRequest對象的HTTPBodyStream屬性置nil後返回
    NSMutableURLRequest *mutableRequest = [request mutableCopy];
    mutableRequest.HTTPBodyStream = nil;

    return mutableRequest;
}
複製代碼
4.1.2.4.5 AFURLRequestSerialization協議方法的實現
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
                               withParameters:(id)parameters
                                        error:(NSError *__autoreleasing *)error
{
    // 缺乏request則會crash
    NSParameterAssert(request);

    NSMutableURLRequest *mutableRequest = [request mutableCopy];

    // 遍歷request的請求頭,對沒有值的字段進行賦值
    [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
        if (![request valueForHTTPHeaderField:field]) {
            [mutableRequest setValue:value forHTTPHeaderField:field];
        }
    }];

    // 對參數parameters進行編碼
    NSString *query = nil;
    if (parameters) {
        // 若是用戶自定義了編碼代碼塊則用用戶自定義的方法編碼
        if (self.queryStringSerialization) {
            NSError *serializationError;
            query = self.queryStringSerialization(request, parameters, &serializationError);

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

                return nil;
            }
        // 若是用戶沒有自定義編碼代碼塊則用AFNetworking默認的編碼方式,即百分號編碼
        } else {
            switch (self.queryStringSerializationStyle) {
                case AFHTTPRequestQueryStringDefaultStyle:
                    query = AFQueryStringFromParameters(parameters);
                    break;
            }
        }
    }

    // 若是HTTP請求方法爲GET、HEAD或DELETE其中之一
    if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
        // 就把查詢字符串拼接到url後面
        if (query && query.length > 0) {
            mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
        }
    // 若是HTTP請求方法爲POST、PUT其中之一
    } 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]];
    }

    // 返回拼接好參數的mutableRequest對象
    return mutableRequest;
}
複製代碼
4.1.2.4.6 KVO方法回調的處理
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
    // 若是是須要觀察的AFHTTPRequestSerializer對象的屬性,則不自動實現KVO
    if ([AFHTTPRequestSerializerObservedKeyPaths() containsObject:key]) {
        return NO;
    }

    return [super automaticallyNotifiesObserversForKey:key];
}

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(__unused id)object
                        change:(NSDictionary *)change
                       context:(void *)context
{
    // 若是觀察到的是AFHTTPRequestSerializer類添加觀察的屬性
    if (context == AFHTTPRequestSerializerObserverContext) {
        // 若是給當前屬性賦的值不爲null就添加到self.mutableObservedChangedKeyPaths中,不然從其中移除
        if ([change[NSKeyValueChangeNewKey] isEqual:[NSNull null]]) {
            [self.mutableObservedChangedKeyPaths removeObject:keyPath];
        } else {
            [self.mutableObservedChangedKeyPaths addObject:keyPath];
        }
    }
}
複製代碼
4.1.2.4.7 NSSecureCoding協議方法的實現

在iOS6中,蘋果引入了一個新的協議,是基於NSCoding的,叫作NSSecureCodingNSSecureCodingNSCoding是同樣的,除了在解碼時要同時指定key和要解碼的對象的類,若是要求的類和從文件中解碼出的對象的類不匹配,NSCoder會拋出異常,告訴你數據已經被篡改了。

+ (BOOL)supportsSecureCoding {
    // 若是一個類符合 NSSecureCoding 協議並在 + supportsSecureCoding 返回 YES,就聲明瞭它能夠處理自己實例的編碼解碼方式,以防止替換攻擊。
    return YES;
}

- (instancetype)initWithCoder:(NSCoder *)decoder {
    self = [self init];
    if (!self) {
        return nil;
    }

    self.mutableHTTPRequestHeaders = [[decoder decodeObjectOfClass:[NSDictionary class] forKey:NSStringFromSelector(@selector(mutableHTTPRequestHeaders))] mutableCopy];
    self.queryStringSerializationStyle = (AFHTTPRequestQueryStringSerializationStyle)[[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(queryStringSerializationStyle))] unsignedIntegerValue];

    return self;
}

- (void)encodeWithCoder:(NSCoder *)coder {
    [coder encodeObject:self.mutableHTTPRequestHeaders forKey:NSStringFromSelector(@selector(mutableHTTPRequestHeaders))];
    [coder encodeInteger:self.queryStringSerializationStyle forKey:NSStringFromSelector(@selector(queryStringSerializationStyle))];
}
複製代碼
4.1.2.4.8 NSCopying協議方法的實現
- (instancetype)copyWithZone:(NSZone *)zone {
    AFHTTPRequestSerializer *serializer = [[[self class] allocWithZone:zone] init];
    serializer.mutableHTTPRequestHeaders = [self.mutableHTTPRequestHeaders mutableCopyWithZone:zone];
    serializer.queryStringSerializationStyle = self.queryStringSerializationStyle;
    serializer.queryStringSerialization = self.queryStringSerialization;

    return serializer;
}

@end
複製代碼

4.2 AFJSONRequestSerializer類

AFJSONRequestSerializerAFHTTPRequestSerializer的子類,當服務器要求咱們上傳的數據格式類型爲json時,就可使用此類

4.2.1 接口部分

4.2.1.1 屬性

// 設置JSON的編碼類型
@property (nonatomic, assign) NSJSONWritingOptions writingOptions;
複製代碼

4.2.1.2 方法

// 實例化工廠方法
+ (instancetype)serializerWithWritingOptions:(NSJSONWritingOptions)writingOptions;
複製代碼

4.2.2 私有實現部分

4.2.2.1 公共方法的實現

+ (instancetype)serializer {
    // 調用下面的方法並傳默認的JSON輸出格式
    return [self serializerWithWritingOptions:(NSJSONWritingOptions)0];
}

+ (instancetype)serializerWithWritingOptions:(NSJSONWritingOptions)writingOptions
{
    // 調用父類的初始化方法並保存了傳入的參數
    AFJSONRequestSerializer *serializer = [[self alloc] init];
    serializer.writingOptions = writingOptions;

    return serializer;
}
複製代碼

4.2.2.2 AFURLRequestSerialization協議方法的實現

- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
                               withParameters:(id)parameters
                                        error:(NSError *__autoreleasing *)error
{
    // 缺乏request則會crash
    NSParameterAssert(request);

    // 若是HTTP請求方法爲GET、HEAD或DELETE其中之一
    if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
        // 就直接調用父類的實現並返回
        return [super requestBySerializingRequest:request withParameters:parameters error:error];
    }

    NSMutableURLRequest *mutableRequest = [request mutableCopy];

    // 遍歷request的請求頭,對沒有值的字段進行賦值
    [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
        if (![request valueForHTTPHeaderField:field]) {
            [mutableRequest setValue:value forHTTPHeaderField:field];
        }
    }];

    // 若是傳入了參數
    if (parameters) {
        // 若是mutableRequest的請求頭的Content-Type字段沒有值
        if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
            // 爲mutableRequest的請求頭的Content-Type字段賦值爲application/json
            [mutableRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
        }

        // 將傳入的parameters轉成JSON格式的NSData對象並添加到mutableRequest的請求體中
        [mutableRequest setHTTPBody:[NSJSONSerialization dataWithJSONObject:parameters options:self.writingOptions error:error]];
    }

    return mutableRequest;
}
複製代碼

4.2.2.3 NSSecureCoding協議方法的實現

就是在父類的基礎上添加了writingOptions屬性

- (instancetype)initWithCoder:(NSCoder *)decoder {
    self = [super initWithCoder:decoder];
    if (!self) {
        return nil;
    }

    self.writingOptions = [[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(writingOptions))] unsignedIntegerValue];

    return self;
}

- (void)encodeWithCoder:(NSCoder *)coder {
    [super encodeWithCoder:coder];

    [coder encodeInteger:self.writingOptions forKey:NSStringFromSelector(@selector(writingOptions))];
}
複製代碼

4.2.2.4 NSCopying協議方法的實現

一樣也是在父類的基礎上添加了writingOptions屬性

- (instancetype)copyWithZone:(NSZone *)zone {
    AFJSONRequestSerializer *serializer = [super copyWithZone:zone];
    serializer.writingOptions = self.writingOptions;

    return serializer;
}
複製代碼

4.3 AFPropertyListRequestSerializer類

AFPropertyListRequestSerializerAFHTTPRequestSerializer的子類,此類能夠把傳入的參數編碼成plist格式NSDate對象傳給服務器,通常是用來向服務器傳遞XML格式的數據

4.3.1 接口部分

4.3.1.1 屬性

// plist輸出格式
@property (nonatomic, assign) NSPropertyListFormat format;
// plist編碼類型,目前這個值尚未用
@property (nonatomic, assign) NSPropertyListWriteOptions writeOptions;
複製代碼

4.3.1.2 方法

// 實例化工廠方法
+ (instancetype)serializerWithFormat:(NSPropertyListFormat)format
                        writeOptions:(NSPropertyListWriteOptions)writeOptions;tions;
複製代碼

4.3.2 私有實現部分

4.3.2.1 公共方法的實現

+ (instancetype)serializer {
    // 調用下面的實例化方法,設置plist的輸出格式爲XML類型
    return [self serializerWithFormat:NSPropertyListXMLFormat_v1_0 writeOptions:0];
}

+ (instancetype)serializerWithFormat:(NSPropertyListFormat)format
                        writeOptions:(NSPropertyListWriteOptions)writeOptions
{
    // 調用父類的初始化方法並保存了傳入的參數
    AFPropertyListRequestSerializer *serializer = [[self alloc] init];
    serializer.format = format;
    serializer.writeOptions = writeOptions;

    return serializer;
}
複製代碼

4.3.2.2 AFURLRequestSerialization協議方法的實現

- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
                               withParameters:(id)parameters
                                        error:(NSError *__autoreleasing *)error
{
    // 缺乏request則會crash
    NSParameterAssert(request);

    // 若是HTTP請求方法爲GET、HEAD或DELETE其中之一
    if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
        // 就直接調用父類的實現並返回
        return [super requestBySerializingRequest:request withParameters:parameters error:error];
    }

    NSMutableURLRequest *mutableRequest = [request mutableCopy];

    // 遍歷request的請求頭,對沒有值的字段進行賦值
    [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
        if (![request valueForHTTPHeaderField:field]) {
            [mutableRequest setValue:value forHTTPHeaderField:field];
        }
    }];

    // 若是傳入了參數
    if (parameters) {
        // 若是mutableRequest的請求頭的Content-Type字段沒有值
        if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
            // // 爲mutableRequest的請求頭的Content-Type字段賦值application/x-plist
            [mutableRequest setValue:@"application/x-plist" forHTTPHeaderField:@"Content-Type"];
        }

        // 將傳入的parameters轉成plist格式的NSData對象並添加到mutableRequest的請求體中
        [mutableRequest setHTTPBody:[NSPropertyListSerialization dataWithPropertyList:parameters format:self.format options:self.writeOptions error:error]];
    }

    return mutableRequest;
}

複製代碼

5.四個全局靜態常量

/**
 AFURLRequestSerializer類的錯誤,錯誤碼對應NSURLErrorDomain的錯誤碼
 */
FOUNDATION_EXPORT NSString * const AFURLRequestSerializationErrorDomain;

/**
 這個key只存在AFURLRequestSerializationErrorDomain中,其對應的值是NSURLRequest錯誤請求的操做
 */
FOUNDATION_EXPORT NSString * const AFNetworkingOperationFailingURLRequestErrorKey;

/**
 HTTP請求輸入流的節流帶寬字節數的最大分組大小。等於16KB。
 */
FOUNDATION_EXPORT NSUInteger const kAFUploadStream3GSuggestedPacketSize;

/**
 HTTP請求輸入流的節流帶寬每次讀取數據包時的延遲時間。等於0.2秒。
 */
FOUNDATION_EXPORT NSTimeInterval const kAFUploadStream3GSuggestedDelay;
複製代碼

6.四個私有類

6.1 AFQueryStringPair類

在對請求的查詢參數編碼時,傳入的參數會被拆分紅最小的集合類對象,而後將集合對象轉成AFQueryStringPair對象,再對field和value百分號編碼後生成field=value類型的字符串

6.1.1 接口部分

6.1.1.1 屬性

@property (readwrite, nonatomic, strong) id field;    // 字段
@property (readwrite, nonatomic, strong) id value;    // 值
複製代碼

6.1.1.2 方法

/**
 AFQueryStringPair對象初始化方法

 @param field 字段
 @param value 值
 @return 初始化的AFQueryStringPair對象
 */
- (instancetype)initWithField:(id)field value:(id)value;

/**
 將屬性field和value進行百分號編碼後,之間用」=「拼接成一個字符串

 @return 處理好的字符串
 */
- (NSString *)URLEncodedStringValue;
複製代碼

6.1.2 實現部分

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

    // 屬性保存初始化傳入的參數
    self.field = field;
    self.value = value;

    return self;
}

- (NSString *)URLEncodedStringValue {
    // 若是value值爲nil或null
    if (!self.value || [self.value isEqual:[NSNull null]]) {
        // 只把屬性field的字符串描述屬性進行百分號編碼後返回
        return AFPercentEscapedStringFromString([self.field description]);
    // 若是value值不爲nil或null
    } else {
        // 把屬性field和value進行百分號編碼後,之間用」=「拼接成一個字符串返回
        return [NSString stringWithFormat:@"%@=%@", AFPercentEscapedStringFromString([self.field description]), AFPercentEscapedStringFromString([self.value description])];
    }
}
複製代碼

6.2 AFHTTPBodyPart類

每個AFHTTPBodyPart就是表明一項表單數據,即一個要上傳的文件的數據,並由它本身讀取它內部的數據

6.2.1 私有全局屬性

/**
 回車換行
 */
static NSString * const kAFMultipartFormCRLF = @"\r\n";

/**
 3G環境上傳建議帶寬
 */
NSUInteger const kAFUploadStream3GSuggestedPacketSize = 1024 * 16;

/**
 3G環境上傳建議延時
 */
NSTimeInterval const kAFUploadStream3GSuggestedDelay = 0.2;
複製代碼

6.2.2 私有全局方法

/**
 由隨機生成的八位16進制字符串組成的邊界字符串
 */
static NSString * AFCreateMultipartFormBoundary() {
    return [NSString stringWithFormat:@"Boundary+%08X%08X", arc4random(), arc4random()];
}

/**
 生成開始邊界字符串
 */
static inline NSString * AFMultipartFormInitialBoundary(NSString *boundary) {
    return [NSString stringWithFormat:@"--%@%@", boundary, kAFMultipartFormCRLF];
}

/**
 生成中間邊界字符串
 */
static inline NSString * AFMultipartFormEncapsulationBoundary(NSString *boundary) {
    return [NSString stringWithFormat:@"%@--%@%@", kAFMultipartFormCRLF, boundary, kAFMultipartFormCRLF];
}

/**
 生成結束邊界字符串
 */
static inline NSString * AFMultipartFormFinalBoundary(NSString *boundary) {
    return [NSString stringWithFormat:@"%@--%@--%@", kAFMultipartFormCRLF, boundary, kAFMultipartFormCRLF];
}

/**
 根據文件後綴名獲取文件的MIME類型,即Content-Type字段的值
 */
static inline NSString * AFContentTypeForPathExtension(NSString *extension) {
    // 經過傳入的文件後綴字符串生成一個UTI字符串(統一類型標識符是惟一標識抽象類型的字符串。它們能夠用來描述文件格式或內存中的數據類型,但也能夠用來描述其餘類型的實體類型,如目錄,卷或包。)
    NSString *UTI = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)extension, NULL);
    // 將UTI轉成MIME類型
    NSString *contentType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)UTI, kUTTagClassMIMEType);
    if (!contentType) {
        return @"application/octet-stream";
    } else {
        return contentType;
    }
}
複製代碼

6.2.3 接口部分

6.2.3.1 屬性

/**
 編碼方式
 */
@property (nonatomic, assign) NSStringEncoding stringEncoding;

/**
 段落頭
 */
@property (nonatomic, strong) NSDictionary *headers;

/**
 邊界
 */
@property (nonatomic, copy) NSString *boundary;

/**
 內容
 */
@property (nonatomic, strong) id body;

/**
 內容長度
 */
@property (nonatomic, assign) unsigned long long bodyContentLength;

/**
 輸入流
 */
@property (nonatomic, strong) NSInputStream *inputStream;

/**
 是否有開始邊界
 */
@property (nonatomic, assign) BOOL hasInitialBoundary;

/**
 是否有結束邊界
 */
@property (nonatomic, assign) BOOL hasFinalBoundary;

/**
 內容長度
 */
@property (readonly, nonatomic, assign, getter = hasBytesAvailable) BOOL bytesAvailable;

/**
 內容長度
 */
@property (readonly, nonatomic, assign) unsigned long long contentLength;
複製代碼

6.2.3.2 方法

/**
 將AFHTTPBodyPart對象中的數據讀出,並寫入到buffer中,也就是AFHTTPBodyPart對象本身把本身保存的數據讀取出來,而後寫入到傳遞進來的參數buffer中
 */
- (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)length;
複製代碼

6.2.4 私有枚舉

typedef enum {
    AFEncapsulationBoundaryPhase = 1,    // 中間邊界段落
    AFHeaderPhase                = 2,    // 頭段落
    AFBodyPhase                  = 3,    // 內容段落
    AFFinalBoundaryPhase         = 4,    // 結束邊界段落
} AFHTTPBodyPartReadPhase;
複製代碼

6.2.5 類擴展部分

6.2.5.1 成員變量

/**
 保存要讀取的段落,其實就是利用狀態機模式控制對AFHTTPBodyPart對象不一樣內容的讀取
 */
AFHTTPBodyPartReadPhase _phase;

/**
 保存由AFHTTPBodyPart對象的body屬性生成的輸入流對象
 */
NSInputStream *_inputStream;

/**
 保存當前已讀取字節數,用來計算讀取進度
 */
unsigned long long _phaseReadOffset;
複製代碼

6.2.5.2 私有方法聲明

/**
 切換到下一段落進行讀取,即控制狀態機的狀態
 */
- (BOOL)transitionToNextPhase;
/**
 將AFHTTPBodyPart對象的屬性中保存的數據轉成的NSDdata對象寫入到buffer中
 */
- (NSInteger)readData:(NSData *)data
           intoBuffer:(uint8_t *)buffer
            maxLength:(NSUInteger)length;
複製代碼

6.2.6 實現部分

- (instancetype)init {
    self = [super init];
    if (!self) {
        return nil;
    }

    // 切換到主線程,初始化成員變量_phase爲AFEncapsulationBoundaryPhase,_phaseReadOffset爲0
    [self transitionToNextPhase];

    return self;
}

- (void)dealloc {
    // 關閉輸入流並置空
    if (_inputStream) {
        [_inputStream close];
        _inputStream = nil;
    }
}

/**
 inputStream的懶加載方法
 */
- (NSInputStream *)inputStream {
    if (!_inputStream) {
        // 根據body屬性的類生成對應的NSInputStream對象並保存
        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;
}

/**
 將headers屬性所保存的字典類型的數據拼接成指定格式的字符串
 */
- (NSString *)stringForHeaders {
    NSMutableString *headerString = [NSMutableString string];
    for (NSString *field in [self.headers allKeys]) {
        [headerString appendString:[NSString stringWithFormat:@"%@: %@%@", field, [self.headers valueForKey:field], kAFMultipartFormCRLF]];
    }
    [headerString appendString:kAFMultipartFormCRLF];

    return [NSString stringWithString:headerString];
}

/**
 獲取內容的總長度
 */
- (unsigned long long)contentLength {
    unsigned long long length = 0;

    // 若是有開始邊界就生成開始邊界字符串,不然就生成中間邊界字符串,而後生成對應的NSData對象,並獲取長度
    NSData *encapsulationBoundaryData = [([self hasInitialBoundary] ? AFMultipartFormInitialBoundary(self.boundary) : AFMultipartFormEncapsulationBoundary(self.boundary)) dataUsingEncoding:self.stringEncoding];
    length += [encapsulationBoundaryData length];

    // 添加header對應的NSData對象的長度
    NSData *headersData = [[self stringForHeaders] dataUsingEncoding:self.stringEncoding];
    length += [headersData length];

    // 添加body對應的NSData對象的長度
    length += _bodyContentLength;

    // 若是有結束邊界就生成結束邊界字符串,不然就生成中間邊界字符串,而後生成對應的NSData對象,並獲取長度後添加
    NSData *closingBoundaryData = ([self hasFinalBoundary] ? [AFMultipartFormFinalBoundary(self.boundary) dataUsingEncoding:self.stringEncoding] : [NSData data]);
    length += [closingBoundaryData length];

    return length;
}

/**
 判斷是否有可讀數據
 */
- (BOOL)hasBytesAvailable {
    // Allows `read:maxLength:` to be called again if `AFMultipartFormFinalBoundary` does not fit into the available buffer
    if (_phase == AFFinalBoundaryPhase) {
        return YES;
    }

    // 根據inputStream的屬性streamStatus來判斷是否有可讀數據
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wcovered-switch-default"
    switch (self.inputStream.streamStatus) {
        case NSStreamStatusNotOpen:
        case NSStreamStatusOpening:
        case NSStreamStatusOpen:
        case NSStreamStatusReading:
        case NSStreamStatusWriting:
            return YES;
        case NSStreamStatusAtEnd:
        case NSStreamStatusClosed:
        case NSStreamStatusError:
        default:
            return NO;
    }
#pragma clang diagnostic pop
}

/**
 將自身的數據寫入到buffer中
 */
- (NSInteger)read:(uint8_t *)buffer
        maxLength:(NSUInteger)length
{
    NSInteger totalNumberOfBytesRead = 0;

    // 若是要讀取的段落是中間邊界段落
    if (_phase == AFEncapsulationBoundaryPhase) {
        // 根據是否有開始邊界生成對應的邊界字符串,而後生成相應的NSData對象,寫入到butter中
        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) {
        // 將header編碼寫入到buffer中
        NSData *headersData = [[self stringForHeaders] dataUsingEncoding:self.stringEncoding];
        totalNumberOfBytesRead += [self readData:headersData intoBuffer:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)];
    }

    // 若是要讀取的段落是內容段落
    if (_phase == AFBodyPhase) {
        // 將屬性body中保存的數據轉爲NSInputStream對象再寫入到buffer中
        NSInteger numberOfBytesRead = 0;

        numberOfBytesRead = [self.inputStream read:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)];
        if (numberOfBytesRead == -1) {
            return -1;
        } else {
            totalNumberOfBytesRead += numberOfBytesRead;

            // 若是inputStream的狀態是結束、關閉或者出錯,就切換狀態機的狀態
            if ([self.inputStream streamStatus] >= NSStreamStatusAtEnd) {
                [self transitionToNextPhase];
            }
        }
    }

    // 若是要讀取的段落是結束邊界段落
    if (_phase == AFFinalBoundaryPhase) {
        // 根據是否有結束邊界生成對應的邊界字符串,而後生成相應的NSData對象,寫入到butter中
        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;
}

/**
 將data中的數據寫入到buffer中
 */
- (NSInteger)readData:(NSData *)data
           intoBuffer:(uint8_t *)buffer
            maxLength:(NSUInteger)length
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
    // 計算要讀取的範圍
    NSRange range = NSMakeRange((NSUInteger)_phaseReadOffset, MIN([data length] - ((NSUInteger)_phaseReadOffset), length));
    // 根據計算好的範圍讀寫
    [data getBytes:buffer range:range];
#pragma clang diagnostic pop

    // 記錄讀寫的進度
    _phaseReadOffset += range.length;

    // 若是data中的數據讀寫完成,就切換狀態機的狀態
    if (((NSUInteger)_phaseReadOffset) >= [data length]) {
        [self transitionToNextPhase];
    }

    return (NSInteger)range.length;
}

/**
 切換到下一段落進行讀取,即控制狀態機的狀態
 */
- (BOOL)transitionToNextPhase {
    // 若是該方法不是在主線程調用,就切換到主線程
    if (![[NSThread currentThread] isMainThread]) {
        dispatch_sync(dispatch_get_main_queue(), ^{
            [self transitionToNextPhase];
        });
        return YES;
    }

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wcovered-switch-default"
    // 根據目前正在讀取的段落,修改接下來要讀取的段落
    switch (_phase) {
        // 若是如今讀取的是中間邊界段落,接下來就要讀取頭部段落
        case AFEncapsulationBoundaryPhase:
            _phase = AFHeaderPhase;
            break;
        // 若是如今讀取的是頭部段落,接下來就要讀取內容段落,初始化inputStream添加到當前運行循環中,並開啓
        case AFHeaderPhase:
            [self.inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
            [self.inputStream open];
            _phase = AFBodyPhase;
            break;
        // 若是如今讀取的是內容段落,接下來就要讀取結束邊界段落,關閉inputStream
        case AFBodyPhase:
            [self.inputStream close];
            _phase = AFFinalBoundaryPhase;
            break;
        // 若是如今讀取的是結束邊界段落,就賦值爲中間邊界段落
        case AFFinalBoundaryPhase:
        default:
            _phase = AFEncapsulationBoundaryPhase;
            break;
    }
    
    // 段落讀取偏移量置零
    _phaseReadOffset = 0;
#pragma clang diagnostic pop

    return YES;
}
複製代碼

6.2.7 NSCopying協議方法的實現

- (instancetype)copyWithZone:(NSZone *)zone {
    AFHTTPBodyPart *bodyPart = [[[self class] allocWithZone:zone] init];

    // 複製了主要屬性
    bodyPart.stringEncoding = self.stringEncoding;
    bodyPart.headers = self.headers;
    bodyPart.bodyContentLength = self.bodyContentLength;
    bodyPart.body = self.body;
    bodyPart.boundary = self.boundary;

    return bodyPart;
}
複製代碼

6.3 AFMultipartBodyStream類

AFMultipartBodyStream類繼承自NSInputStream類,並遵照了NSStreamDelegate協議。這個類保存着用戶要上傳的數據,並在數據上傳時控制數據的讀取。

6.3.1 接口部分

6.3.1.1 屬性

/**
 單個包的大小
 */
@property (nonatomic, assign) NSUInteger numberOfBytesInPacket;

/**
 延時
 */
@property (nonatomic, assign) NSTimeInterval delay;

/**
 輸入流
 */
@property (nonatomic, strong) NSInputStream *inputStream;

/**
 內容大小
 */
@property (readonly, nonatomic, assign) unsigned long long contentLength;

/**
 是否爲空
 */
@property (readonly, nonatomic, assign, getter = isEmpty) BOOL empty;
複製代碼

6.3.1.2 方法

/**
 經過編碼方式初始化
 */
- (instancetype)initWithStringEncoding:(NSStringEncoding)encoding;

/**
 設置開始和結束邊界
 */
- (void)setInitialAndFinalBoundaries;

/**
 添加AFHTTPBodyPart對象
 */
- (void)appendHTTPBodyPart:(AFHTTPBodyPart *)bodyPart;
複製代碼

6.3.2 NSStream的類擴展

由於AFMultipartBodyStream類繼承自NSInputStream類,而NSInputStream繼承自NSStream類,但NSStream類的streamStatus屬性和streamError屬性是readonly,想要在AFMultipartBodyStream類內部使用讀寫這兩個屬性,因而添加了類擴展,改成私有可讀寫的。

@property (readwrite) NSStreamStatus streamStatus;
@property (readwrite, copy) NSError *streamError;
複製代碼

但這樣會出現一個問題:本來只要經過@property聲明屬性,編譯器就會自動幫咱們生成gettersetter和成員變量,可是子類經過@property覆蓋了父類的屬性,這時編譯器就不會自動生成成員變量,所以在AFMultipartBodyStream類的@implementation中能夠看到@synthesize streamStatus;@synthesize streamError;兩句代碼來生成成員變量;

6.3.3 類擴展

/**
 編碼方式
 */
@property (readwrite, nonatomic, assign) NSStringEncoding stringEncoding;

/**
 保存AFHTTPBodyPart的數組
 */
@property (readwrite, nonatomic, strong) NSMutableArray *HTTPBodyParts;

/**
 保存對屬性HTTPBodyParts內容的遍歷
 */
@property (readwrite, nonatomic, strong) NSEnumerator *HTTPBodyPartEnumerator;

/**
 當前讀寫的HTTPBodyPart
 */
@property (readwrite, nonatomic, strong) AFHTTPBodyPart *currentHTTPBodyPart;

/**
 輸出流
 */
@property (readwrite, nonatomic, strong) NSOutputStream *outputStream;

/**
 緩衝
 */
@property (readwrite, nonatomic, strong) NSMutableData *buffer;
複製代碼

6.3.4 實現部分

// 這三個屬性在6.3.2已經解釋了
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wimplicit-atomic-properties"
#if (defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000) || (defined(__MAC_OS_X_VERSION_MAX_ALLOWED) && __MAC_OS_X_VERSION_MAX_ALLOWED >= 1100)
@synthesize delegate;
#endif
@synthesize streamStatus;
@synthesize streamError;
#pragma clang diagnostic pop

- (instancetype)initWithStringEncoding:(NSStringEncoding)encoding {
    self = [super init];
    if (!self) {
        return nil;
    }

    // 保存傳入的參數和初始化屬性
    self.stringEncoding = encoding;
    self.HTTPBodyParts = [NSMutableArray array];
    self.numberOfBytesInPacket = NSIntegerMax;

    return self;
}

- (void)setInitialAndFinalBoundaries {
    // 若是屬性HTTPBodyParts內有元素,就將第一個元素設置爲有開始邊界,最後一個元素設置爲有結束邊界,其餘元素都設置爲無
    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];
    }
}

- (void)appendHTTPBodyPart:(AFHTTPBodyPart *)bodyPart {
    // 向HTTPBodyParts屬性內添加元素
    [self.HTTPBodyParts addObject:bodyPart];
}

- (BOOL)isEmpty {
    // 判斷HTTPBodyParts屬性內是否有元素
    return [self.HTTPBodyParts count] == 0;
}  
複製代碼

6.3.5 對父類NSInputStream方法的重寫

- (NSInteger)read:(uint8_t *)buffer
        maxLength:(NSUInteger)length
{
    // 若是輸入流的狀態是關閉就結束
    if ([self streamStatus] == NSStreamStatusClosed) {
        return 0;
    }

    // 定義變量記錄已讀取總數
    NSInteger totalNumberOfBytesRead = 0;

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
    // 只要已讀取的數量小於限定的數量和包的總數量兩者中的最小值
    while ((NSUInteger)totalNumberOfBytesRead < MIN(length, self.numberOfBytesInPacket)) {
        // 若是當前HTTPBodyPart爲空或者沒有可讀數據
        if (!self.currentHTTPBodyPart || ![self.currentHTTPBodyPart hasBytesAvailable]) {
            // 爲currentHTTPBodyPart賦值,但若是下一個元素爲空則跳出循環
            if (!(self.currentHTTPBodyPart = [self.HTTPBodyPartEnumerator nextObject])) {
                break;
            }
        // 若是當前HTTPBodyPart有值
        } else {
            // 計算還能讀取的最大數量
            NSUInteger maxLength = MIN(length, self.numberOfBytesInPacket) - (NSUInteger)totalNumberOfBytesRead;
            // 將currentHTTPBodyPart中的數據寫入到buffer中
            NSInteger numberOfBytesRead = [self.currentHTTPBodyPart read:&buffer[totalNumberOfBytesRead] maxLength:maxLength];
            // 若是寫入失敗
            if (numberOfBytesRead == -1) {
                // 記錄錯誤並跳出循環
                self.streamError = self.currentHTTPBodyPart.inputStream.streamError;
                break;
            } else {
                // 記錄當前已讀總數
                totalNumberOfBytesRead += numberOfBytesRead;

                // 若是設置了延時,就在當前線程延時一段時間
                if (self.delay > 0.0f) {
                    [NSThread sleepForTimeInterval:self.delay];
                }
            }
        }
    }
#pragma clang diagnostic pop

    return totalNumberOfBytesRead;
}

- (BOOL)getBuffer:(__unused uint8_t **)buffer
           length:(__unused NSUInteger *)len
{
    // 關閉讀取緩存的方法
    return NO;
}

- (BOOL)hasBytesAvailable {
    // 只要狀態爲開就是有數據
    return [self streamStatus] == NSStreamStatusOpen;
}

複製代碼

6.3.6 對父類NSInputStream的父類NSStream方法的重寫

- (void)open {
    // 若是流的狀態是打開就不繼續執行
    if (self.streamStatus == NSStreamStatusOpen) {
        return;
    }

    // 將流的狀態設置爲打開
    self.streamStatus = NSStreamStatusOpen;

    // 設置開始和結束邊界
    [self setInitialAndFinalBoundaries];
    // 初始化HTTPBodyPartEnumerator屬性
    self.HTTPBodyPartEnumerator = [self.HTTPBodyParts objectEnumerator];
}

- (void)close {
    // 將流的狀態設置爲關閉
    self.streamStatus = NSStreamStatusClosed;
}

- (id)propertyForKey:(__unused NSString *)key {
    // 關閉對key屬性的查詢
    return nil;
}

- (BOOL)setProperty:(__unused id)property
             forKey:(__unused NSString *)key
{
    // 關閉對key屬性的賦值
    return NO;
}

// 將設置和移除運行環境的方法設置爲何都不作
- (void)scheduleInRunLoop:(__unused NSRunLoop *)aRunLoop
                  forMode:(__unused NSString *)mode
{}

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

- (unsigned long long)contentLength {
    // 遍歷HTTPBodyParts中的元素計算總長度
    unsigned long long length = 0;
    for (AFHTTPBodyPart *bodyPart in self.HTTPBodyParts) {
        length += [bodyPart contentLength];
    }

    return length;
}
複製代碼

6.3.6 對父類NSInputStream的父類NSStream私有方法的重寫

爲何要重寫私有方法?由於NSMutableURLRequestsetHTTPBodyStream方法接受的是一個NSInputStream *參數,那咱們要自定義NSInputStream的話,建立一個NSInputStream的子類傳給它是否是就能夠了?實際上不行,這樣作後用NSMutableURLRequest發出請求會致使crash,提示[xx _scheduleInCFRunLoop:forMode:]: unrecognized selector

這是由於NSMutableURLRequest實際上接受的不是NSInputStream對象,而是CoreFoundationCFReadStreamRef對象,因爲CFReadStreamRefNSInputStreamtoll-free bridged,能夠自由轉換,但CFReadStreamRef會用到CFStreamScheduleWithRunLoop這個方法,當它調用到這個方法時,object-ctoll-free bridging機制會調用object-c對象NSInputStream的相應函數,這裏就調用到了_scheduleInCFRunLoop:forMode:,若不實現這個方法就會crash。以上解釋摘自這篇博客

- (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;
}
複製代碼

6.3.7 NSCopying協議方法的實現

- (instancetype)copyWithZone:(NSZone *)zone {
    // 拷貝了HTTPBodyParts並設置了啓示和結束邊界
    AFMultipartBodyStream *bodyStreamCopy = [[[self class] allocWithZone:zone] initWithStringEncoding:self.stringEncoding];

    for (AFHTTPBodyPart *bodyPart in self.HTTPBodyParts) {
        [bodyStreamCopy appendHTTPBodyPart:[bodyPart copy]];
    }

    [bodyStreamCopy setInitialAndFinalBoundaries];

    return bodyStreamCopy;
}
複製代碼

6.4 AFStreamingMultipartFormData類

這個類的做用是提供接口以便用戶添加上傳的數據。

當用戶添加數據時,該類會將用戶想要上傳的數據分別轉成AFHTTPBodyPart對象,而後依次保存到自身AFMultipartBodyStream *類型的屬性bodyStream中,當數據添加完成,就會將屬性bodyStream賦值給NSMutableURLRequestHTTPBodyStream屬性。

6.4.1 接口部分

/**
 經過傳遞請求和編碼方式進行初始化
 */
- (instancetype)initWithURLRequest:(NSMutableURLRequest *)urlRequest
                    stringEncoding:(NSStringEncoding)encoding;

/**
 返回最終處理好的NSMutableURLRequest
 */
- (NSMutableURLRequest *)requestByFinalizingMultipartFormData;
複製代碼

6.4.2 類擴展部分

/**
 保存傳入的NSMutableURLRequest對象
 */
@property (readwrite, nonatomic, copy) NSMutableURLRequest *request;

/**
 保存傳入的編碼方式
 */
@property (readwrite, nonatomic, assign) NSStringEncoding stringEncoding;

/**
 保存邊界字符串
 */
@property (readwrite, nonatomic, copy) NSString *boundary;

/**
 保存輸入數據流
 */
@property (readwrite, nonatomic, strong) AFMultipartBodyStream *bodyStream;
複製代碼

6.4.3 實現部分

- (instancetype)initWithURLRequest:(NSMutableURLRequest *)urlRequest
                    stringEncoding:(NSStringEncoding)encoding
{
    self = [super init];
    if (!self) {
        return nil;
    }

    // 保存傳入的參數,初始化私有屬性
    self.request = urlRequest;
    self.stringEncoding = encoding;
    self.boundary = AFCreateMultipartFormBoundary();
    self.bodyStream = [[AFMultipartBodyStream alloc] initWithStringEncoding:encoding];

    return self;
}

- (NSMutableURLRequest *)requestByFinalizingMultipartFormData {
    // 若是沒有數據流就直接返回NSMutableURLRequest對象
    if ([self.bodyStream isEmpty]) {
        return self.request;
    }

    // 設置數據流的開始和結束邊界
    // Reset the initial and final boundaries to ensure correct Content-Length
    [self.bodyStream setInitialAndFinalBoundaries];
    // 將數據流賦值給NSMutableURLRequest對象
    [self.request setHTTPBodyStream:self.bodyStream];

    // 爲NSMutableURLRequest對象的請求頭的Content-Type和Content-Length字段賦值
    [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;
}
複製代碼

6.4.4 AFMultipartFormData協議方法的實現

- (BOOL)appendPartWithFileURL:(NSURL *)fileURL
                         name:(NSString *)name
                        error:(NSError * __autoreleasing *)error
{
    // 在debug模式下缺乏對應參數會crash
    NSParameterAssert(fileURL);
    NSParameterAssert(name);

    // 經過文件的路徑中獲取帶有後綴的文件名
    NSString *fileName = [fileURL lastPathComponent];
    // 經過文件的路徑獲取不帶「.」的後綴名後獲取文件的mime類型
    NSString *mimeType = AFContentTypeForPathExtension([fileURL pathExtension]);

    // 調用下面那個方法
    return [self appendPartWithFileURL:fileURL name:name fileName:fileName mimeType:mimeType error:error];
}

- (BOOL)appendPartWithFileURL:(NSURL *)fileURL
                         name:(NSString *)name
                     fileName:(NSString *)fileName
                     mimeType:(NSString *)mimeType
                        error:(NSError * __autoreleasing *)error
{
    // 在debug模式下缺乏對應參數會crash
    NSParameterAssert(fileURL);
    NSParameterAssert(name);
    NSParameterAssert(fileName);
    NSParameterAssert(mimeType);

    // 若是不是一個合法的文件路徑
    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;
    // 若是文件路徑沒法訪問
    } 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;
    }

    // 生成一個可變字典保存請求頭的相關信息,併爲Content-Disposition和Content-Type字段賦值
    NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary];
    [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"; filename=\"%@\"", name, fileName] forKey:@"Content-Disposition"];
    [mutableHeaders setValue:mimeType forKey:@"Content-Type"];

    // 生成一個AFHTTPBodyPart對象保存要傳輸的內容,並添加到私有屬性bodyStream中
    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;
}

- (void)appendPartWithInputStream:(NSInputStream *)inputStream
                             name:(NSString *)name
                         fileName:(NSString *)fileName
                           length:(int64_t)length
                         mimeType:(NSString *)mimeType
{
    // 在debug模式下缺乏對應參數會crash
    NSParameterAssert(name);
    NSParameterAssert(fileName);
    NSParameterAssert(mimeType);

    // 生成一個可變字典保存請求頭的相關信息,併爲Content-Disposition和Content-Type字段賦值
    NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary];
    [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"; filename=\"%@\"", name, fileName] forKey:@"Content-Disposition"];
    [mutableHeaders setValue:mimeType forKey:@"Content-Type"];

    // 生成一個AFHTTPBodyPart對象保存要傳輸的內容,並添加到私有屬性bodyStream中
    AFHTTPBodyPart *bodyPart = [[AFHTTPBodyPart alloc] init];
    bodyPart.stringEncoding = self.stringEncoding;
    bodyPart.headers = mutableHeaders;
    bodyPart.boundary = self.boundary;
    bodyPart.body = inputStream;

    bodyPart.bodyContentLength = (unsigned long long)length;

    [self.bodyStream appendHTTPBodyPart:bodyPart];
}

- (void)appendPartWithFileData:(NSData *)data
                          name:(NSString *)name
                      fileName:(NSString *)fileName
                      mimeType:(NSString *)mimeType
{
    // 在debug模式下缺乏對應參數會crash
    NSParameterAssert(name);
    NSParameterAssert(fileName);
    NSParameterAssert(mimeType);

    // 生成一個可變字典保存請求頭的相關信息,併爲Content-Disposition和Content-Type字段賦值
    NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary];
    [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"; filename=\"%@\"", name, fileName] forKey:@"Content-Disposition"];
    [mutableHeaders setValue:mimeType forKey:@"Content-Type"];

    // 調用下下面的那個方法
    [self appendPartWithHeaders:mutableHeaders body:data];
}

- (void)appendPartWithFormData:(NSData *)data
                          name:(NSString *)name
{
    // 在debug模式下缺乏對應參數會crash
    NSParameterAssert(name);

    // 生成一個可變字典保存請求頭的相關信息,併爲Content-Disposition和Content-Type字段賦值
    NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary];
    [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"", name] forKey:@"Content-Disposition"];

    // 調用下面的那個方法
    [self appendPartWithHeaders:mutableHeaders body:data];
}

- (void)appendPartWithHeaders:(NSDictionary *)headers
                         body:(NSData *)body
{
    // 在debug模式下缺乏對應參數會crash
    NSParameterAssert(body);

    // 生成一個AFHTTPBodyPart對象保存要傳輸的內容,並添加到私有屬性bodyStream中
    AFHTTPBodyPart *bodyPart = [[AFHTTPBodyPart alloc] init];
    bodyPart.stringEncoding = self.stringEncoding;
    bodyPart.headers = headers;
    bodyPart.boundary = self.boundary;
    bodyPart.bodyContentLength = [body length];
    bodyPart.body = body;

    [self.bodyStream appendHTTPBodyPart:bodyPart];
}

- (void)throttleBandwidthWithPacketSize:(NSUInteger)numberOfBytes
                                  delay:(NSTimeInterval)delay
{
    // 設置發送單個包的大小和請求延遲
    self.bodyStream.numberOfBytesInPacket = numberOfBytes;
    self.bodyStream.delay = delay;
}
複製代碼

7. 總結

經過對AFURLRequestSerialization源碼的閱讀,能夠看出AFURLRequestSerialization這個類是利用用戶傳入的各類參數來實例化NSMutableURLRequest對象。但這還分爲兩個部分,一個部分是構建普通的請求:如GETPOST;另外一部分是構建multipart/form-data請求。

7.1 普通請求

普通請求的過程是:設置HTTP請求頭、設置mutableRequest的一些屬性、參數編碼、查詢參數拼接

7.1.1 設置HTTP請求頭

- (instancetype)init方法中分別設置HTTP請求頭Accept-Language字段和User-Agent字段。

- (nullable NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request withParameters:(nullable id)parameters error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;方法中將用戶自定義的屬性賦值給mutableRequest對應的字段。

若是請求方式是POST或者PUT,還會在- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request withParameters:(id)parameters error:(NSError *__autoreleasing *)error方法中設置Content-Type字段。

除此以外,用戶還能夠經過暴露的接口爲Authorization字段賦值

7.1.2 設置mutableRequest的一些屬性

首先,一樣在- (instancetype)init方法中,對自身的屬性allowsCellularAccesscachePolicyHTTPShouldHandleCookiesHTTPShouldUsePipeliningnetworkServiceTypetimeoutInterval設置了KVO。

而後,在KVO方法回調中監聽用戶自定義了哪一個屬性,保存對應的keyvalue

最後在- (NSMutableURLRequest *)requestWithMethod:(NSString *)method URLString:(NSString *)URLString parameters:(nullable id)parameters error:(NSError * _Nullable __autoreleasing *)error;方法中將value賦值給mutableRequest對應的key,也就是爲mutableRequest的屬性賦值。

7.1.3 參數編碼

參數編碼提供了三種方式,分別是key0=value0&key1=value1百分號編碼方式、json編碼方法和plist編碼方式,這三種方式能夠經過分別實例化AFHTTPRequestSerializer對象、AFJSONRequestSerializer對象和AFPropertyListRequestSerializer對象來實現。

當時也能夠經過調用- (void)setQueryStringSerializationWithBlock:(nullable NSString * (^)(NSURLRequest *request, id parameters, NSError * __autoreleasing *error))block;方法,實現block,來自定義編碼方式。

不過若是請求方式是GETHEAD或者DELETE,只能經過百分號編碼方式和自定義編碼方式來進行編碼

7.1.4 查詢參數拼接

參數拼接分爲兩種狀況:

第一種,若是請求方式是GETHEADDELETE,就把處理好的查詢參數直接拼接到url後面;

第二種,請求方式是POSTPUT,就把處理好的參數轉成NSData對象後拼接到請求體中。

7.2 multipart/form-data請求

multipart/form-data請求的過程是:設置HTTP請求頭、設置mutableRequest的一些屬性、實例化AFStreamingMultipartFormData對象處理數據、上傳時讀取數據

7.2.1 設置HTTP請求頭

同7.1.1

7.2.2 設置mutableRequest的一些屬性

同7.1.2

7.2.3 實例化AFStreamingMultipartFormData對象處理數據

首先,在- (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對象formData

而後,將用戶傳入的參數通過解析後轉爲NSData類型的數據,添加到formData中;

接着,經過block回調,將formData對象暴露給用戶,用戶經過AFStreamingMultipartFormData類提供的接口,將想要上傳的數據添加到formData中;

其中,formData會將傳入的數據,分別轉成AFHTTPBodyPart對象,而後依次添加到自身屬性bodyStream中,bodyStream會將添加進來的AFHTTPBodyPart對象用數組保存起來;

最後,當數據添加處理完成,formData就會將bodyStream賦值給mutableRequest``HTTPBodyStream屬性,等待數據的讀取。

7.2.4 上傳時讀取數據

當發送請求上傳時,NSURLSession對象會不斷讀取NSURLRequest對象的屬性HTTPBodyStream中的數據,在讀取數據時會調用NSInputStream對象的- (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)len;方法;

AFMultipartBodyStream類中,重寫了其父類的這個方法,在這其中,經過遍歷屬性HTTPBodyParts中的數據,將AFHTTPBodyPart對象中保存的數據讀取出來;

AFHTTPBodyPart對象經過調用- (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)length;方法,並經過狀態機控制對不一樣部分的數據的處理,數據邊讀取邊拼接成帶有格式的字符串,而後再轉換成NSData類型的數據寫入到buffer中;

除此以外,還能夠經過調用AFStreamingMultipartFormData類遵照的代理AFMultipartFormData中的方法- (void)throttleBandwidthWithPacketSize:(NSUInteger)numberOfBytes delay:(NSTimeInterval)delay;來設置在上傳數據時,每次發送數據包的最大大小和每次讀取數據包的延遲時間。

源碼閱讀系列:AFNetworking

源碼閱讀:AFNetworking(一)——從使用入手

源碼閱讀:AFNetworking(二)——AFURLRequestSerialization

源碼閱讀:AFNetworking(三)——AFURLResponseSerialization

源碼閱讀:AFNetworking(四)——AFSecurityPolicy

源碼閱讀:AFNetworking(五)——AFNetworkReachabilityManager

源碼閱讀:AFNetworking(六)——AFURLSessionManager

源碼閱讀:AFNetworking(七)——AFHTTPSessionManager

源碼閱讀:AFNetworking(八)——AFAutoPurgingImageCache

源碼閱讀:AFNetworking(九)——AFImageDownloader

源碼閱讀:AFNetworking(十)——AFNetworkActivityIndicatorManager

源碼閱讀:AFNetworking(十一)——UIActivityIndicatorView+AFNetworking

源碼閱讀:AFNetworking(十二)——UIButton+AFNetworking

源碼閱讀:AFNetworking(十三)——UIImageView+AFNetworking

源碼閱讀:AFNetworking(十四)——UIProgressView+AFNetworking

源碼閱讀:AFNetworking(十五)——UIRefreshControl+AFNetworking

源碼閱讀:AFNetworking(十六)——UIWebView+AFNetworking

相關文章
相關標籤/搜索