AFNetworking之AFURLRequestSerialization深入學習

OneAlon發表於2018-01-31

AFURLRequestSerialization主要是對請求進行編碼.

1. AFURLRequestSerialization協議

AFURLRequestSerialization是一個協議, 請求序列化將引數編碼為查詢字串、HTTP主體、必要時設定適當的HTTP頭欄位. AFURLRequestSerialization協議中宣告瞭一個方法:

- (nullable NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
                               withParameters:(nullable id)parameters
                                        error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;
複製程式碼

根據指定的引數parametersrequest進行編碼, 並將編碼以後的request進行返回.

AFURLRequestSerialization檔案中宣告瞭三種序列化器, 分別為: AFHTTPRequestSerializer實現了AFURLRequestSerialization協議, 查詢字串/URL表單編碼的引數序列化和預設的請求頭,以及響應狀態程式碼和內容型別驗證. AFJSONRequestSerializerAFHTTPRequestSerializer的一個子類, 將parameters引數使用NSJSONSerialization序列化為JSON, 並且設定Content-Typeapplication/json. AFPropertyListRequestSerializerAFHTTPRequestSerializer的一個子類, 將parameters引數使用NSPropertyListSerializer序列化為JSON, 並且設定Content-Typeapplication/x-plist.

2. 對外提供的序列化函式

FOUNDATION_EXPORT NSString * AFPercentEscapedStringFromString(NSString *string);
FOUNDATION_EXPORT NSString * AFQueryStringFromParameters(NSDictionary *parameters);
複製程式碼

個人理解為對外提供了兩個全域性函式 AFPercentEscapedStringFromString:將指定的string字串進行百分號編碼. AFQueryStringFromParameters:將指定的parameters字典轉換為查詢字串,

3. 在AFN中的使用

我們來看一下AFN是如何使用AFHTTPRequestSerializer進行編碼的, 在AFHTTPSessionManagerdataTaskWithHTTPMethod方法中進行構建NSMutableURLRequest的方法如下.

- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
                                       URLString:(NSString *)URLString
                                      parameters:(id)parameters
                                  uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress
                                downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress
                                         success:(void (^)(NSURLSessionDataTask *, id))success
                                         failure:(void (^)(NSURLSessionDataTask *, NSError *))failure
{
    NSError *serializationError = nil;
    NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
  
    ......
}
複製程式碼

self.requestSerializer如果沒有特殊設定這裡預設的是AFHTTPRequestSerializer, 所以這裡我們看一下AFHTTPRequestSerializer的序列化方法.

4. AFHTTPRequestSerializer構建

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

    self.stringEncoding = NSUTF8StringEncoding;

    // 儲存請求頭的字典
    self.mutableHTTPRequestHeaders = [NSMutableDictionary dictionary];
    // 請求頭改變的時候在本佇列執行, 並行佇列
    self.requestHeaderModificationQueue = dispatch_queue_create("requestHeaderModificationQueue", DISPATCH_QUEUE_CONCURRENT);

    // Accept-Language HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
    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"];

    NSString *userAgent = nil;
#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
    if (userAgent) {
        if (![userAgent canBeConvertedToEncoding:NSASCIIStringEncoding]) {
            NSMutableString *mutableUserAgent = [userAgent mutableCopy];
            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
    self.HTTPMethodsEncodingParametersInURI = [NSSet setWithObjects:@"GET", @"HEAD", @"DELETE", nil];

    self.mutableObservedChangedKeyPaths = [NSMutableSet set];
    for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
        if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
            [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
        }
    }
    
    return self;
}
複製程式碼

AFHTTPRequestSerializer的建立也是很重要的一部分. stringEncoding指定預設的編碼方式為UTF8編碼.

mutableHTTPRequestHeaders儲存了我們修改的請求頭的資訊, 當對請求頭做修改時會在requestHeaderModificationQueue並行佇列中執行, 將修改的資訊儲存在mutableHTTPRequestHeaders字典中, 在呼叫requestBySerializingRequest對請求編碼的時候遍歷這個字典, 給request設定header.

acceptLanguagesComponents表示客戶端支援的語言. userAgent將客戶端的環境通過User-Agent欄位傳給伺服器. 通過AFHTTPRequestSerializerObservedKeyPaths函式獲取AFN監聽哪些頭部欄位的變化, 並且提供監聽方法, 在監聽方法中如果有值改變, 就賦值給mutableHTTPRequestHeaders字典.

5. AFURLRequestSerialization序列化方法

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

    NSMutableURLRequest *mutableRequest = [request mutableCopy];
    
    // 從自己的head遍歷, 如果有值就給request的header賦值
    [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
        if (![request valueForHTTPHeaderField:field]) {
            [mutableRequest setValue:value forHTTPHeaderField:field];
        }
    }];

    // 把各種型別的引數, 轉成string
    NSString *query = nil;
    if (parameters) {
        // 按照自定義的方式解析
        if (self.queryStringSerialization) {
            NSError *serializationError;
            query = self.queryStringSerialization(request, parameters, &serializationError);

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

                return nil;
            }
        } else {
            // 預設的方式解析引數
            switch (self.queryStringSerializationStyle) {
                case AFHTTPRequestQueryStringDefaultStyle:
                    // 將傳入的paramter引數, 用=號連結(key1=name1&key2=name2)
                    query = AFQueryStringFromParameters(parameters);
                    break;
            }
        }
    }

    // HTTPMethodsEncodingParametersInURI, 預設為`GET`, `HEAD`, and `DELETE`
    if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
        if (query && query.length > 0) {
            // 拼接URL
            mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
        }
    } else {
        // #2864: an empty string is a valid x-www-form-urlencoded payload
        if (!query) {
            query = @"";
        }
        if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
            [mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
        }
        [mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
    }

    return mutableRequest;
}
複製程式碼

將傳入的parameters引數序列化到request中. 先遍歷HTTPRequestHeaders字典, 將遍歷到的值設定到request請求頭中. 如果有自定的queryStringSerialization序列化方式, 就執行自定義, 否則按照預設的方式序列化. 這裡呼叫了AFN的自定義函式AFQueryStringFromParameters進行序列化, 下邊會講解這個函式. 比如我們傳進來的引數是@{@"key1" : @"name1", @"key2" : @"name2", @"key3" : @"哈哈"}, 引數會經過百分號編碼, 執行AFQueryStringFromParameters序列化之後是key1=name1&key2=name2&key3=%E5%93%88%E5%93%88.

如果request的HTTPMethod是GET, HEAD或者DELETE, 就把經過序列化以後的查詢字串拼接到request的URL中. 否則將查詢字串query設定到request的請求體中, 至此request請求設定完畢.

6.AFURLRequestSerialization中的輔助函式

NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
    NSMutableArray *mutablePairs = [NSMutableArray array];
    for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
        [mutablePairs addObject:[pair URLEncodedStringValue]];
    }

    return [mutablePairs componentsJoinedByString:@"&"];
}

NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) {
    return AFQueryStringPairsFromKeyAndValue(nil, dictionary);
}

/*{
 @"key1" : @"value1",
 @"key2" : @"value2"
 }*/

/**
 對引數進行轉碼,
 */
NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) {
    NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];

    // 將引數升序進行排序
    NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)];

    if ([value isKindOfClass:[NSDictionary class]]) {
        NSDictionary *dictionary = value;
        // Sort dictionary keys to ensure consistent ordering in query string, which is important when deserializing potentially ambiguous sequences, such as an array of dictionaries
        for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
            id nestedValue = dictionary[nestedKey];
            if (nestedValue) {
                [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)];
            }
        }
    } else if ([value isKindOfClass:[NSArray class]]) {
        NSArray *array = value;
        for (id nestedValue in array) {
            [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[]", key], nestedValue)];
        }
    } else if ([value isKindOfClass:[NSSet class]]) {
        NSSet *set = value;
        for (id obj in [set sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
            [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue(key, obj)];
        }
    } else {
        [mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]];
    }

    return mutableQueryStringComponents;
}
複製程式碼

相關文章