原始碼閱讀:AFNetworking(二)——AFURLRequestSerialization

堯少羽發表於2019-02-28

該文章閱讀的AFNetworking的版本為3.2.0。

AFHTTPRequestSerializer這個類是用來構建NSMutableURLRequest,主要做了請求資料序列化,也就是利用傳遞來的HTTP請求方法(如:GET)method、請求URLURLString和請求引數parameters來例項化NSMutableURLRequest類的物件request

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作為引數傳遞過去

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

下面這個方法就是對字典進行處理,轉成元素為AFQueryStringPair物件的陣列

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.一個列舉

緊接著可以看到一個列舉,定義了請求查詢欄位的編碼方式,不過目前只定義了一種預設方式

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

4.三個類

可以在.h檔案中看到一共有三個類,分別是AFHTTPRequestSerializer和它的兩個子類AFJSONRequestSerializerAFPropertyListRequestSerializer,先來看一下AFHTTPRequestSerializer這個類

4.1 AFHTTPRequestSerializer類

先在.h檔案中看一下對外暴漏的介面部分

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 = @"
";

/**
 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

相關文章