處理請求(AFURLRequestSerialization)和響應(AFURLResponseSerialization)
在這篇文章裡,我們將分析一下發出請求以及接受響應
的過程,這部分內容主要涉及以下兩個模組:
前者的主要作用處理請求所需的引數(主要是 HTTP 請求),最終得到請求網路需要的NSMutableURLRequest例項。而後者是處理響應的模組,將請求返回的資料解析成對應的格式。
我們首先對AFURLRequestSerialization
進行分析,應為它是一個請求的開始。
AFURLRequestSerialization
在具體瞭解這個模組的具體實現之前,我們先看一下在這個模組的結構(我覺得這樣可以更好的去分析它):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件事:
- 設定request的請求型別,get,post,put...等
- 往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件事:
- 將
self.HTTPRequestHeaders
中設定的引數,賦值要請求的request裡去。 - 把請求引數,從 array,dic,set 這些容器型別轉換為字串,我們可以使自定義的方式,也可以使用AF預設的。詳細程式碼可以去檢視這個方法
NSString * AFQueryStringFromParameters(NSDictionary *parameters)
。 - 根據該request中請求型別來判斷引數字串應該如何設定到request中去。如果是GET、HEAD、DELETE,則把引數quey是拼接到url後面的。而POST、PUT是把query拼接到http body中。
至此,我們得到了一個我們想要的request。
AFHTTPRequestSerializer
,AFJSONRequestSerializer
,AFPropertyListRequestSerializer
這三個類的主要區別在於協議的實現的不同,主要體現在請求頭Content-Type
的不同。
AFHTTPRequestSerializer :application/x-www-form-urlencoded
AFJSONRequestSerializer :application/json
AFPropertyListRequestSerializer : application/x-plist
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 響應進行序列化,所以這裡設定了 stringEncoding
為 NSUTF8StringEncoding
而且沒有對self.acceptableContentTypes
(接收的內容型別)加以限制。
將 acceptableStatusCodes
設定為從 200
到 299
之間的狀態碼, 因為只有這些狀態碼錶示獲得了有效的響應
。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;
}
複製程式碼
至於剩下的類大家可以去看原始碼的實現,其實差別不大,只是協議的實現不同而已。