續(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模塊就講到這了