本文主要分析AFNetworking序列化NSMutableURLRequest
1:AFNetworking支持三種media-type對應的http請求頭字段Content-Type爲:
①application/x-www-form-urlencoded
②application/json
③application/x-plist
在http請求中,請求方式爲(GET,HEAD,DELEATE)都使用第①中第一種,因爲這三種方式的請求參數是放在URI中,其它的(POST,PUT等)放在請求體中,對於AFNetworking默認也是支持第一種方式。
AFNetworking支持的三種方式支持的序列化請求對象和解析請求參數類有一下三個:
AFHTTPRequestSerializer
AFJSONRequestSerializer :AFHTTPRequestSerializer
AFPropertyListRequestSerializer :AFHTTPRequestSerializer
下面先說下對於application/x-www-form-urlencoded 通用標準的解析請求參數的方式
解析原理:遞歸解析參數,直到出現參數中最終的結構爲key :value(沒有嵌套類型)時生成AFQueryStringPair對象,最終生成了所有key:value方式的AFQueryStringPair對象的數組
,然後對數組中的每個對象進行百分號編碼,最終使用&拼接,生成最終的query字符串。示例:
①:{"key":[{"key":"value1"},{"key":"value2"},"key1"],"key2":"value3"} --->②:key[][key]=value1&key[][key]=value2&key[]=key1&key2=value3 --->③:key%5B%5D%5Bkey%5D=value1&key%5B%5D%5Bkey%5D=value2&key%5B%5D=key1&key2=value3
//①-->②
FOUNDATION_EXPORT NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary);
FOUNDATION_EXPORT NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value);
//②-->③
NSString * AFQueryStringFromParameters(NSDictionary *parameters)
以上三個類都遵循序列化協議:
@protocol AFURLRequestSerialization <NSObject, NSSecureCoding, NSCopying>
/**
對原始請求對象進行copy(即序列化原始request,因爲從NSURLRequest定義
@interface NSURLRequest : NSObject <NSSecureCoding, NSCopying, NSMutableCopying>
可以知道內部已經實現了序列化協議,可以直接使用mutableCopy來序列化)
@param request 原始請求對象
@param parameters 需要編碼的請求參數
@param error 編碼請求參數過程中出現的錯誤信息
@return 返回值爲經過序列化之後的請求對象
*/
- (nullable NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(nullable id)parameters
error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;
@end
AFHTTPRequestSerializer實現序列化請求對象的方式:
①:序列化原始請求對象:
//驗證原始request是否爲空,如果爲空就輸出錯誤日誌信息,並結束程序
NSParameterAssert(request);
//深拷貝原始request對象,並設置新request對象的請求頭參數
NSMutableURLRequest *mutableRequest = [request mutableCopy];
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
if (![request valueForHTTPHeaderField:field]) {
[mutableRequest setValue:value forHTTPHeaderField:field];
}
}];
②:解析並生成query string,此處可以實現自定義的解析方式,AFNetworking拋出的有接口 queryStringSerialization block對象,默認是按照上面的application/x-www-form-urlencoded 方式:
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 {
//默認的解析方式有AFNetworking自己實現對參數的解析並生成query字符串
switch (self.queryStringSerializationStyle) {
case AFHTTPRequestQueryStringDefaultStyle:
query = AFQueryStringFromParameters(parameters);
break;
}
}
}
③:設置Content-Type 和處理URI,如果是老三樣(GET,HEAD,DELETE)按照RFC的規範這幾種請求方式的請求參數是放在url query部分,顯式的呈現在URI中,並且是默認使用application/x-www-form-urlencoded,如果是其它的(POST,PUT)則設置Content-Type爲application/x-www-form-urlencoded同時設置請求體參數。因此需要做如下的判斷:
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 = @"";
}
//其它方式的請求如(POST PUT )設置httpBody,默認設置Content-Type爲form表單提交形式
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
[mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
}
[mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
}
AFJSONRequestSerializer實現序列化請求對象的方式:
①:序列化原始請求對象:
如果是(GET,HEAD,DELETE)方式直接調用父類的序列化解析:
NSParameterAssert(request);
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
return [super requestBySerializingRequest:request withParameters:parameters error:error];
}
如果是(POST,PUT)方式先序列化原始請求對象並設置請求參數:
NSMutableURLRequest *mutableRequest = [request mutableCopy];
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
if (![request valueForHTTPHeaderField:field]) {
[mutableRequest setValue:value forHTTPHeaderField:field];
}
}];
②:設置Content-Type爲application/json
if (parameters) {
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
[mutableRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
}
③:解析請求參數爲jsondata並設置請求體參數:
if (![NSJSONSerialization isValidJSONObject:parameters]) {
if (error) {
NSDictionary *userInfo = @{NSLocalizedFailureReasonErrorKey: NSLocalizedStringFromTable(@"The `parameters` argument is not valid JSON.", @"AFNetworking", nil)};
*error = [[NSError alloc] initWithDomain:AFURLRequestSerializationErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:userInfo];
}
return nil;
}
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:parameters options:self.writingOptions error:error];
if (!jsonData) {
return nil;
}
[mutableRequest setHTTPBody:jsonData];
}
AFPropertyListRequestSerializer實現序列化請求對象的方式:
①:序列化原始請求對象:
如果是(GET,HEAD,DELETE)方式直接調用父類的序列化解析:
NSParameterAssert(request);
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
return [super requestBySerializingRequest:request withParameters:parameters error:error];
}
如果是(POST,PUT)方式先序列化原始請求對象並設置請求參數:
NSMutableURLRequest *mutableRequest = [request mutableCopy];
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
if (![request valueForHTTPHeaderField:field]) {
[mutableRequest setValue:value forHTTPHeaderField:field];
}
}];
②:設置Content-Type爲application/x-plist
if (parameters) {
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
[mutableRequest setValue:@"application/x-plist" forHTTPHeaderField:@"Content-Type"];
}
③:解析請求參數爲jsondata並設置請求體參數:
NSData *plistData = [NSPropertyListSerialization dataWithPropertyList:parameters format:self.format options:self.writeOptions error:error];
if (!plistData) {
return nil;
}
[mutableRequest setHTTPBody:plistData];