AFNetworking 3.0 源碼閱讀筆記(五)

原文:http://itangqi.me/2016/05/15/the-notes-of-learning-afnetworking-five/

AFURLRequestSerialization

AFURLRequestSerialization 定義爲協議,其主要工作是對發出的 HTTP 請求進行處理:

1
2
3
4
5
6
7
@protocol AFURLRequestSerialization <NSObject, NSSecureCoding, NSCopying>

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

@end

遵循該協議的類同時也要遵循 NSObject、NSSecureCoding 和 NSCopying 這三個協議,以實現 Objective-C 對象的基本行爲、安全編碼以及拷貝。

AFHTTPRequestSerializer

而在 AFURLRequestSerialization 模塊中,最爲重要的類便是 AFHTTPRequestSerializer,其主要作用爲:

  1. 處理查詢的 URL 參數
  2. 設置 HTTP 頭部字段
  3. 設置請求的屬性
  4. 分塊上傳

這篇文章不會對其中涉及分塊上傳的部分進行分析,因爲其中涉及到了多個類的功能,比較複雜,如果有興趣可以研究一下。

處理查詢參數

處理查詢參數這部分主要是通過 AFQueryStringPair 還有一些 C 函數來完成的,這個類有兩個屬性 fieldvalue 對應 HTTP 請求的查詢 URL 中的參數。

我們來看初始化方法,其中的 - (NSString *)URLEncodedStringValue 方法會返回 key=value 這種格式,同時使用 AFPercentEscapedStringFromString 函數來對 fieldvalue 進行處理,將其中的 :#[]@!$&'()*+,;= 等字符轉換爲百分號表示的形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
- (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])];
    }
}

這一部分代碼還負責返回查詢參數,將 AFQueryStringPair 或者 key value 轉換爲以下這種形式:

1
username = dravenss&password=123456&hello[world]=helloworld

它的實現主要依賴於一個遞歸函數 AFQueryStringPairsFromKeyAndValue,如果當前的 value 是一個集合類型的話,那麼它就會不斷地遞歸調用自己:

1
2
3
4
5
6
7
8
NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) {
    NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];

    NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)];	 
    ...
    
    return mutableQueryStringComponents;
}

最後返回一個數組

1
2
3
4
5
[
    username = draveness,
    password = 123456,
    hello[world] = helloworld
]

得到這個數組之後就會調用 AFQueryStringFromParameters 使用 & 來拼接它們:

1
2
3
4
5
6
7
8
static NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
    NSMutableArray *mutablePairs = [NSMutableArray array];
    for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
        [mutablePairs addObject:[pair URLEncodedStringValue]];
    }

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

設置 HTTP 頭部字段

AFHTTPRequestSerializer 在頭文件中提供了一些屬性方便我們設置 HTTP 頭部字段。同時,在類的內部,它提供了 - [AFHTTPRequestSerializer setValue:forHTTPHeaderField:] 方法來設置 HTTP 頭部,其實它的實現都是基於一個名爲 mutableHTTPRequestHeaders 的屬性的:

1
2
3
4
5
6
7
8
9
- (void)setValue:(NSString *)value
forHTTPHeaderField:(NSString *)field
{
    [self.mutableHTTPRequestHeaders setValue:value forKey:field];
}

- (NSString *)valueForHTTPHeaderField:(NSString *)field {
    return [self.mutableHTTPRequestHeaders valueForKey:field];
}

在設置 HTTP 頭部字段時,都會存儲到這個可變字典中。而當真正使用時,會用 HTTPRequestHeaders 這個方法,來獲取對應版本的不可變字典:

1
2
3
- (NSDictionary *)HTTPRequestHeaders {
    return [NSDictionary dictionaryWithDictionary:self.mutableHTTPRequestHeaders];
}

到了這裏,可以來分析一下,這個類是如何設置一些我們平時常用的頭部字段的。首先是 User-Agent,在 AFHTTPRequestSerializer 剛剛初始化時,就會根據當前編譯的平臺生成一個 userAgent 字符串:

1
2
3
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]];

[self setValue:userAgent forHTTPHeaderField:@"User-Agent"];

設置驗證字段時,可以使用 - [AFHTTPRequestSerializer setAuthorizationHeaderFieldWithUsername:password:] 方法:

1
2
3
4
5
6
7
- (void)setAuthorizationHeaderFieldWithUsername:(NSString *)username
                                       password:(NSString *)password
{
    NSData *basicAuthCredentials = [[NSString stringWithFormat:@"%@:%@", username, password] dataUsingEncoding:NSUTF8StringEncoding];
    NSString *base64AuthCredentials = [basicAuthCredentials base64EncodedStringWithOptions:(NSDataBase64EncodingOptions)0];
    [self setValue:[NSString stringWithFormat:@"Basic %@", base64AuthCredentials] forHTTPHeaderField:@"Authorization"];
}

設置請求的屬性

還有一些 NSURLRequest 的屬性是通過另一種方式來設置的,AFNetworking 爲這些功能提供了接口:

1
2
3
4
5
6
7
8
9
10
11
@property (nonatomic, assign) BOOL allowsCellularAccess;

@property (nonatomic, assign) NSURLRequestCachePolicy cachePolicy;

@property (nonatomic, assign) BOOL HTTPShouldHandleCookies;

@property (nonatomic, assign) BOOL HTTPShouldUsePipelining;

@property (nonatomic, assign) NSURLRequestNetworkServiceType networkServiceType;

@property (nonatomic, assign) NSTimeInterval timeoutInterval;

它們都會通過 AFHTTPRequestSerializerObservedKeyPaths 的調用而返回:

1
2
3
4
5
6
7
8
9
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;
}

在這些屬性被設置時,會觸發 KVO,然後將新的屬性存儲在一個名爲 mutableObservedChangedKeyPaths 的字典中:

1
2
3
4
5
6
7
8
9
10
11
12
13
- (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];
        }
    }
}

然後會在生成 NSURLRequest 的時候設置這些屬性:

1
2
3
4
5
6
7
8
NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
mutableRequest.HTTPMethod = method;

for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
    if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
        [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
    }
}

關於這個方法的的具體實現會在下一節中介紹。

工作流程

AFHTTPRequestSerializer 會在 AHHTTPSessionManager 初始化時一併初始化,這時它會根據當前系統環境預設置一些 HTTP 頭部字段 Accept-Language User-Agent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
- (instancetype)init {
    self = [super init];
    if (!self) {
        return nil;
    }

    self.stringEncoding = NSUTF8StringEncoding;

    self.mutableHTTPRequestHeaders = [NSMutableDictionary dictionary];


    #1: 設置接收語言,用戶代理,略

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

同時它還對一些屬性進行 KVO,確保它們在改變後更新 NSMutableURLRequest 中對應的屬性。

在初始化之後,如果調用了 - [AFHTTPSessionManager dataTaskWithHTTPMethod:URLString:parameters:uploadProgress:downloadProgress:success:failure:],就會進入 AFHTTPRequestSerializer 的這一方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
                                 URLString:(NSString *)URLString
                                parameters:(id)parameters
                                     error:(NSError *__autoreleasing *)error
{
    NSParameterAssert(method);
    NSParameterAssert(URLString);

    NSURL *url = [NSURL URLWithString:URLString];
    // 1. 對參數進行檢查
    NSParameterAssert(url);

    NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
    // 2. 設置 HTTP 方法
    mutableRequest.HTTPMethod = method;
    // 3. 通過 `mutableObservedChangedKeyPaths` 字典設置 `NSMutableURLRequest` 的屬性
    for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
        if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
            [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
        }
    }
    // 設置 HTTP 頭部字段和查詢參數
    mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];

    return mutableRequest;
}

- [AFHTTPRequestSerializer requestBySerializingRequest:withParameters:error:] 方法主要做了以下幾件事情:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
                               withParameters:(id)parameters
                                        error:(NSError *__autoreleasing *)error
{
    NSParameterAssert(request);

    NSMutableURLRequest *mutableRequest = [request mutableCopy];
    // 1. 通過 `HTTPRequestHeaders` 字典設置頭部字段
    [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
        if (![request valueForHTTPHeaderField:field]) {
            [mutableRequest setValue:value forHTTPHeaderField:field];
        }
    }];

    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:
                    // 2. 調用 `AFQueryStringFromParameters` 將參數轉換爲查詢參數
                    query = AFQueryStringFromParameters(parameters);
                    break;
            }
        }
    }
    // 3. 將 parameters 添加到 URL 或者 HTTP body 中
    if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
        if (query) {
            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]];
    }
    // 4. 將 parameters 添加到 URL 或者 HTTP body 中
    return mutableRequest;
}

小結

  • AFURLResponseSerialization 負責對返回的數據進行序列化
  • AFURLRequestSerialization 負責生成 NSMutableURLRequest,爲請求設置 HTTP 頭部,管理髮出的請求

參考

發佈了24 篇原創文章 · 獲贊 19 · 訪問量 34萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章