AFNetworking粗解(Serialization模塊)

111177.png

續(xù)AFNetworking核心類AFURLConnectionOperation的詳解
本篇我們來看AFNetworking的下一個模塊Serialization
其中包括AFURLRequestSerialization和AFURLResponseSerialization兩個類
我們主要講AFURLRequestSerialization,因為AFURLRequestSerialization的實現(xiàn)比AFURLResponseSerialization復雜得多,我們理解了AFURLRequestSerialization就不難理解AFURLResponseSerialization了。
AFURLRequestSerialization的作用是協(xié)助構建NSURLRequest
其主要實現(xiàn)以下兩個功能:
1.構建普通請求:格式化請求參數(shù),生成HTTP Header。
2.構建multipart請求。

1.構建普通請求
? 格式化請求參數(shù)
一般我們請求都會按key=value的方式帶上各種參數(shù),GET方法參數(shù)直接加在URL上,POST方法放在body上,NSURLRequest沒有封裝好這個參數(shù)的解析,只能我們自己拼好字符串。AFNetworking提供了接口,讓參數(shù)可以是NSDictionary, NSArray, NSSet這些類型,再由內部解析成字符串后賦給NSURLRequest。
轉換分為三部分:
第一部分是用戶傳進來的數(shù)據(jù),支持包含NSArray,NSDictionary,NSSet這三種數(shù)據(jù)結構。
第二部分是轉換成AFNetworking內自己的數(shù)據(jù)結構,每一個key-value對都用一個對象AFQueryStringPair表示,作用是最后可以根據(jù)不同的字符串編碼生成各自的key=value字符串。主要函數(shù)是AFQueryStringPairsFromKeyAndValue。
第三部分是最后生成NSURLRequest可用的字符串數(shù)據(jù),并且對參數(shù)進行url編碼,在AFQueryStringFromParametersWithEncoding這個函數(shù)里。
最后在把數(shù)據(jù)賦給NSURLRequest時根據(jù)不同的HTTP方法分別處理,對于GET/HEAD/DELETE方法,把參數(shù)加到URL后面,對于其他如POST/PUT方法,把數(shù)據(jù)加到body上,并設好HTTP頭,告訴服務端字符串的編碼。
相關代碼:
[objc] view plaincopy

1.  //第一部分用戶傳進來的數(shù)據(jù),parameters支持包含NSArray,NSDictionary,NSSet這三種數(shù)據(jù)結構。  
2.  - (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request  
3.                                 withParameters:(id)parameters  
4.                                          error:(NSError *__autoreleasing *)error  
5.  {  
6.      NSParameterAssert(request);  
7.    
8.      NSMutableURLRequest *mutableRequest = [request mutableCopy];  
9.    
10.  //..........省略一些代碼  
11.     if (parameters) {  
12.         NSString *query = nil;  
13.         if (self.queryStringSerialization) {  
14.             query = self.queryStringSerialization(request, parameters, error);  
15.         } else {  
16.             switch (self.queryStringSerializationStyle) {  
17.                 case AFHTTPRequestQueryStringDefaultStyle:<span style="white-space:pre">                                  </span>//調用<span style="font-family: 'Microsoft YaHei';">AFQueryStringFromParametersWithEncoding方法對傳進來的數(shù)據(jù)進行轉換</span>  
18.                     query = AFQueryStringFromParametersWithEncoding(parameters, self.stringEncoding);  
19.                     break;  
20.             }  
21.         }<pre name="code" class="objc" style="color: rgb(37, 37, 37); font-size: 14px; line-height: 28px;">//..........省略一些代碼  
return mutableRequest;}

[objc] view plaincopy

1.  //對用戶傳進來的數(shù)據(jù)進行轉換  
2.  static NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *parameters, NSStringEncoding stringEncoding) {  
3.      NSMutableArray *mutablePairs = [NSMutableArray array];  
4.      for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {  
5.          //第二部分將用戶傳進來的參數(shù)轉換成AFNetworking內自己的數(shù)據(jù)結構  
6.          [mutablePairs addObject:[pair URLEncodedStringValueWithEncoding:stringEncoding]];  
7.      }  
8.    
9.      //第三部分生成NSURLRequest可用的字符串數(shù)據(jù),并且對參數(shù)進行url編碼  
10.     return [mutablePairs componentsJoinedByString:@"&"];  
11. }  
12.   
13. NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) {  
14.     return AFQueryStringPairsFromKeyAndValue(nil, dictionary);  
15. }  
16.   
17. //將用戶傳進來的參數(shù)轉換成AFNetworking內自己的數(shù)據(jù)結構,每一個key-value對都用一個對象AFQueryStringPair表示,作用是最后可以根據(jù)不同的字符串編碼生成各自的key=value字符串。  
18. NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) {  
19.     NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];  
20.   
21.     NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)];  
22.   
23.     if ([value isKindOfClass:[NSDictionary class]]) {       //用戶傳進來的是NSDictionary類型  
24.         NSDictionary *dictionary = value;  
25.         // Sort dictionary keys to ensure consistent ordering in query string, which is important when deserializing potentially ambiguous sequences, such as an array of dictionaries  
26.         for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {  
27.             id nestedValue = [dictionary objectForKey:nestedKey];  
28.             if (nestedValue) {  
29.                 [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)];  
30.             }  
31.         }  
32.     } else if ([value isKindOfClass:[NSArray class]]) {       //用戶傳進來的數(shù)據(jù)是NSArray類型  
33.         NSArray *array = value;  
34.         for (id nestedValue in array) {  
35.             [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[]", key], nestedValue)];  
36.         }  
37.     } else if ([value isKindOfClass:[NSSet class]]) {       //用戶傳進來的數(shù)據(jù)是NSSet類型  
38.         NSSet *set = value;  
39.         for (id obj in [set sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {  
40.             [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue(key, obj)];  
41.         }  
42.     } else {  
43.         [mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]];  
44.     }  
45.       
46.     return mutableQueryStringComponents;  
47. }  

? HTTP Header
AFNetworking幫你組裝好了一些HTTP請求頭,包括語言Accept-Language,根據(jù) [NSLocale preferredLanguages] 方法讀取本地語言,高速服務端自己能接受的語言。還有構建 User-Agent,以及提供Basic Auth 認證接口,幫你把用戶名密碼做 base64 編碼后放入 HTTP 請求頭。
相關代碼:
[objc] view plaincopy

1.  <span style="font-size:14px;">//AFNetworking幫組裝好了一些HTTP請求頭,包括語言Accept-Language,User-Agent等  
2.  - (instancetype)init {  
3.      self = [super init];  
4.      if (!self) {  
5.          return nil;  
6.      }  
7.    ......  
8.      // Accept-Language HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4  
9.      NSMutableArray *acceptLanguagesComponents = [NSMutableArray array];  
10.     //根據(jù) [NSLocale preferredLanguages] 方法讀取本地語言,高速服務端自己能接受的語言  
11.     [[NSLocale preferredLanguages] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOLBOOL *stop) {  
12.         float q = 1.0f - (idx * 0.1f);  
13.         [acceptLanguagesComponents addObject:[NSString stringWithFormat:@"%@;q=%0.1g", obj, q]];  
14.         *stop = q <= 0.5f;  
15.     }];  
16.     [self setValue:[acceptLanguagesComponents componentsJoinedByString:@", "] forHTTPHeaderField:@"Accept-Language"];  
17.     NSString *userAgent = nil;  
18. #pragma clang diagnostic push  
19. #pragma clang diagnostic ignored "-Wgnu"  
20. #if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)  
21.     // User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43  
22.     userAgent = [NSString stringWithFormat:@"%@/%@ (%@; iOS %@; Scale/%0.2f)", [[[NSBundle mainBundle] infoDictionary] objectForKey:(__bridge NSString *)kCFBundleExecutableKey] ?: [[[NSBundle mainBundle] infoDictionary] objectForKey:(__bridge NSString *)kCFBundleIdentifierKey], (__bridge id)CFBundleGetValueForInfoDictionaryKey(CFBundleGetMainBundle(), kCFBundleVersionKey) ?: [[[NSBundle mainBundle] infoDictionary] objectForKey:(__bridge NSString *)kCFBundleVersionKey], [[UIDevice currentDevice] model], [[UIDevice currentDevice] systemVersion], [[UIScreen mainScreen] scale]];  
23. #elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED)  
24.     userAgent = [NSString stringWithFormat:@"%@/%@ (Mac OS X %@)", [[[NSBundle mainBundle] infoDictionary] objectForKey:(__bridge NSString *)kCFBundleExecutableKey] ?: [[[NSBundle mainBundle] infoDictionary] objectForKey:(__bridge NSString *)kCFBundleIdentifierKey], [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleShortVersionString"] ?: [[[NSBundle mainBundle] infoDictionary] objectForKey:(__bridge NSString *)kCFBundleVersionKey], [[NSProcessInfo processInfo] operatingSystemVersionString]];  
25. #endif  
26. #pragma clang diagnostic pop  
27.     if (userAgent) {  
28.         if (![userAgent canBeConvertedToEncoding:NSASCIIStringEncoding]) {  
29.             NSMutableString *mutableUserAgent = [userAgent mutableCopy];  
30.             if (CFStringTransform((__bridge CFMutableStringRef)(mutableUserAgent), NULL, (__bridge CFStringRef)@"Any-Latin; Latin-ASCII; [:^ASCII:] Remove", false)) {  
31.                 userAgent = mutableUserAgent;  
32.             }  
33.         }  
34.         [self setValue:userAgent forHTTPHeaderField:@"User-Agent"];  
35.     }  
36.     // HTTP Method Definitions; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html  
37.     self.HTTPMethodsEncodingParametersInURI = [NSSet setWithObjects:@"GET", @"HEAD", @"DELETE", nil nil];  
38.     return self;  
39. }</span>  

? 其他格式化方式
HTTP請求參數(shù)不一定是要key=value形式,可以是任何形式的數(shù)據(jù),可以是json格式,蘋果的plist格式,二進制protobuf格式等,AFNetworking提供了方法可以很容易擴展支持這些格式,默認就實現(xiàn)了json和plist格式。詳情見源碼的類AFJSONRequestSerializer和AFPropertyListRequestSerializer。

2.構建multipart請求
構建Multipart請求是占篇幅很大的一個功能,AFURLRequestSerialization里2/3的代碼都是在做這個事。
? Multipart協(xié)議介紹
Multipart是HTTP協(xié)議為web表單新增的上傳文件的協(xié)議,協(xié)議文檔是rfc1867,它基于HTTP的POST方法,數(shù)據(jù)同樣是放在body上,跟普通POST方法的區(qū)別是數(shù)據(jù)不是key=value形式,key=value形式難以表示文件實體,為此Multipart協(xié)議添加了分隔符,有自己的格式結構。
? 實現(xiàn)
接下來說說怎樣構造Multipart里的數(shù)據(jù),最簡單的方式就是直接拼數(shù)據(jù),要發(fā)送一個文件,就直接把文件所有內容讀取出來,再按上述協(xié)議加上頭部和分隔符,拼接好數(shù)據(jù)后扔給NSURLRequest的body就可以發(fā)送了,很簡單。但這樣做是不可用的,因為文件可能很大,這樣拼數(shù)據(jù)把整個文件讀進內存,很可能把內存撐爆了。

第二種方法是不把文件讀出來,不在內存拼,而是新建一個臨時文件,在這個文件上拼接數(shù)據(jù),再把文件地址扔給NSURLRequest的bodyStream,這樣上傳的時候是分片讀取這個文件,不會撐爆內存,但這樣每次上傳都需要新建個臨時文件,對這個臨時文件的管理也挺麻煩的。

第三種方法是構建自己的數(shù)據(jù)結構,只保存要上傳的文件地址,邊上傳邊拼數(shù)據(jù),上傳是分片的,拼數(shù)據(jù)也是分片的,拼到文件實體部分時直接從原來的文件分片讀取。這方法沒上述兩種的問題,只是實現(xiàn)起來也沒上述兩種簡單,AFNetworking就是實現(xiàn)這第三種方法,而且還更進一步,除了文件,還可以添加多個其他不同類型的數(shù)據(jù),包括NSData,和InputStream。

AFNetworking 里 multipart 請求的使用方式是這樣:
[objc] view plaincopy

1.  <span style="font-size:14px;">    /* 
2.      urlstring:字符串型的鏈接 
3.      params:請求參數(shù) 
4.      datas:數(shù)組里存得是上傳的NSdata數(shù)據(jù) 
5.      */  
6.      AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];  
7.      [manager POST:urlstring parameters:params constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {     
8.          //將需要上傳的文件數(shù)據(jù)添加到formData  
9.          //循環(huán)遍歷需要上的文件數(shù)據(jù)  
10.         for (NSString *name in datas) {  
11.             NSData *data = datas[name];  
12.             [formData appendPartWithFileData:data name:name fileName:name mimeType:@"image/jpeg"];  
13.         }      
14.     } success:^(AFHTTPRequestOperation *operation, id responseObject) {  
15.         block(responseObject);  
16.     } failure:^(AFHTTPRequestOperation *operation, NSError *error) {  
17.         NSLog(@"網(wǎng)絡請求失敗:%@",error);  
18.     }];</span> 

這里通過constructingBodyWithBlock向使用者提供了一個AFStreamingMultipartFormData對象,調這個對象的幾種append方法就可以添加不同類型的數(shù)據(jù),包括FileURL/NSData/NSInputStream,AFStreamingMultipartFormData內部把這些append的數(shù)據(jù)轉成不同類型的 AFHTTPBodyPart,添加到自定義的 AFMultipartBodyStream 里。最后把 AFMultipartBodyStream 賦給原來 NSMutableURLRequest的bodyStream。NSURLConnection 發(fā)送請求時會讀取這個 bodyStream,在讀取數(shù)據(jù)時會調用這個 bodyStream 的 -read:maxLength: 方法,AFMultipartBodyStream 重寫了這個方法,不斷讀取之前 append進來的 AFHTTPBodyPart 數(shù)據(jù)直到讀完。

AFHTTPBodyPart 封裝了各部分數(shù)據(jù)的組裝和讀取,一個 AFHTTPBodyPart 就是一個數(shù)據(jù)塊。實際上三種類型 (FileURL/NSData/NSInputStream) 的數(shù)據(jù)在 AFHTTPBodyPart 都轉成 NSInputStream,讀取數(shù)據(jù)時只需讀這個 inputStream。inputStream 只保存了數(shù)據(jù)的實體,沒有包括分隔符和頭部,AFHTTPBodyPart 是邊讀取變拼接數(shù)據(jù),用一個狀態(tài)機確定現(xiàn)在數(shù)據(jù)讀取到哪一部份,以及保存這個狀態(tài)下已被讀取的字節(jié)數(shù),以此定位要讀的數(shù)據(jù)位置,詳見 AFHTTPBodyPart 的-read:maxLength:方法。

AFMultipartBodyStream封裝了整個multipart數(shù)據(jù)的讀取,主要是根據(jù)讀取的位置確定現(xiàn)在要讀哪一個AFHTTPBodyPart。AFStreamingMultipartFormData對外提供友好的append接口,并把構造好的AFMultipartBodyStream賦回給NSMutableURLRequest。 詳情請看在AFURLRequestSerialization類中定義的AFHTTPBodyPart類、AFMultipartBodyStream、AFStreamingMultipartFormData。

僅僅列舉幾段代碼:
[objc] view plaincopy

1.  <span style="font-size:14px;">/* 
2.       NSURLConnection 發(fā)送請求時會讀取這個 bodyStream,在讀取數(shù)據(jù)時會調用這個 bodyStream 的 
3.       -read:maxLength: 方法,AFMultipartBodyStream 重寫了這個方法,不斷讀取之前 append進來 
4.       的 AFHTTPBodyPart 數(shù)據(jù)直到讀完。 
5.       FMultipartBodyStream 重寫了這個方法,不斷讀取之前 append進來的 AFHTTPBodyPart 數(shù)據(jù)直到讀完。 
6.   */  
7.  - (NSInteger)read:(uint8_t *)buffer  
8.          maxLength:(NSUInteger)length  
9.  {  
10.     if ([self streamStatus] == NSStreamStatusClosed) {  
11.         return 0;  
12.     }  
13.     NSInteger totalNumberOfBytesRead = 0;  
14. #pragma clang diagnostic push  
15. #pragma clang diagnostic ignored "-Wgnu"  
16.     while ((NSUInteger)totalNumberOfBytesRead < MIN(length, self.numberOfBytesInPacket)) {  
17.         if (!self.currentHTTPBodyPart || ![self.currentHTTPBodyPart hasBytesAvailable]) {  
18.             if (!(self.currentHTTPBodyPart = [self.HTTPBodyPartEnumerator nextObject])) {  
19.                 break;  
20.             }  
21.         } else {  
22.             NSUInteger maxLength = length - (NSUInteger)totalNumberOfBytesRead;  
23.             NSInteger numberOfBytesRead = [self.currentHTTPBodyPart read:&buffer[totalNumberOfBytesRead] maxLength:maxLength];  
24.             if (numberOfBytesRead == -1) {  
25.                 self.streamError = self.currentHTTPBodyPart.inputStream.streamError;  
26.                 break;  
27.             } else {  
28.                 totalNumberOfBytesRead += numberOfBytesRead;  
29.                 if (self.delay > 0.0f) {  
30.                     [NSThread sleepForTimeInterval:self.delay];  
31.                 }  
32.             }  
33.         }  
34.     }  
35. #pragma clang diagnostic pop  
36.     return totalNumberOfBytesRead;  
37. }</span>  

[objc] view plaincopy

1.  <span style="font-size:14px;">//添加NSInputStream類型數(shù)據(jù)  
2.  - (void)appendPartWithInputStream:(NSInputStream *)inputStream  
3.                               name:(NSString *)name  
4.                           fileName:(NSString *)fileName  
5.                             length:(int64_t)length  
6.                           mimeType:(NSString *)mimeType  
7.  {  
8.      NSParameterAssert(name);  
9.      NSParameterAssert(fileName);  
10.     NSParameterAssert(mimeType);  
11.     NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary];  
12.     [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"; filename=\"%@\"", name, fileName] forKey:@"Content-Disposition"];  
13.     [mutableHeaders setValue:mimeType forKey:@"Content-Type"];  
14.     //把這些append進來的數(shù)據(jù)轉成不同類型的 AFHTTPBodyPart  
15.     AFHTTPBodyPart *bodyPart = [[AFHTTPBodyPart alloc] init];  
16.     bodyPart.stringEncoding = self.stringEncoding;  
17.     bodyPart.headers = mutableHeaders;  
18.     bodyPart.boundary = self.boundary;  
19.     bodyPart.body = inputStream;  
20.     bodyPart.bodyContentLength = (unsigned long long)length;  
21.     //添加 bodyPart 到自定義的 AFMultipartBodyStream 里  
22.     [self.bodyStream appendHTTPBodyPart:bodyPart];  
23. }</span>  

3.AFURLResponseSerialization
AFURLResponseSerialization主要的功能是處理返回數(shù)據(jù),告訴AFNetworking 以怎樣的方式接受數(shù)據(jù),如果后段接口都是標準的JSON數(shù)據(jù)格式,那么很愉快的就選擇了AFJSONResponseSerializer,在請求成功的Block中的responseObject就會是一個 AFNetworking 幫你解好檔的JSON,也就是一個NSDictionary對象。
AFURLResponseSerialization對數(shù)據(jù)的處理有以下幾個方式
? AFHTTPResponseSerializer
? AFJSONResponseSerializer
? AFXMLParserResponseSerializer
? AFXMLDocumentResponseSerializer ( Mac OS X 中)
? AFPropertyListResponseSerializer
? AFImageResponseSerializer
? AFCompoundResponseSerializer

[objc] view plaincopy

1.  //HTTP解析  
2.  + (instancetype)serializer;  
3.  //JSON解析  
4.  + (instancetype)serializerWithReadingOptions:(NSJSONReadingOptions)readingOptions;  
5.  //XML解析  
6.  + (instancetype)serializerWithXMLDocumentOptions:(NSUInteger)mask;  
7.  //Plist解析  
8.  + (instancetype)serializerWithFormat:(NSPropertyListFormat)format  
9.                           readOptions:(NSPropertyListReadOptions)readOptions;  
10. //Compound解析  
11. + (instancetype)compoundSerializerWithResponseSerializers:(NSArray *)responseSerializers;  

Serialization模塊就講到這了

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容