AFNetworking主線梳理(二)

上一篇整理了AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];,這一篇整理AFN的重頭戲:請(qǐng)求。從[manager POST:parameters:progress:success:failure:]開(kāi)始梳理。


AFHTTPSessionManager 部分

日常開(kāi)發(fā)中請(qǐng)求主要就是GET和POST兩種

GET 和 POST 的聲明

GET聲明

- (nullable NSURLSessionDataTask *)GET:(NSString *)URLString
                            parameters:(nullable id)parameters
                              progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgress
                               success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
                               failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;

POST聲明

- (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
                             parameters:(nullable id)parameters
                               progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress
                                success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
                                failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;

GET 和 POST 的實(shí)現(xiàn)

GET實(shí)現(xiàn)

- (NSURLSessionDataTask *)GET:(NSString *)URLString
                   parameters:(id)parameters
                     progress:(void (^)(NSProgress * _Nonnull))downloadProgress
                      success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success
                      failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure
{

    NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET"
                                                        URLString:URLString
                                                       parameters:parameters
                                                   uploadProgress:nil
                                                 downloadProgress:downloadProgress
                                                          success:success
                                                          failure:failure];

    [dataTask resume];

    return dataTask;
}

POST實(shí)現(xiàn)

- (NSURLSessionDataTask *)POST:(NSString *)URLString
                    parameters:(id)parameters
                      progress:(void (^)(NSProgress * _Nonnull))uploadProgress
                       success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success
                       failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure
{
    NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"POST" URLString:URLString parameters:parameters uploadProgress:uploadProgress downloadProgress:nil success:success failure:failure];

    [dataTask resume];

    return dataTask;
}

分解說(shuō)明

  1. 首先要理解,AFHTTPSessionManager類的GET和POST方法內(nèi)部實(shí)現(xiàn)的核心是:調(diào)用dataTaskWithHTTPMethod:URLString:parameters:uploadProgress:downloadProgress:success:failure:方法來(lái)獲取NSURLSessionDataTask。
  2. 這個(gè)NSURLSessionDataTask才是真正可以發(fā)起網(wǎng)絡(luò)請(qǐng)求的類。而開(kāi)啟請(qǐng)求的真正方法是resume,當(dāng)NSURLSessionDataTask調(diào)用了resume,網(wǎng)絡(luò)請(qǐng)求才發(fā)起。

?

GET、POST 核心方法dataTaskWithHTTPMethod:URLString:parameters:uploadProgress:downloadProgress:success:failure:

dataTaskWithHTTPMethod...的實(shí)現(xiàn)

第一部分解析

一句話描述 : 第一部分做的事情就是創(chuàng)建一個(gè)NSMutableURLRequest。

NSError *serializationError = nil;
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
if (serializationError) {
    if (failure) {
        dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
            failure(nil, serializationError);
        });
    }

    return nil;
}

先說(shuō)整體邏輯

  1. 第一部分創(chuàng)建的NSMutableURLRequest是要提供給第二部分使用。
  2. 這個(gè)NSMutableURLRequest不是AFHTTPSessionManager類自己創(chuàng)建的,是通過(guò)自己的屬性requestSerializer調(diào)用requestWithMethod:URLString:parameters:error:方法創(chuàng)建的。如果NSMutableURLRequest創(chuàng)建失敗,則直接回調(diào)給開(kāi)發(fā)者網(wǎng)絡(luò)請(qǐng)求失敗。

分解說(shuō)明:

  1. 屬性requestSerializer:requestSerializer是AFHTTPSessionManager類自己獨(dú)有的屬性,父類AFHTTPRequestSerializer沒(méi)有這個(gè)屬性。requestSerializer在AFHTTPSessionManager初始化的時(shí)候已經(jīng)默認(rèn)進(jìn)行了初始化,開(kāi)發(fā)者也可以隨時(shí)修改此屬性。初始化詳情可以參考AFNetworking主線梳理(一)中的介紹。

  2. 參數(shù)completionQueue:失敗回調(diào)的隊(duì)列默認(rèn)是主隊(duì)列,如果設(shè)置了completionQueue,則走設(shè)置的隊(duì)列。completionQueue是AFHTTPSessionManager父類AFURLSessionManager的屬性,需要開(kāi)發(fā)者來(lái)設(shè)置。

?
接下來(lái),解析requestWithMethod:URLString:parameters:error:,需要跳轉(zhuǎn)到AFHTTPRequestSerializer 部分(往下翻)。
?

第二部分解析

一句話描述:
通過(guò)調(diào)用dataTaskWithRequest:uploadProgress:downloadProgress:completionHandler:方法來(lái)創(chuàng)建一個(gè)NSURLSessionDataTask實(shí)例對(duì)象。

__block NSURLSessionDataTask *dataTask = nil;
dataTask = [self dataTaskWithRequest:request
                      uploadProgress:uploadProgress
                    downloadProgress:downloadProgress
                   completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
    if (error) {
        if (failure) {
            failure(dataTask, error);
        }
    } else {
        if (success) {
            success(dataTask, responseObject);
        }
    }
}];

?
分解說(shuō)明

  1. NSURLSessionDataTask就是前面說(shuō)的GET和POST方法核心實(shí)現(xiàn)所需要的task。
  2. dataTaskWithRequest:uploadProgress:downloadProgress:completionHandler:方法所需要的參數(shù)request就是第一部分創(chuàng)建的NSMutableURLRequest實(shí)例。
  3. AFHTTPSessionManager自己并沒(méi)有實(shí)現(xiàn)dataTaskWithRequest:uploadProgress:downloadProgress:completionHandler:方法,是它的父類AFURLSessionManager實(shí)現(xiàn)了這個(gè)方法。

?
接下來(lái),解析dataTaskWithRequest:uploadProgress:downloadProgress:completionHandler:方法,跳轉(zhuǎn)到AFURLSessionManager 部分(使勁往下翻)。
?


AFHTTPRequestSerializer 部分

接續(xù) GET、POST 核心方法dataTaskWithHTTPMethod 方法第一部分,AFHTTPRequestSerializer的requestWithMethod:URLString:parameters:error:方法最終目的是創(chuàng)建一個(gè)NSMutableURLRequest實(shí)例并返回。

requestWithMethod:URLString:parameters:error: 方法解析

以下簡(jiǎn)稱:requestWithMethod方法。

requestWithMethod...方法的實(shí)現(xiàn)

requestWithMethod方法整體邏輯:

  1. 使用傳入的參數(shù)URLString創(chuàng)建一個(gè)NSURL *url
  2. 使用url創(chuàng)建一個(gè)NSMutableURLRequest *mutableRequest
  3. 將傳入的參數(shù) method 保存到 mutableRequest 的 HTTPMethod 屬性中
  4. 第一部分:簡(jiǎn)單講就是在配置NSMutableURLRequest的各種參數(shù)。
  5. 第二部分:簡(jiǎn)單講其實(shí)也是在配置NSMutableURLRequest的各種參數(shù)。
  6. NSMutableURLRequest配置完成后,返回其實(shí)例mutableRequest。

第一部分

整體邏輯:

  1. AFHTTPRequestSerializerObservedKeyPaths()函數(shù)會(huì)創(chuàng)建并返回一個(gè)數(shù)組,數(shù)組中有6個(gè)屬性名的字符串(keyPath)。
  2. for in 遍歷這個(gè)數(shù)組,取出每一個(gè)屬性名的字符串(keyPath)。
  3. 判斷self.mutableObservedChangedKeyPaths(一個(gè)NSMutableSet)中是否包含keyPath,如果包含這個(gè)keyPath,則把AFHTTPRequestSerializer自己這個(gè)屬性的值賦值給mutableRequest。

分解說(shuō)明:

  1. AFHTTPRequestSerializer有6個(gè)特定的屬性,分別是:

    1. @property (nonatomic, assign) BOOL allowsCellularAccess;
    2. @property (nonatomic, assign) NSURLRequestCachePolicy cachePolicy;
    3. @property (nonatomic, assign) BOOL HTTPShouldHandleCookies;
    4. @property (nonatomic, assign) BOOL HTTPShouldUsePipelining;
    5. @property (nonatomic, assign) NSURLRequestNetworkServiceType networkServiceType;
    6. @property (nonatomic, assign) NSTimeInterval timeoutInterval;
  2. 這6個(gè)屬性開(kāi)發(fā)者可以讀寫(xiě),而這6個(gè)屬性與NSMutableURLRequest的屬性一一對(duì)應(yīng),換句話說(shuō)AFHTTPRequestSerializer的6個(gè)屬性最終目的是開(kāi)發(fā)者設(shè)置具體值,然后傳遞給NSMutableURLRequest,只不過(guò)開(kāi)發(fā)者從設(shè)置值到mutableRequest接收到值的過(guò)程略顯復(fù)雜。

  3. 在AFHTTPRequestSerializer的init方法中依次對(duì)6個(gè)屬性添加了鍵值觀察(KVO)。
    init方法片段:

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

    ...省略...
    
    for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
        if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
            [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
        }
    }

    return self;
}

?

  1. AFHTTPRequestSerializer重寫(xiě)了每一個(gè)屬性的setter,成員變量Ivar賦值前后加了KVO的willChangeValueForKeydidChangeValueForKey來(lái)手動(dòng)觸發(fā)KVO。
    AFHTTPRequestSerializer的6個(gè)屬性的setter實(shí)現(xiàn)方式完全一致,這里只貼兩個(gè)屬性的setter源碼:
- (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))];
}

等等......

?

  1. AFHTTPRequestSerializerObservedKeyPaths()函數(shù):
    下的源碼就是AFHTTPRequestSerializerObservedKeyPaths()函數(shù)的實(shí)現(xiàn),其實(shí)很簡(jiǎn)單,只是創(chuàng)建了一個(gè)單例數(shù)組,數(shù)組中是這個(gè)6個(gè)屬性的getter由selector轉(zhuǎn)成的string。
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;
}

?

  1. self.mutableObservedChangedKeyPaths 解析
    1. self.mutableObservedChangedKeyPaths是什么?
      • 答:AFHTTPRequestSerializer的一個(gè)屬性。用來(lái)保存被開(kāi)發(fā)者賦值的6個(gè)屬性中的一個(gè)或多個(gè)(也可以沒(méi)有)。
      @property (readwrite, nonatomic, strong) NSMutableSet *mutableObservedChangedKeyPaths;
      
    2. self.mutableObservedChangedKeyPaths是在什么地方被初始化的?
      • 答:是在AFHTTPRequestSerializer的init方法中被初始化的。
      self.mutableObservedChangedKeyPaths = [NSMutableSet set];
      
    3. 案例Demo中并沒(méi)有調(diào)用AFHTTPRequestSerializer的init方法,AFHTTPRequestSerializer的init方法是什么時(shí)候調(diào)用的?
      • 答:完整的調(diào)用順序是:[AFHTTPSessionManager manager] => [AFHTTPRequestSerializer serializer] => [[self alloc] init]。這里self是指AFHTTPRequestSerializer。
    4. self.mutableObservedChangedKeyPaths在什么時(shí)候添加的元素?
      • 答:繼續(xù)往下看。

?
第一部分總結(jié):

AFHTTPRequestSerializer早在初始化的時(shí)候就對(duì)6個(gè)屬性依次添加了鍵值觀察(KVO)。如果我們?cè)谡{(diào)用 AFHTTPSessionManager 的 GET 方法或者 POST 方法之前對(duì)這6個(gè)屬性賦了值,就會(huì)調(diào)用屬性的setter,接著會(huì)觸發(fā)KVO的通知,來(lái)到KVO的observeValueForKeyPath:ofObject:change:context:回調(diào)方法。

#pragma mark - NSKeyValueObserving

+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
    if ([AFHTTPRequestSerializerObservedKeyPaths() containsObject:key]) {
        return NO;
    }

    return [super automaticallyNotifiesObserversForKey:key];
}

- (void)observeValueForKeyPath:(NSString *)keyPath
                      ofObject:(__unused id)object
                        change:(NSDictionary *)change
                       context:(void *)context
{
    if (context == AFHTTPRequestSerializerObserverContext) {
        if ([change[NSKeyValueChangeNewKey] isEqual:[NSNull null]]) {
            [self.mutableObservedChangedKeyPaths removeObject:keyPath];
        } else {
            [self.mutableObservedChangedKeyPaths addObject:keyPath];
        }
    }
}

在KVO的observeValueForKeyPath:ofObject:change:context:回調(diào)方法中,根據(jù)值是否為空,來(lái)操作self.mutableObservedChangedKeyPaths是添加該keyPath還是移除。

我們假定開(kāi)發(fā)者在調(diào)用GET或POST方法請(qǐng)求前設(shè)置了timeoutInterval屬性:

  1. 走timeoutInterval屬性的setter
  2. 觸發(fā)KVO的通知,來(lái)到KVO的observeValueForKeyPath:ofObject:change:context:回調(diào)方法
  3. timeoutInterval屬性被添加到了mutableObservedChangedKeyPaths集合中
  4. 接下來(lái)就是調(diào)用AFHTTPSessionManager的 GET 或 POST 方法發(fā)起請(qǐng)求
  5. 然后就會(huì)來(lái)到此處(requestWithMethod:URLString:parameters:error:方法的第一部分,就是現(xiàn)在在解析的這里)
  6. 接著把timeoutInterval賦值給NSMutableURLRequest。

以上就是第一部分做的全部事情。

?
一些補(bǔ)充:

  • automaticallyNotifiesObserversForKey:函數(shù)用來(lái)根據(jù)條件阻止KVO發(fā)出通知,系統(tǒng)提供的方法,AFN只是進(jìn)行了重寫(xiě)。
  • 集合(set)自帶去重的功能,所以當(dāng)6個(gè)屬性被多次賦值時(shí),也就是多次觸發(fā)observeValueForKeyPath,mutableObservedChangedKeyPaths集合(set)中不會(huì)出現(xiàn)多個(gè)同樣的keyPath。

為什么AFN要手動(dòng)觸發(fā)KVO,而不是自動(dòng)觸發(fā)KVO ?

答:AFN的早期版本也使用KVO觀察這6個(gè)屬性,有人發(fā)現(xiàn)在單元測(cè)試中會(huì)出現(xiàn)崩潰,所以后來(lái)增加了automaticallyNotifiesObserversForKey方法來(lái)阻止這6個(gè)屬性通過(guò)KVO自動(dòng)發(fā)送通知。但是很快人們發(fā)現(xiàn)這樣做,這6個(gè)屬性使用開(kāi)始出現(xiàn)問(wèn)題,會(huì)有設(shè)置后無(wú)效的情況。于是作者又改了回去,刪了automaticallyNotifiesObserversForKey方法,但是問(wèn)題又繞回原地了。因此為了能在單元測(cè)試中正常使用,并且這6個(gè)屬性的使用不能受到影響,即正常可以使用KVO,于是增加了6個(gè)屬性的setter,在setter里面手動(dòng)觸發(fā)KVO。

?

第二部分 (高能預(yù)警)

一句話描述:
根據(jù)請(qǐng)求方法(GET、POST)的不同,將Parameters按照每種方法的要求組裝到NSMutableURLRequest的實(shí)例mutableRequest中去。即GET方法是將參數(shù)處理后拼接到URL中,POST方法是將參數(shù)處理后設(shè)置到請(qǐng)求體中。

第二部分核心方法:requestBySerializingRequest:withParameters:error:

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

    NSMutableURLRequest *mutableRequest = [request mutableCopy];
    
    // 這部分起個(gè)名:self.HTTPRequestHeaders部分
    [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
        if (![request valueForHTTPHeaderField:field]) {
            [mutableRequest setValue:value forHTTPHeaderField:field];
        }
    }];

    // 這部分起個(gè)名:處理傳入的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;
            }
        } else {
            switch (self.queryStringSerializationStyle) {
                case AFHTTPRequestQueryStringDefaultStyle:
                    query = AFQueryStringFromParameters(parameters);
                    break;
            }
        }
    }

    // 這部分起個(gè)名:使用query裝配mutableRequest部分
    if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
        if (query && query.length > 0) {
            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;
}

?
在源碼的注釋中為了方便敘述我給劃分成了3個(gè)部分,分別是:

  1. self.HTTPRequestHeaders部分
  2. 處理傳入的parameters部分
  3. 使用query裝配mutableRequest部分

按照代碼順序,逐步解析:

self.HTTPRequestHeaders部分

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

一句話描述:
遍歷self.HTTPRequestHeaders,如果我們創(chuàng)建的新 NSURLRequest *request 中沒(méi)有開(kāi)發(fā)者指定(init中也指定了默認(rèn))的請(qǐng)求頭字段和值,那么把沒(méi)有的這部分開(kāi)發(fā)者指定的的請(qǐng)求頭字段和值保存一份到request中。

整體邏輯

  1. 遍歷self.HTTPRequestHeaders
    • self.HTTPRequestHeaders只有初始化時(shí)AFN默認(rèn)添加的Accept-LanguageUser-Agent兩個(gè)鍵值對(duì)。
  2. 如果我們創(chuàng)建的新 NSURLRequest *request 中開(kāi)發(fā)者指定了Accept-LanguageUser-Agent以外的請(qǐng)求頭鍵值對(duì),那么把這些Accept-LanguageUser-Agent以外的請(qǐng)求頭鍵值對(duì)保存一份到request中。

分解說(shuō)明

  1. self.HTTPRequestHeaders 的聲明在 AFHTTPRequestSerializer.h:

    @property (readonly, nonatomic, strong) NSDictionary <NSString *, NSString *> *HTTPRequestHeaders;
    
  2. self.HTTPRequestHeaders 的取值方法,即getter:

    - (NSDictionary *)HTTPRequestHeaders {
        NSDictionary __block *value;
        dispatch_sync(self.requestHeaderModificationQueue, ^{
            value = [NSDictionary dictionaryWithDictionary:self.mutableHTTPRequestHeaders];
        });
        return value;
    }
    

    getter解析:

    • self.HTTPRequestHeaders 的值完全來(lái)自于 self.mutableHTTPRequestHeaders
    • requestHeaderModificationQueue 在 AFHTTPRequestSerializer 的 init 方法中初始化,是一個(gè)并發(fā)隊(duì)列。具體請(qǐng)參考AFNetworking主線梳理(一)AFHTTPRequestSerializer 部分。
    self.requestHeaderModificationQueue = dispatch_queue_create("requestHeaderModificationQueue",  DISPATCH_QUEUE_CONCURRENT);
    
  3. self.mutableHTTPRequestHeaders

    • self.mutableHTTPRequestHeaders 的聲明在 AFHTTPRequestSerializer.m:
    @property (readwrite, nonatomic, strong) NSMutableDictionary *mutableHTTPRequestHeaders;
    
    • self.mutableHTTPRequestHeaders 在 AFHTTPRequestSerializer 的 init 方法中初始化
    self.mutableHTTPRequestHeaders = [NSMutableDictionary dictionary];
    
    • self.mutableHTTPRequestHeaders 如何被賦值?
      • self.mutableHTTPRequestHeaders 是在 setValue:forHTTPHeaderField: 方法中間接賦值的。
      • requestHeaderModificationQueue 在 AFHTTPRequestSerializer 的 init 方法中初始化,和前面第2小點(diǎn)的"self.HTTPRequestHeaders取值方法"用的是同一個(gè)并發(fā)隊(duì)列。具體請(qǐng)參考AFNetworking主線梳理(一)AFHTTPRequestSerializer 部分。
  1. 那么 self.mutableHTTPRequestHeaders 是什么時(shí)候被賦上默認(rèn)值的呢?
    • 答:在 AFHTTPRequestSerializer的init 方法中,分兩次給 self.mutableHTTPRequestHeaders 賦了默認(rèn)值,分別是:Accept-Language、User-Agent。具體請(qǐng)參考AFNetworking主線梳理(一)AFHTTPRequestSerializer 部分。
      ?

至此,“第二部分 (高能預(yù)警)” => “self.HTTPRequestHeaders部分”的使命完成。

問(wèn):開(kāi)發(fā)者如何給request指定請(qǐng)求頭的字段和值呢?
答:通過(guò) AFHTTPRequestSerializer 的 setValue:forHTTPHeaderField: 方法。


處理傳入的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;
        }
    } else {
        switch (self.queryStringSerializationStyle) {
            case AFHTTPRequestQueryStringDefaultStyle:
                query = AFQueryStringFromParameters(parameters);
                break;
        }
    }
}

一句話描述:“處理傳入的parameters部分” 就是將parameters按照規(guī)則拼接成query string。

假定開(kāi)發(fā)者傳入的parameters為字典{"key1" : "value1", "key2" : "value2"},那么query = "key1=value1&key2=value2"。

接下來(lái)我們看看parameters是如何被拼接成query的。

“處理傳入的parameters部分” 按照 if else被自然分割成兩部分,先看 if 這部分。

if 分支整體邏輯:

  1. 如果 self.queryStringSerialization(一個(gè)block)存在,即開(kāi)發(fā)者實(shí)現(xiàn)了這個(gè)block,則執(zhí)行 if 分支內(nèi)的語(yǔ)句。
  2. 把 request、parameters 以及 *error 傳遞給self.queryStringSerialization這個(gè)block,由block處理完成后,返回query。

分解說(shuō)明:

  1. self.queryStringSerialization 是一個(gè)block,需要開(kāi)發(fā)者實(shí)現(xiàn),聲明以及賦值方法 API 如下
// AFURLRequestSerialization.h
- (void)setQueryStringSerializationWithBlock:(nullable NSString * (^)(NSURLRequest *request, id parameters, NSError * __autoreleasing *error))block;

// AFURLRequestSerialization.m
typedef NSString * (^AFQueryStringSerializationBlock)(NSURLRequest *request, id parameters, NSError *__autoreleasing *error);

@property (readwrite, nonatomic, copy) AFQueryStringSerializationBlock queryStringSerialization;

- (void)setQueryStringSerializationWithBlock:(NSString *(^)(NSURLRequest *, id, NSError *__autoreleasing *))block {
    self.queryStringSerialization = block;
}
  1. self.queryStringSerialization 是什么時(shí)候、什么地方被實(shí)現(xiàn)的?
    答:沒(méi)有默認(rèn)的初始化,需要開(kāi)發(fā)者去實(shí)現(xiàn)此block。如果開(kāi)發(fā)者沒(méi)有實(shí)現(xiàn)這個(gè)block,就不會(huì)走 if 分支。

  2. self.queryStringSerialization 的作用
    答:這個(gè)block的意義所在,是當(dāng)開(kāi)發(fā)者如果需要自定義 query string 的拼接規(guī)則,則用block傳過(guò)來(lái)的request、parameters 以及 error 自行拼接一個(gè) query string 并返回。

  3. NSError *__autoreleasing *error 干啥的?
    答案:如果開(kāi)發(fā)者在拼接 query string 過(guò)程中出現(xiàn)錯(cuò)誤,想要拋出Error,就可以利用這個(gè)指針的指針來(lái)直接傳遞error。

?
else 分支整體邏輯:

可以說(shuō) else 分支直接可以看成 query = AFQueryStringFromParameters(parameters); 這一句代碼最為合適。

如果開(kāi)發(fā)者沒(méi)有使用(實(shí)現(xiàn))self.queryStringSerialization,則會(huì)走 else分支,else分支也是最普通、最常用的分支。

分解說(shuō)明:

  1. 雖然 else 分支是一整個(gè)switch控制的,但是AFHTTPRequestQueryStringSerializationStyle枚舉值只有一個(gè)而且還是 0 (NSUInteger)。也就是說(shuō)開(kāi)發(fā)者設(shè)置這個(gè)Style也好不設(shè)置也好,最終都會(huì)走switch的第一個(gè)case分支,目前AFN的版本中你沒(méi)得選。
typedef NS_ENUM(NSUInteger, AFHTTPRequestQueryStringSerializationStyle) {
    AFHTTPRequestQueryStringDefaultStyle = 0,
};
  1. self.queryStringSerializationStyle 是一個(gè)枚舉值,需要開(kāi)發(fā)者指定,聲明以及賦值方法 API 如下
// AFURLRequestSerialization.h
- (void)setQueryStringSerializationWithStyle:(AFHTTPRequestQueryStringSerializationStyle)style;

// AFURLRequestSerialization.m
@property (readwrite, nonatomic, assign) AFHTTPRequestQueryStringSerializationStyle queryStringSerializationStyle;

- (void)setQueryStringSerializationWithStyle:(AFHTTPRequestQueryStringSerializationStyle)style {
    self.queryStringSerializationStyle = style;
    self.queryStringSerialization = nil;
}

?
重點(diǎn)?。。?/strong>
else 分支的核心函數(shù) AFQueryStringFromParameters()

  1. AFQueryStringFromParameters() 函數(shù)

一句話描述:按照規(guī)則把parameters中所有的key和value拼接成一個(gè)字符串。

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

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

?
詳細(xì)邏輯:

  1. 創(chuàng)建一個(gè)空的可變數(shù)組,NSMutableArray *mutablePairs
  2. 調(diào)用AFQueryStringPairsFromDictionary()函數(shù)獲得一個(gè)數(shù)組,一個(gè)裝滿AFQueryStringPair實(shí)例對(duì)象的數(shù)組。AFQueryStringPair是AFURLRequestSerialization的私有類,具體實(shí)現(xiàn)貼在文末(往下翻)。
NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) {
    return AFQueryStringPairsFromKeyAndValue(nil, dictionary);
}
  1. AFQueryStringPairsFromDictionary()函數(shù)直接調(diào)用AFQueryStringPairsFromKeyAndValue()函數(shù)
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 { // 這個(gè)分支才是主要分支
        [mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]];
    }

    return mutableQueryStringComponents;
}
  1. AFQueryStringPairsFromKeyAndValue()函數(shù)采用遞歸調(diào)用方式將傳進(jìn)來(lái)的value(假定是字典)一層一層剝開(kāi),直到value不再是任何類型的集合,然后組裝成AFQueryStringPair實(shí)例對(duì)象添加到數(shù)組中,返回?cái)?shù)組。
    • 4.1 如果傳進(jìn)來(lái)的value是一個(gè)字典套字典的結(jié)構(gòu):value = {data : {adSource : Admob}}。如果傳進(jìn)來(lái)的key是空,則第一次遞歸不重新組裝函數(shù)的參數(shù)key,直接用下一層的key當(dāng)做函數(shù)遞歸時(shí)的key。
    • 4.2 假定傳進(jìn)來(lái)的key = "Hello"。
    • 4.3 先把value的allKeys進(jìn)行升序排序,形成一個(gè)新的數(shù)組。
    • 4.4 for in 遍歷這個(gè)新的數(shù)組,取出每一個(gè)key(data這一層),使用這個(gè)key從字典取出{adSource : Admob},即先把value這個(gè)字典剝開(kāi)一層。
    • 4.5 重新組裝AFQueryStringPairsFromKeyAndValue(NSString *key, id value)函數(shù)的參數(shù),進(jìn)行遞歸。
    • 4.6 若函數(shù)傳進(jìn)來(lái)的key為Hello,則遞歸時(shí)key="Hello[data]",value={adSource : Admob}。若函數(shù)傳進(jìn)來(lái)的key為空,則遞歸時(shí)key="data",value={adSource : Admob}
    • 4.7 遞歸到最后,會(huì)走到AFQueryStringPairsFromKeyAndValue()函數(shù)的最后一個(gè)分支,即不是任何一個(gè)集合的else分支里,此例的最終key="Hello[data][adSource]"或者"data[adSource]",最終的value="Admob",用這個(gè)鍵值對(duì)初始化一個(gè)AFQueryStringPair實(shí)例對(duì)象,添加到數(shù)組,返回此數(shù)組。
  2. 經(jīng)過(guò)3和4已經(jīng)獲得了2所需要的裝滿AFQueryStringPair實(shí)例對(duì)象的數(shù)組,for in 遍歷這個(gè)數(shù)組中的每一個(gè)AFQueryStringPair實(shí)例對(duì)象,將[AFQueryStringPair URLEncodedStringValue]的返回值添加到數(shù)組。
  3. 第5步的數(shù)組組裝完成后,大致應(yīng)該是這樣("data[adSource]=Admob", "data[adType]=3")。然后將這個(gè)數(shù)組使用"&"拆分組裝成字符串,返回這個(gè)字符串,這個(gè)字符串就是query string。這個(gè)query大致長(zhǎng)這樣:"data[adSource]=Admob& data[adType]=3"
  4. query組裝完成,AFQueryStringFromParameters()函數(shù)完成使命,返回query

至此,AFQueryStringFromParameters()函數(shù)的使命已完成,返回了拼接好的query。
??把文章往上翻??
這個(gè)query由else分支的query接收,也就是說(shuō)else分支的使命也已完成。
??再把文章往上翻??
if分支是由開(kāi)發(fā)者實(shí)現(xiàn)的self.queryStringSerialization block來(lái)完成query的拼接。ifelse 分支都可完成query的拼接。
??再一次把文章往上翻??
“第二部分 (高能預(yù)警)” => “處理傳入的parameters部分”就全部完成了,接下來(lái)該說(shuō)一說(shuō)“第二部分 (高能預(yù)警)” => “使用query裝配mutableRequest部分”


使用query裝配mutableRequest部分

一句話描述:將query裝配到mutableRequest里。

if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
    if (query && query.length > 0) {
        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]];
}

詳細(xì)邏輯:

  1. 這一部分仍然是 if else 天然分割成的兩個(gè)分支。判斷條件是:self.HTTPMethodsEncodingParametersInURI(NSSet)里是否包含 [request HTTPMethod]的請(qǐng)求方法。

  2. if 分支:如果[request HTTPMethod]的方法是GET、DELETEHEAD這三種,則屬于包含在里面。那么把請(qǐng)求時(shí)傳進(jìn)來(lái)的URL拆分,連同query一起,添加進(jìn)去,將URL重新組裝,再賦值給mutableRequest.URL,比如GET方法的請(qǐng)求參數(shù)就是拼接在URL里面。

  3. else 分支:如果[request HTTPMethod]的方法是POST,則不包含在里面。那么就把query按照UTF8編碼成data后,調(diào)用[mutableRequest setHTTPBody:]POST 的請(qǐng)求參數(shù)(query)設(shè)置到請(qǐng)求體中。

?
分解說(shuō)明:

  1. self.HTTPMethodsEncodingParametersInURI 是一個(gè)集合(NSSet),AFN給了默認(rèn)值GET, HEAD, DELETE。開(kāi)發(fā)者也可以自己指定。頭文件里聲明,AFJSONRequestSerializer 的 init 方法設(shè)置的默認(rèn)值:
// AFURLRequestSerialization.h
@property (nonatomic, strong) NSSet <NSString *> *HTTPMethodsEncodingParametersInURI;

// AFURLRequestSerialization.m => init 方法

self.HTTPMethodsEncodingParametersInURI = [NSSet setWithObjects:@"GET", @"HEAD", @"DELETE", nil];
  1. [request HTTPMethod] 是什么?
    答:[request HTTPMethod] 方法返回的是開(kāi)發(fā)者設(shè)置到request的請(qǐng)求方法,諸如 GET、POST 等。

  2. request的請(qǐng)求方法是什么時(shí)候設(shè)置的?
    答:完整的調(diào)用順序是

    1. AFHTTPSessionManager:GET或者POST方法
    2. AFHTTPSessionManager:[dataTaskWithHTTPMethod:URLString:parameters:uploadProgress:downloadProgress:success:failure:]方法
    3. AFHTTPRequestSerializer:[requestWithMethod:URLString:parameters:error:] 方法
    4. AFHTTPRequestSerializer:mutableRequest.HTTPMethod = method;
  3. self.stringEncoding 是什么?
    答:字符串編碼,默認(rèn)為NSUTF8StringEncoding。

  4. self.stringEncoding 是什么時(shí)候設(shè)置的默認(rèn)值?
    答:AFHTTPRequestSerializer 的 init 方法中。

self.stringEncoding = NSUTF8StringEncoding;

至此,“第二部分 (高能預(yù)警)” => “使用query裝配mutableRequest部分” 使命已完成,已成功的將query string裝配到mutableRequest里。

同時(shí)也意味著“第二部分 (高能預(yù)警)”這一部分使命也已完成。

再加上第一部分,[requestWithMethod:URLString:parameters:error:]方法的使命也已完成,創(chuàng)建一個(gè)NSMutableURLRequest實(shí)例,并返回給 AFHTTPSessionManager 的 [dataTaskWithHTTPMethod:URLString:parameters:uploadProgress:downloadProgress:success:failure:] 方法。

AFHTTPRequestSerializer 部分使命全部完成。AFHTTPRequestSerializer 部分已經(jīng)負(fù)責(zé)創(chuàng)建 NSURLRequest 并返回給 AFHTTPSessionManager 的 dataTaskWithHTTPMethod:URLString:parameters:uploadProgress:downloadProgress:success:failure: 第一部分。


AFURLSessionManager 部分

接續(xù) AFHTTPSessionManager 的 dataTaskWithHTTPMethod:URLString:parameters:uploadProgress:downloadProgress:success:failure: 第二部分,AFURLSessionManager 部分的dataTaskWithRequest:uploadProgress:downloadProgress:completionHandler:函數(shù)主要任務(wù)就是創(chuàng)建一個(gè) NSURLSessionDataTask 實(shí)例對(duì)象,并返回。

dataTaskWithRequest:uploadProgress:downloadProgress:completionHandler: 函數(shù)解析

- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
                               uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
                             downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
                            completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject,  NSError * _Nullable error))completionHandler {

    __block NSURLSessionDataTask *dataTask = nil;
    url_session_manager_create_task_safely(^{
        dataTask = [self.session dataTaskWithRequest:request];
    });

    [self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];

    return dataTask;
}

整體邏輯

  1. 創(chuàng)建一個(gè)局域變量:NSURLSessionDataTask *dataTask,初始化為 nil 。
  2. 實(shí)現(xiàn) url_session_manager_create_task_safely 函數(shù)的block。
  3. 在block的回調(diào)中,通過(guò)屬性 session 調(diào)用 dataTaskWithRequest:方法。
  4. dataTaskWithRequest:方法會(huì)創(chuàng)建一個(gè) NSURLSessionDataTask 實(shí)例對(duì)象,并返回。
  5. 調(diào)用 addDelegateForDataTask:uploadProgress:downloadProgress:completionHandler: 方法(參考 AFNetworking主線梳理(一))。

分解說(shuō)明

  1. url_session_manager_create_task_safely 函數(shù)
    • 函數(shù)的實(shí)現(xiàn)
    static void url_session_manager_create_task_safely(dispatch_block_t block) {
        if (NSFoundationVersionNumber < NSFoundationVersionNumber_With_Fixed_5871104061079552_bug) {
            // Fix of bug
            // Open Radar:http://openradar.appspot.com/radar?id=5871104061079552 (status: Fixed in iOS8)
            // Issue about:https://github.com/AFNetworking/AFNetworking/issues/2093
            dispatch_sync(url_session_manager_creation_queue(), block);
        } else {
            block();
        }
    }
    
    • 函數(shù)的主要邏輯:
      只有if else, 如果版本號(hào)小于iOS修復(fù)版本號(hào),則需要把當(dāng)前任務(wù)同步的添加到一個(gè)串行隊(duì)列中,保證不發(fā)生并發(fā)。否則,直接調(diào)用block回調(diào)回去。
    • 函數(shù)的block回調(diào):
      函數(shù)的block回調(diào)中的代碼最終還是順序執(zhí)行的,無(wú)論是在if還是else里都是同步順序執(zhí)行。簡(jiǎn)單講就是dataTask = [self.session dataTaskWithRequest:request];這句代碼是順序執(zhí)行的,就執(zhí)行順序而言與在block外無(wú)異。
    • 函數(shù)的參數(shù):
      url_session_manager_creation_queue()函數(shù)創(chuàng)建并返回一個(gè)類似單例管理的串行隊(duì)列。
    static dispatch_queue_t url_session_manager_creation_queue() {
        static dispatch_queue_t af_url_session_manager_creation_queue;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            af_url_session_manager_creation_queue = dispatch_queue_create("com.alamofire.networking.session.manager.creation", DISPATCH_QUEUE_SERIAL);
        });
    
        return af_url_session_manager_creation_queue;
    }
    

?

  1. self.session
    • session是AFURLSessionManager的屬性
    @property (readonly, nonatomic, strong) NSURLSession *session;
    
    • session屬性是什么時(shí)候初始化的?
      在AFURLSessionManager初始化方法initWithSessionConfiguration:中進(jìn)行的初始化,由AFN指定默認(rèn)配置。具體請(qǐng)參考AFNetworking主線梳理(一)。
    self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
    

?

  1. dataTaskWithRequest:request 是 NSURLSession 的方法,利用入?yún)SURLRequest創(chuàng)建一個(gè) NSURLSessionDataTask 實(shí)例對(duì)象并返回。
    ?

問(wèn):為什么創(chuàng)建dataTask的代碼要在url_session_manager_create_task_safely函數(shù)的block回調(diào)中執(zhí)行?
答:為了解決 iOS 7 存在的bug。在 iOS 7 某些版本中如果并發(fā)創(chuàng)建NSURLSessionTask,會(huì)造成 NSURLSessionTask 的 taskIdentifier不唯一(有重復(fù)的),而AFN依賴 taskIdentifier 保存了很多信息,會(huì)造成混亂。

問(wèn):為什么url_session_manager_creation_queue()函數(shù)用單例保存隊(duì)列?
個(gè)人猜測(cè):因?yàn)樵撽?duì)列職責(zé)單一,無(wú)需多次重復(fù)創(chuàng)建新隊(duì)列。網(wǎng)絡(luò)請(qǐng)求多數(shù)情況下是高頻的,所以多次創(chuàng)建不是一個(gè)好的選擇。


AFQueryStringPair 部分

#pragma mark -

@interface AFQueryStringPair : NSObject
@property (readwrite, nonatomic, strong) id field;
@property (readwrite, nonatomic, strong) id value;

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

- (NSString *)URLEncodedStringValue;
@end

@implementation AFQueryStringPair

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

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

    return self;
}

- (NSString *)URLEncodedStringValue {
    if (!self.value || [self.value isEqual:[NSNull null]]) {
        return AFPercentEscapedStringFromString([self.field description]);
    } else {
        return [NSString stringWithFormat:@"%@=%@", AFPercentEscapedStringFromString([self.field description]), AFPercentEscapedStringFromString([self.value description])];
    }
}

@end

AFPercentEscapedStringFromString()沒(méi)什么特殊,就是把傳進(jìn)去的字符串做了百分號(hào)編碼的處理,防止網(wǎng)絡(luò)請(qǐng)求時(shí)出現(xiàn)不符合規(guī)則的字符。處理完成后,返回安全的字符串。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 說(shuō)到AFNetwokring這個(gè)強(qiáng)大第三方網(wǎng)絡(luò)請(qǐng)求庫(kù),大家應(yīng)該都不陌生吧,ios開(kāi)發(fā)、mac開(kāi)發(fā)都經(jīng)常用,主要是他...
    塵峰的小孩閱讀 521評(píng)論 0 0
  • 最近一直在研究AFN,覺(jué)得有必要記錄一下自己的心得,一是可以自己加深印象,二是可以幫助一下不太了解AFN的人,都是...
    種惡因得惡果閱讀 517評(píng)論 0 0
  • 一 .實(shí)現(xiàn)步驟 NSString *requestUrl =@""; AFHTTPSessionManager *...
    Super_Chester閱讀 1,096評(píng)論 0 1
  • 1、登錄(文本輸入、按鈕交互、基于網(wǎng)絡(luò)的交互) 2、刷新界面:(表視圖) 1>小部分應(yīng)用程序數(shù)據(jù)來(lái)源于本地 2>更...
    炙冰閱讀 896評(píng)論 0 1
  • #AFNetworking源碼閱讀系列 一 前言: AFNetWorking一款輕量級(jí)網(wǎng)絡(luò)請(qǐng)求開(kāi)源框架,基于iOS...
    Xcode_破曉閱讀 371評(píng)論 0 0

友情鏈接更多精彩內(nèi)容