處理請求(AFURLRequestSerialization)和響應(AFURLResponseSerialization)

雲本尊發表於2018-05-21

處理請求(AFURLRequestSerialization)和響應(AFURLResponseSerialization)

在這篇文章裡,我們將分析一下發出請求以及接受響應的過程,這部分內容主要涉及以下兩個模組:

  1. AFURLRequestSerialization
  2. AFURLResponseSerialization

前者的主要作用處理請求所需的引數(主要是 HTTP 請求),最終得到請求網路需要的NSMutableURLRequest例項。而後者是處理響應的模組,將請求返回的資料解析成對應的格式。 我們首先對AFURLRequestSerialization進行分析,應為它是一個請求的開始。

AFURLRequestSerialization

在具體瞭解這個模組的具體實現之前,我們先看一下在這個模組的結構(我覺得這樣可以更好的去分析它):

處理請求(AFURLRequestSerialization)和響應(AFURLResponseSerialization)

AFURLRequestSerialization並不是一個類,它是一個協議,協議的內容很簡單,只有一個必須實現的方法。

@protocol AFURLRequestSerialization <NSObject, NSSecureCoding, NSCopying>

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

上圖中的所有類都遵循AFURLRequestSerialization協議,AFHTTPRequestSerializer是模組中的最重要的基類。

AFURLRequestSerialization 的主要工作是對發出的 HTTP 請求進行處理,最終返回NSMutableURLRequest例項。 接下來,我們看看基類AFHTTPRequestSerializer做了什麼。

AFHTTPRequestSerializer

初始化

首先是這個類的例項化方法:

+ (instancetype)serializer {
    return [[self alloc] init];
}

- (instancetype)init {
    self = [super init];
    if (!self) {
        return nil;
    }
    //字串編碼
    self.stringEncoding = NSUTF8StringEncoding;
    //HTTP請求頭
    self.mutableHTTPRequestHeaders = [NSMutableDictionary dictionary];
    //設定預設的Accept-Language請求頭
    .......
    //設定預設的User-Agent請求頭
    ......
    //對一些欄位新增KVO(timeoutInterval,cachePolicy,...)
    ......
    return self;
}
複製程式碼

獲取NSMutableURLRequest例項

- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
                                 URLString:(NSString *)URLString
                                parameters:(id)parameters
                                     error:(NSError *__autoreleasing *)error
{
    //斷言,debug模式下,如果缺少改引數,crash
    NSParameterAssert(method);
    NSParameterAssert(URLString);

    NSURL *url = [NSURL URLWithString:URLString];

    NSParameterAssert(url);

    NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
    mutableRequest.HTTPMethod = method;
    //將request的各種屬性迴圈遍歷
    for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
    //如果自己觀察到的發生變化的屬性,在這些方法裡
        if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
        //把給自己設定的屬性給request設定
            [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
        }
    }
    //將傳入的parameters進行編碼,並新增到request中
    mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];

	return mutableRequest;
}
複製程式碼

這個方法做了3件事:

  1. 設定request的請求型別,get,post,put...等
  2. 往request裡新增一些引數設定,其中AFHTTPRequestSerializerObservedKeyPaths()是一個c函式,返回一個陣列,我們來看看這個函式:
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;
}
複製程式碼

其實這個函式就是封裝了一些屬性的名字,這些都是NSUrlRequest的屬性。 3. 把傳進來的引數進行編碼,然後放到我們請求的request中。

AFURLRequestSerialization協議實現

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

    NSMutableURLRequest *mutableRequest = [request mutableCopy];
    //遍歷請求頭(HTTP head),如果有值,就放入request的head中
    [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:
                    query = AFQueryStringFromParameters(parameters);
                    break;
            }
        }
    }

    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件事:

  1. self.HTTPRequestHeaders中設定的引數,賦值要請求的request裡去。
  2. 把請求引數,從 array,dic,set 這些容器型別轉換為字串,我們可以使自定義的方式,也可以使用AF預設的。詳細程式碼可以去檢視這個方法NSString * AFQueryStringFromParameters(NSDictionary *parameters)
  3. 根據該request中請求型別來判斷引數字串應該如何設定到request中去。如果是GET、HEAD、DELETE,則把引數quey是拼接到url後面的。而POST、PUT是把query拼接到http body中。

至此,我們得到了一個我們想要的request。

AFHTTPRequestSerializerAFJSONRequestSerializerAFPropertyListRequestSerializer這三個類的主要區別在於協議的實現的不同,主要體現在請求頭Content-Type的不同。 AFHTTPRequestSerializer :application/x-www-form-urlencoded AFJSONRequestSerializer :application/json AFPropertyListRequestSerializer : application/x-plist

AFURLResponseSerialization

處理請求(AFURLRequestSerialization)和響應(AFURLResponseSerialization)

AFURLResponseSerialization並不是一個類,它是一個協議,協議的內容很簡單,只有一個必須實現的方法。

@protocol AFURLResponseSerialization <NSObject, NSSecureCoding, NSCopying>

- (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response
                           data:(nullable NSData *)data
                          error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;

@end
複製程式碼

AFHTTPResponseSerializer

初始化
+ (instancetype)serializer {
    return [[self alloc] init];
}

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

    self.stringEncoding = NSUTF8StringEncoding;

    self.acceptableStatusCodes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(200, 100)];
    self.acceptableContentTypes = nil;

    return self;
}
複製程式碼

因為是對 HTTP 響應進行序列化,所以這裡設定了 stringEncodingNSUTF8StringEncoding 而且沒有對self.acceptableContentTypes(接收的內容型別)加以限制。 將 acceptableStatusCodes 設定為從 200299 之間的狀態碼, 因為只有這些狀態碼錶示獲得了有效的響應HTTP code

驗證響應的有效性

- (BOOL)validateResponse:(NSHTTPURLResponse *)response
                    data:(NSData *)data
                   error:(NSError * __autoreleasing *)error
{
    BOOL responseIsValid = YES;
    NSError *validationError = nil;

    if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) {
        if (self.acceptableContentTypes && ![self.acceptableContentTypes containsObject:[response MIMEType]]) {
			#1: 返回內容型別無效
        }

        if (self.acceptableStatusCodes && ![self.acceptableStatusCodes containsIndex:(NSUInteger)response.statusCode] && [response URL]) {
			#2: 返回狀態碼無效
        }
    }

    if (error && !responseIsValid) {
        *error = validationError;
    }

    return responseIsValid;
}
複製程式碼

AFURLResponseSerialization 協議的實現

- (id)responseObjectForResponse:(NSURLResponse *)response
                           data:(NSData *)data
                          error:(NSError *__autoreleasing *)error
{
    [self validateResponse:(NSHTTPURLResponse *)response data:data error:error];

    return data;
}
複製程式碼

呼叫上面的方法對響應進行驗證,然後返回二進位制資料。

AFJSONResponseSerializer

AFJSONResponseSerializer 這個繼承自 AFHTTPResponseSerializer類的實現。

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

    self.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", nil];

    return self;
}
複製程式碼

從上面我們可以看出,在呼叫玩父類的初始化方法後,更新了 acceptableContentTypes 屬性。

協議的實現
- (id)responseObjectForResponse:(NSURLResponse *)response
                           data:(NSData *)data
                          error:(NSError *__autoreleasing *)error
{
	#1: 驗證請求

	#2: 解決一個由只包含一個空格的響應引起的 bug, 略

	#3: 序列化 JSON
	
	#4: 移除 JSON 中的 null

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

    return responseObject;
}
複製程式碼

至於剩下的類大家可以去看原始碼的實現,其實差別不大,只是協議的實現不同而已。

相關文章