續(xù)AFNetworking核心類AFURLConnectionOperation的詳解
本篇我們來(lái)看AFNetworking的下一個(gè)模塊Serialization
其中包括AFURLRequestSerialization和AFURLResponseSerialization兩個(gè)類
我們主要講AFURLRequestSerialization柜思,因?yàn)锳FURLRequestSerialization的實(shí)現(xiàn)比AFURLResponseSerialization復(fù)雜得多源内,我們理解了AFURLRequestSerialization就不難理解AFURLResponseSerialization了徽鼎。
AFURLRequestSerialization的作用是協(xié)助構(gòu)建NSURLRequest
其主要實(shí)現(xiàn)以下兩個(gè)功能:
1.構(gòu)建普通請(qǐng)求:格式化請(qǐng)求參數(shù),生成HTTP Header枢纠。
2.構(gòu)建multipart請(qǐng)求。
1.構(gòu)建普通請(qǐng)求
? 格式化請(qǐng)求參數(shù)
一般我們請(qǐng)求都會(huì)按key=value的方式帶上各種參數(shù),GET方法參數(shù)直接加在URL上发钝,POST方法放在body上菇篡,NSURLRequest沒(méi)有封裝好這個(gè)參數(shù)的解析漩符,只能我們自己拼好字符串。AFNetworking提供了接口驱还,讓參數(shù)可以是NSDictionary, NSArray, NSSet這些類型嗜暴,再由內(nèi)部解析成字符串后賦給NSURLRequest。
轉(zhuǎn)換分為三部分:
第一部分是用戶傳進(jìn)來(lái)的數(shù)據(jù)铝侵,支持包含NSArray,NSDictionary,NSSet這三種數(shù)據(jù)結(jié)構(gòu)灼伤。
第二部分是轉(zhuǎn)換成AFNetworking內(nèi)自己的數(shù)據(jù)結(jié)構(gòu),每一個(gè)key-value對(duì)都用一個(gè)對(duì)象AFQueryStringPair表示咪鲜,作用是最后可以根據(jù)不同的字符串編碼生成各自的key=value字符串狐赡。主要函數(shù)是AFQueryStringPairsFromKeyAndValue。
第三部分是最后生成NSURLRequest可用的字符串?dāng)?shù)據(jù)疟丙,并且對(duì)參數(shù)進(jìn)行url編碼颖侄,在AFQueryStringFromParametersWithEncoding這個(gè)函數(shù)里。
最后在把數(shù)據(jù)賦給NSURLRequest時(shí)根據(jù)不同的HTTP方法分別處理享郊,對(duì)于GET/HEAD/DELETE方法览祖,把參數(shù)加到URL后面,對(duì)于其他如POST/PUT方法炊琉,把數(shù)據(jù)加到body上展蒂,并設(shè)好HTTP頭,告訴服務(wù)端字符串的編碼苔咪。
相關(guān)代碼:
[objc] view plaincopy
1. //第一部分用戶傳進(jìn)來(lái)的數(shù)據(jù)锰悼,parameters支持包含NSArray,NSDictionary,NSSet這三種數(shù)據(jù)結(jié)構(gòu)。
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>//調(diào)用<span style="font-family: 'Microsoft YaHei';">AFQueryStringFromParametersWithEncoding方法對(duì)傳進(jìn)來(lái)的數(shù)據(jù)進(jìn)行轉(zhuǎn)換</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. //對(duì)用戶傳進(jìn)來(lái)的數(shù)據(jù)進(jìn)行轉(zhuǎn)換
2. static NSString * AFQueryStringFromParametersWithEncoding(NSDictionary *parameters, NSStringEncoding stringEncoding) {
3. NSMutableArray *mutablePairs = [NSMutableArray array];
4. for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
5. //第二部分將用戶傳進(jìn)來(lái)的參數(shù)轉(zhuǎn)換成AFNetworking內(nèi)自己的數(shù)據(jù)結(jié)構(gòu)
6. [mutablePairs addObject:[pair URLEncodedStringValueWithEncoding:stringEncoding]];
7. }
8.
9. //第三部分生成NSURLRequest可用的字符串?dāng)?shù)據(jù)团赏,并且對(duì)參數(shù)進(jìn)行url編碼
10. return [mutablePairs componentsJoinedByString:@"&"];
11. }
12.
13. NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) {
14. return AFQueryStringPairsFromKeyAndValue(nil, dictionary);
15. }
16.
17. //將用戶傳進(jìn)來(lái)的參數(shù)轉(zhuǎn)換成AFNetworking內(nèi)自己的數(shù)據(jù)結(jié)構(gòu)箕般,每一個(gè)key-value對(duì)都用一個(gè)對(duì)象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]]) { //用戶傳進(jìn)來(lái)的是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]]) { //用戶傳進(jìn)來(lái)的數(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]]) { //用戶傳進(jìn)來(lái)的數(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請(qǐng)求頭丝里,包括語(yǔ)言Accept-Language曲初,根據(jù) [NSLocale preferredLanguages] 方法讀取本地語(yǔ)言,高速服務(wù)端自己能接受的語(yǔ)言杯聚。還有構(gòu)建 User-Agent臼婆,以及提供Basic Auth 認(rèn)證接口,幫你把用戶名密碼做 base64 編碼后放入 HTTP 請(qǐng)求頭械媒。
相關(guān)代碼:
[objc] view plaincopy
1. <span style="font-size:14px;">//AFNetworking幫組裝好了一些HTTP請(qǐng)求頭目锭,包括語(yǔ)言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] 方法讀取本地語(yǔ)言,高速服務(wù)端自己能接受的語(yǔ)言
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請(qǐng)求參數(shù)不一定是要key=value形式纷捞,可以是任何形式的數(shù)據(jù)痢虹,可以是json格式,蘋果的plist格式主儡,二進(jìn)制protobuf格式等奖唯,AFNetworking提供了方法可以很容易擴(kuò)展支持這些格式,默認(rèn)就實(shí)現(xiàn)了json和plist格式糜值。詳情見(jiàn)源碼的類AFJSONRequestSerializer和AFPropertyListRequestSerializer丰捷。
2.構(gòu)建multipart請(qǐng)求
構(gòu)建Multipart請(qǐng)求是占篇幅很大的一個(gè)功能,AFURLRequestSerialization里2/3的代碼都是在做這個(gè)事寂汇。
? Multipart協(xié)議介紹
Multipart是HTTP協(xié)議為web表單新增的上傳文件的協(xié)議病往,協(xié)議文檔是rfc1867,它基于HTTP的POST方法骄瓣,數(shù)據(jù)同樣是放在body上停巷,跟普通POST方法的區(qū)別是數(shù)據(jù)不是key=value形式,key=value形式難以表示文件實(shí)體榕栏,為此Multipart協(xié)議添加了分隔符畔勤,有自己的格式結(jié)構(gòu)。
? 實(shí)現(xiàn)
接下來(lái)說(shuō)說(shuō)怎樣構(gòu)造Multipart里的數(shù)據(jù)扒磁,最簡(jiǎn)單的方式就是直接拼數(shù)據(jù)庆揪,要發(fā)送一個(gè)文件,就直接把文件所有內(nèi)容讀取出來(lái)妨托,再按上述協(xié)議加上頭部和分隔符缸榛,拼接好數(shù)據(jù)后扔給NSURLRequest的body就可以發(fā)送了,很簡(jiǎn)單兰伤。但這樣做是不可用的内颗,因?yàn)槲募赡芎艽螅@樣拼數(shù)據(jù)把整個(gè)文件讀進(jìn)內(nèi)存医清,很可能把內(nèi)存撐爆了起暮。
第二種方法是不把文件讀出來(lái)卖氨,不在內(nèi)存拼会烙,而是新建一個(gè)臨時(shí)文件负懦,在這個(gè)文件上拼接數(shù)據(jù),再把文件地址扔給NSURLRequest的bodyStream柏腻,這樣上傳的時(shí)候是分片讀取這個(gè)文件纸厉,不會(huì)撐爆內(nèi)存,但這樣每次上傳都需要新建個(gè)臨時(shí)文件五嫂,對(duì)這個(gè)臨時(shí)文件的管理也挺麻煩的颗品。
第三種方法是構(gòu)建自己的數(shù)據(jù)結(jié)構(gòu),只保存要上傳的文件地址沃缘,邊上傳邊拼數(shù)據(jù)躯枢,上傳是分片的,拼數(shù)據(jù)也是分片的槐臀,拼到文件實(shí)體部分時(shí)直接從原來(lái)的文件分片讀取锄蹂。這方法沒(méi)上述兩種的問(wèn)題,只是實(shí)現(xiàn)起來(lái)也沒(méi)上述兩種簡(jiǎn)單水慨,AFNetworking就是實(shí)現(xiàn)這第三種方法得糜,而且還更進(jìn)一步,除了文件晰洒,還可以添加多個(gè)其他不同類型的數(shù)據(jù)朝抖,包括NSData,和InputStream谍珊。
AFNetworking 里 multipart 請(qǐng)求的使用方式是這樣:
[objc] view plaincopy
1. <span style="font-size:14px;"> /*
2. urlstring:字符串型的鏈接
3. params:請(qǐng)求參數(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)絡(luò)請(qǐng)求失斨涡:%@",error);
18. }];</span>
這里通過(guò)constructingBodyWithBlock向使用者提供了一個(gè)AFStreamingMultipartFormData對(duì)象,調(diào)這個(gè)對(duì)象的幾種append方法就可以添加不同類型的數(shù)據(jù)抬驴,包括FileURL/NSData/NSInputStream炼七,AFStreamingMultipartFormData內(nèi)部把這些append的數(shù)據(jù)轉(zhuǎn)成不同類型的 AFHTTPBodyPart,添加到自定義的 AFMultipartBodyStream 里布持。最后把 AFMultipartBodyStream 賦給原來(lái) NSMutableURLRequest的bodyStream豌拙。NSURLConnection 發(fā)送請(qǐng)求時(shí)會(huì)讀取這個(gè) bodyStream,在讀取數(shù)據(jù)時(shí)會(huì)調(diào)用這個(gè) bodyStream 的 -read:maxLength: 方法题暖,AFMultipartBodyStream 重寫了這個(gè)方法按傅,不斷讀取之前 append進(jìn)來(lái)的 AFHTTPBodyPart 數(shù)據(jù)直到讀完。
AFHTTPBodyPart 封裝了各部分?jǐn)?shù)據(jù)的組裝和讀取胧卤,一個(gè) AFHTTPBodyPart 就是一個(gè)數(shù)據(jù)塊唯绍。實(shí)際上三種類型 (FileURL/NSData/NSInputStream) 的數(shù)據(jù)在 AFHTTPBodyPart 都轉(zhuǎn)成 NSInputStream,讀取數(shù)據(jù)時(shí)只需讀這個(gè) inputStream枝誊。inputStream 只保存了數(shù)據(jù)的實(shí)體况芒,沒(méi)有包括分隔符和頭部,AFHTTPBodyPart 是邊讀取變拼接數(shù)據(jù)叶撒,用一個(gè)狀態(tài)機(jī)確定現(xiàn)在數(shù)據(jù)讀取到哪一部份绝骚,以及保存這個(gè)狀態(tài)下已被讀取的字節(jié)數(shù)耐版,以此定位要讀的數(shù)據(jù)位置,詳見(jiàn) AFHTTPBodyPart 的-read:maxLength:方法压汪。
AFMultipartBodyStream封裝了整個(gè)multipart數(shù)據(jù)的讀取粪牲,主要是根據(jù)讀取的位置確定現(xiàn)在要讀哪一個(gè)AFHTTPBodyPart。AFStreamingMultipartFormData對(duì)外提供友好的append接口止剖,并把構(gòu)造好的AFMultipartBodyStream賦回給NSMutableURLRequest腺阳。 詳情請(qǐng)看在AFURLRequestSerialization類中定義的AFHTTPBodyPart類、AFMultipartBodyStream穿香、AFStreamingMultipartFormData亭引。
僅僅列舉幾段代碼:
[objc] view plaincopy
1. <span style="font-size:14px;">/*
2. NSURLConnection 發(fā)送請(qǐng)求時(shí)會(huì)讀取這個(gè) bodyStream,在讀取數(shù)據(jù)時(shí)會(huì)調(diào)用這個(gè) bodyStream 的
3. -read:maxLength: 方法皮获,AFMultipartBodyStream 重寫了這個(gè)方法痛侍,不斷讀取之前 append進(jìn)來(lái)
4. 的 AFHTTPBodyPart 數(shù)據(jù)直到讀完。
5. FMultipartBodyStream 重寫了這個(gè)方法魔市,不斷讀取之前 append進(jìn)來(lái)的 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進(jìn)來(lái)的數(shù)據(jù)轉(zhuǎn)成不同類型的 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主要的功能是處理返回?cái)?shù)據(jù),告訴AFNetworking 以怎樣的方式接受數(shù)據(jù)待德,如果后段接口都是標(biāo)準(zhǔn)的JSON數(shù)據(jù)格式葬毫,那么很愉快的就選擇了AFJSONResponseSerializer炕泳,在請(qǐng)求成功的Block中的responseObject就會(huì)是一個(gè) AFNetworking 幫你解好檔的JSON文判,也就是一個(gè)NSDictionary對(duì)象物赶。
AFURLResponseSerialization對(duì)數(shù)據(jù)的處理有以下幾個(gè)方式
? 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模塊就講到這了