这篇就讲到了跟请求相关的类了
关于AFNetworking 3.0 源码解读 的文章篇幅都会很长,因为不仅仅要把代码进行详细的的解释,还会大概讲解和代码相关的知识点。
上半篇: URI编码的知识
关于什么叫URI编码和为什么要编码,请看我转载的这篇文章 url 编码(percentcode 百分号编码)
给定一个URL:http://www.imkevinyang.com/2009/08/%E8%AF%A6%E8%A7%A3javascript%E4%B8%AD%E7%9A%84url%E7%BC%96%E8%A7%A3%E7%A0%81.html
其实这个URL并不算复杂,查询部分(query)都是简单的key:NSString value: NSString 还有一些比这个更复杂的例子。
我们现在这埋个伏笔:假如你的请求参数是这样的,那么编码后的URL是什么样呢? 不用着急,后边会做出详细解答。
根据RFC 3986的规定:URL百分比编码的保留字段分为:
1. ':' '#' '[' ']' '@' '?' '/'
2. '!' '$' '&' ''' '(' ')' '*' '+' ',' ';' '='
在对查询字段百分比编码时,'?'和'/'可以不用编码,其他的都要进行编码。
这个方法是上边的那个方法的实现部分。
这里值得注意的是:
1. 字符串需要经过过滤 ,过滤法则通过 NSMutableCharacterSet 实现。添加规则后,只对规则内的因子进行编码。
2. 为了处理类似emoji这样的字符串,rangeOfComposedCharacterSequencesForRange 使用了while循环来处理,也就是把字符串按照batchSize分割处理完再拼回。
该方法最终把类型为NSDictionary的参数处理为字符串类型。
举个简单的没进行百分百编码的例子:
如果参数是NSDictionary *info = @{@"name":@"zhangsan",@"age":20} ;
AFQueryStringFromParameters(info) 的结果就是:name=zhangsan&age=20 (没有百分比编码)
上边的AFQueryStringFromParameters 这个方法的实现部分还是有点复杂的。我们先逐步分析一下。
可以看出最终转化后的结果肯定有一个=号,这个等号左边和右边都有一个数据。而且这个数据是字符串,因此我们定义一个模型用来记录左边和右边的数据。
有名字就可得知这个类 代表一个查询字符串对。field代表=号左边的,value代表=号右边的数据。
核心方法URLEncodedStringValue 可定也是把左右的数据使用AFPercentEscapedStringFromString函数百分比编码后用=拼接起来。
看到那两个方法的声明了吗?我们不看下边的代码也应该能猜到这个核心方法的实现肯定借助了这两个被声明的方法。当然也可以调整函数的顺序,没有这么做也是为了可读性的考虑吧。
看这个方法的实现,由一个字符串数组根据连接符&拼接成一个字符串并返回。
这行代码也说明上边声明的AFQueryStringPairsFromDictionary函数接受一个字典参数,最终返回一个装着AFQueryStringPair模型的数组。便利数组后取出模型,然后转换成字符串,保存到新的数组中。
接下来在看看AFQueryStringPairsFromDictionary 这个函数的实现部分。
可以看出,这个函数又借助了另外一个函数AFQueryStringPairsFromKeyAndValue
其实到了这里,有一个疑问,感觉AFQueryStringPairsFromDictionary这个函数有点多余,这两个函数差不多啊,能不能使用一个呢?
其实也是可以的,这里这么写我猜应该也是出于函数编程的想法吧。更容易理解为根据一个字典得到一个数组,当然还有就是参数的限制,这里只能接受字典,下边的方法的value是id类型。(如有错误,请留言指正,谢谢)
改成这样也行:
好了,在来看看下班的函数
1 // 把 key value 数据转换成数组 2 NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) { 3 NSMutableArray *mutableQueryStringComponents = [NSMutableArray array]; 4 5 //排序: 升序 6 NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)]; 7 8 // 如果参数的value 是字典数据 9 /* 10 举个例子: 11 key: info 12 value: @{@"name":@"zhangsan",@"age": @"30"} 13 */ 14 if ([value isKindOfClass:[NSDictionary class]]) { 15 NSDictionary *dictionary = value; 16 // Sort dictionary keys to ensure consistent ordering in query string, which is important when deserializing potentially ambiguous sequences, such as an array of dictionaries 17 for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]]) { 18 id nestedValue = dictionary[nestedKey]; 19 if (nestedValue) { 20 [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)]; 21 } 22 } 23 } else if ([value isKindOfClass:[NSArray class]]) { 24 NSArray *array = value; 25 for (id nestedValue in array) { 26 [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[]", key], nestedValue)]; 27 } 28 } else if ([value isKindOfClass:[NSSet class]]) { 29 NSSet *set = value; 30 for (id obj in [set sortedArrayUsingDescriptors:@[ sortDescriptor ]]) { 31 [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue(key, obj)]; 32 } 33 } else { 34 [mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]]; 35 } 36 37 return mutableQueryStringComponents; 38 }
这里值得注意的有两点:
1. 函数迭代的一种思想 ,当value时NSDictionary/NSArray/NSSet 这种集合类型的时候,通过调用自身这种思想来实现功能,这个很重要,在很多地方能够用到,
看看YYModel 源码解读(二)之NSObject+YYModel.h (4)就知道了。
2. 就是为什么排序呢?这个不是很懂,如果有懂得,可以留言。
=============================== 分割线 ============================
介绍了URI编码的一些知识,我们继续看源码,但是,往下看,就感觉有点乱了,1000多行的带码中有好几个小类,要是按照顺序一点点看,难免让人烦躁,难度也不小。
因此,我决定对它进行拆分,分成好几部分进行介绍,然后再串联起来。
那就先从HTTPBody开始说起。大家在使用AFNetworking上传图片或者其他文件的时候会用到AFMultipartFormData这个协议,然后调用协议内的方法,拼接数据,这个使用起来非常简单,但是它内部又是如何实现的呢?接下来就对他进行解析。
我们先来看一个完整的post请求:
某app的一个登录POST请求: POST / HTTP/1.1 Host: log.nuomi.com Content-Type: multipart/form-data; boundary=Boundary+6D3E56AA6EAA83B7 Cookie: access_log=7bde65268e2260bb0a85c7de2c67c468; BAIDUID=428D86FDBA6028DE2A5496BE3E7FC308:FG=1; BAINUOCUID=4368e1b7499c455dcd437da336ca1ca9feb8f57d; BDUSS=Ecwa3NvN1NjNWhsVGxWZktFfkc2bzJxQjZ3RFJpTFBiUzZqZUJZU0ZTSmZsN0ZXQVFBQUFBJCQAAAAAAAAAAAEAAABxbLRYWXV1dXV3dXV1AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAF8KilZfCopWR; bn_na_copid=60139b4b2ba75706fc384d987c2e4007; bn_na_ctag=W3siayI6Imljb25fMSIsInMiOiJ0dWFuIiwidiI6IjMyNiIsInQiOiIxNDUxODg2OTE0In1d; channel=user_center%7C%7C; channel_content=; channel_webapp=webapp; condition=6.0.3; domainUrl=sh; na_qab=6be39bfce918bb7b51887412e009faa6; UID=1488219249 Connection: keep-alive Accept: */* User-Agent: Bainuo/6.1.0 (iPhone; iOS 9.0; Scale/2.00) Accept-Language: zh-Hans-CN;q=1, en-CN;q=0.9 Content-Length: 22207 Accept-Encoding: gzip, deflate --Boundary+6D3E56AA6EAA83B7 /// 开始 Content-Disposition: form-data; name="app_version" 6.1.0 --Boundary+6D3E56AA6EAA83B7
HTTP请求头我们就暂时不说了,看这个body的内容
--Boundary+6D3E56AA6EAA83B7 /// 开始 Content-Disposition: form-data; name="app_version" 6.1.0 --Boundary+6D3E56AA6EAA83B7
组成分为4个部分: 1.初始边界 2.body头 3.body 4.结束边界。 下边就会用着这些知识。
我把AFMultipartFormData协议提供的方法总结成了一张图?
当然这几个方法在下边都会有介绍。我们还是介绍body部分。
看着个body类的声明就能够知道body包含的所有信息,这些信息也和例子中的body的4大组成部分息息相关。
对AFHTTPBodyPart的扩展部分,可以看出曾加了三个属性:
1. phase:使用枚举包装body4大组成部分
2. 输入流
3. 每个组成部分的位置
增加了两个方法:
- (BOOL)transitionToNextPhase; 转移到下一个阶段
- (NSInteger)readData:(NSData *)data
intoBuffer:(uint8_t *)buffer
maxLength:(NSUInteger)length; 读取数据
实现部分呢,这个就没什么好解释的了,我们看看transitionToNextPhase这个函数的实现部分。
这个就是把这几个阶段,依次往后传递,值得注意的是在第②个阶段打开了流,第③个阶段关闭了流。
body可能有好几种类型,根据不同的类型返回不同方法创建的NSInputStream 。
这个方法是根据headers字典来拼接body头,看个例子:
Content-Disposition: form-data; name="record"; filename="record.jpg" Content-Type: application/json
规则:Content-Disposition + : + 空格 + 其他 然后以 结尾,在头部结束部分再拼接一个
这个比较好理解了,HTTP协议就是这么用的,在这个例子中self.headers 的值为:
{ "Content-Disposition" = "form-data; name="record"; filename="record.jpg""; "Content-Type" = "application/json"; }
这个方法用来获取body的大小的。方法实现比较简单,需要注意的是初始和结束边界的问题,要做个判断,然后调用函数转换为NSData,计算大小
该方法返回是否还有数据可读。
1 - (NSInteger)read:(uint8_t *)buffer 2 maxLength:(NSUInteger)length 3 { 4 NSInteger totalNumberOfBytesRead = 0; 5 6 if (_phase == AFEncapsulationBoundaryPhase) { 7 NSData *encapsulationBoundaryData = [([self hasInitialBoundary] ? AFMultipartFormInitialBoundary(self.boundary) : AFMultipartFormEncapsulationBoundary(self.boundary)) dataUsingEncoding:self.stringEncoding]; 8 totalNumberOfBytesRead += [self readData:encapsulationBoundaryData intoBuffer:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)]; 9 } 10 11 if (_phase == AFHeaderPhase) { 12 NSData *headersData = [[self stringForHeaders] dataUsingEncoding:self.stringEncoding]; 13 totalNumberOfBytesRead += [self readData:headersData intoBuffer:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)]; 14 } 15 16 if (_phase == AFBodyPhase) { 17 NSInteger numberOfBytesRead = 0; 18 19 numberOfBytesRead = [self.inputStream read:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)]; 20 if (numberOfBytesRead == -1) { 21 return -1; 22 } else { 23 totalNumberOfBytesRead += numberOfBytesRead; 24 25 if ([self.inputStream streamStatus] >= NSStreamStatusAtEnd) { 26 [self transitionToNextPhase]; 27 } 28 } 29 } 30 31 if (_phase == AFFinalBoundaryPhase) { 32 NSData *closingBoundaryData = ([self hasFinalBoundary] ? [AFMultipartFormFinalBoundary(self.boundary) dataUsingEncoding:self.stringEncoding] : [NSData data]); 33 totalNumberOfBytesRead += [self readData:closingBoundaryData intoBuffer:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)]; 34 } 35 36 return totalNumberOfBytesRead; 37 } 38 39 - (NSInteger)readData:(NSData *)data 40 intoBuffer:(uint8_t *)buffer 41 maxLength:(NSUInteger)length 42 { 43 #pragma clang diagnostic push 44 #pragma clang diagnostic ignored "-Wgnu" 45 // 比较数据和允许的最大长度 选取比较小的那个 46 NSRange range = NSMakeRange((NSUInteger)_phaseReadOffset, MIN([data length] - ((NSUInteger)_phaseReadOffset), length)); 47 48 // copy data中range的数据到buffer 49 [data getBytes:buffer range:range]; 50 #pragma clang diagnostic pop 51 52 _phaseReadOffset += range.length; 53 54 if (((NSUInteger)_phaseReadOffset) >= [data length]) { 55 [self transitionToNextPhase]; 56 } 57 58 return (NSInteger)range.length; 59 }
这两个方法是把body数据写入到buffer中。通过观察着这两个方法,可得知,这两个方法肯定在其他的代码中的某个循环中被调用,目的是得到想要的数据格式。
举个正常点的例子吧:
假如说初始的边界的大小为10 头部为20 主体为30 结束边界为10 最大长度为100
在
- (NSInteger)readData:(NSData *)data intoBuffer:(uint8_t *)buffer maxLength:(NSUInteger)length
内部大概是这样的:
_phaseReadOffset == 0 -》 range == (0,10) -》 buffer[0-10] == data(10) -》 _phaseReadOffset == 10
由于_phaseReadOffset == 10 >= 10 -》 [self transitionToNextPhase]; 进入下一个阶段。
由于这个小类遵守NSCopying协议,加了下边方法。
============================= 分割线 ==============================
在这里补充一点。对于NSInputStream的使用来说,我们要手动实现方法
- (NSInteger)read:(uint8_t *)buffer
maxLength:(NSUInteger)length;
这样当我们使用open打开流的时候,就会调用这个方法,我们需要在这个方法中处理我们的逻辑。
这也是上边中为什么能完整的读取body中的数据的解释。
还有这个maxLength和buffer有关系,uint8_t在mac 64为上为32768.
============================= 分割线 ===========================
下边介绍 AFMultipartBodyStream
其实AFHTTPBodyPart就像是一个个具体的数据一样,而AFMultipartBodyStream更像是一个管道,和body相连,数据从body沿着管道流入request中去。
这层抽象的概念还是蛮重要的。再设计之初,这两个抽象类就应该各自完成各自的任务,即使body中也有stream 但那也只属于body自身的业务。
它是继承自NSInputStream的,那么为什么要继承自这个呢? 就要看下边的这个方法了。
由此可以看出,数据最终是通过setHTTPBodySteam方法传递给Request的。是一个NSInputStream类型,因此AFMultipartBodyStream 继承自NSInputStream。
这个是又增加了一些需要的属性,这个会在下边的方法中介绍到,在这里就先略过。
初始化一些属性。
重置初始边界和结束边界,当有多个body的时候,只需要拼接一个头部边界和一个结束边界就可以了。
1 - (NSInteger)read:(uint8_t *)buffer 2 maxLength:(NSUInteger)length 3 { 4 5 NSLog(@"length:%ld",(long)length); 6 if ([self streamStatus] == NSStreamStatusClosed) { 7 return 0; 8 } 9 10 NSInteger totalNumberOfBytesRead = 0; 11 12 #pragma clang diagnostic push 13 #pragma clang diagnostic ignored "-Wgnu" 14 // 遍历读取数据 15 while ((NSUInteger)totalNumberOfBytesRead < MIN(length, self.numberOfBytesInPacket)) { 16 // 如果当前读取的body不存在或者body没有可读字节 17 if (!self.currentHTTPBodyPart || ![self.currentHTTPBodyPart hasBytesAvailable]) { 18 //把下一个body赋值给当前的body 如果下一个为nil 就退出循环 19 if (!(self.currentHTTPBodyPart = [self.HTTPBodyPartEnumerator nextObject])) { 20 break; 21 } 22 } else { // 当前body存在 23 24 // 剩余可读文件的大小 25 NSUInteger maxLength = MIN(length, self.numberOfBytesInPacket) - (NSUInteger)totalNumberOfBytesRead; 26 NSLog(@"maxLength:%ld",(long)maxLength); 27 // 把当前的body的数据读入到buffer中 28 NSInteger numberOfBytesRead = [self.currentHTTPBodyPart read:&buffer[totalNumberOfBytesRead] maxLength:maxLength]; 29 // NSInteger numberOfBytesRead = 0; 30 NSLog(@"numberOfBytesRead:%ld",(long)maxLength); 31 if (numberOfBytesRead == -1) { 32 self.streamError = self.currentHTTPBodyPart.inputStream.streamError; 33 break; 34 } else { 35 36 // 37 totalNumberOfBytesRead += numberOfBytesRead; 38 39 if (self.delay > 0.0f) { 40 [NSThread sleepForTimeInterval:self.delay]; 41 } 42 } 43 } 44 } 45 #pragma clang diagnostic pop 46 47 return totalNumberOfBytesRead; 48 }
这个方法是AFMultipartBodyStream通过body读取数据的核心方法。下面通过举一个例子来看看这个方法究竟是怎么工作的?
1. 假如我们上传一张图片img.png 他的大小为80000,也就是差不多80k吧。
2. 通过AFMultipartBodyStream读取数据,会首先调用上边的方法。读取数据并不是一次性读取的,而是分批分次读取的,这这个方法中,每次读取的大小为32k,也就是32*1024 = 32768的大小。
3. 第一次调用后self.currentHTTPBodyPart 指向我们的img.png 通过
NSInteger numberOfBytesRead = [self.currentHTTPBodyPart read:&buffer[totalNumberOfBytesRead] maxLength:maxLength]; 方法在body中读取了32768大小的数据保存到了缓存buffer中。
4. 由于整个图片大小是80000 一次调用只读取了32768 还有数据没读完,一次这个方法还会再次被调用。
5. 第二次调用这个方法,由于[self.currentHTTPBodyPart hasBytesAvailable]还有数据,所以还是会走到else的方法中,self.currentHTTPBodyPart并没有指向别的body。因此继续执行 3.的方法。
6. 至于为什么能接着从上次的已读取的数据开始读数据,这个是body内部封装实现的,可参考本文上边关于body的介绍。
7. 重复 3 4 5 的步骤,直到没有数据可读时,stream就会关闭流。到此我们的突变数据就以流的形式上传到服务器了。
ps: 如有错误地方,请给与指正哦。
关闭读取缓存和设置getter方法
重写open close方法
重写NSSteam的方法,说实话不太明白为什么写这个??
返回总大小
设置 跟CoreFoundation相关的方法。
实现NSCopying协议。
============================= 分割线 ==============================
在写一个功能的时候,我们往往并不能把业务功能分隔的很完美,这个就跟经验相关了,通过封装AFHTTPBodyPart和AFMultipartBodyStream这两个小工具,我们已经能够拿到数据了。还记得之前的 AFMultipartFormData 协议吗?在使用时,我们调用协议的方法,来把数据上传的。理所当然,我们只要让AFMultipartBodyStream实现这个协议不就可以做到我们的目的了吗?
但这显然是不够好的,因此AFNetworking 又 再次对 AFHTTPBodyPart和AFMultipartBodyStream 进行了封装。
看到这个类,应该能联想到它的作用了。再看这个方法
那个formData 正好是AFStreamingMultipartFormData啊,他也遵守AFMultipartFormData协议,这个下边会详细说。
之所以说他起到了连接request和数据的作用,就是因为用于这两个属性。
初始化方法,在这里,创建了边界和管道。
1 - (BOOL)appendPartWithFileURL:(NSURL *)fileURL 2 name:(NSString *)name 3 fileName:(NSString *)fileName 4 mimeType:(NSString *)mimeType 5 error:(NSError * __autoreleasing *)error 6 { 7 NSParameterAssert(fileURL); 8 NSParameterAssert(name); 9 NSParameterAssert(fileName); 10 NSParameterAssert(mimeType); 11 12 if (![fileURL isFileURL]) { 13 NSDictionary *userInfo = @{NSLocalizedFailureReasonErrorKey: NSLocalizedStringFromTable(@"Expected URL to be a file URL", @"AFNetworking", nil)}; 14 if (error) { 15 *error = [[NSError alloc] initWithDomain:AFURLRequestSerializationErrorDomain code:NSURLErrorBadURL userInfo:userInfo]; 16 } 17 18 return NO; 19 } else if ([fileURL checkResourceIsReachableAndReturnError:error] == NO) { 20 NSDictionary *userInfo = @{NSLocalizedFailureReasonErrorKey: NSLocalizedStringFromTable(@"File URL not reachable.", @"AFNetworking", nil)}; 21 if (error) { 22 *error = [[NSError alloc] initWithDomain:AFURLRequestSerializationErrorDomain code:NSURLErrorBadURL userInfo:userInfo]; 23 } 24 25 return NO; 26 } 27 28 NSDictionary *fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:[fileURL path] error:error]; 29 if (!fileAttributes) { 30 return NO; 31 } 32 33 NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary]; 34 [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name="%@"; filename="%@"", name, fileName] forKey:@"Content-Disposition"]; 35 [mutableHeaders setValue:mimeType forKey:@"Content-Type"]; 36 37 AFHTTPBodyPart *bodyPart = [[AFHTTPBodyPart alloc] init]; 38 bodyPart.stringEncoding = self.stringEncoding; 39 bodyPart.headers = mutableHeaders; 40 bodyPart.boundary = self.boundary; 41 bodyPart.body = fileURL; 42 bodyPart.bodyContentLength = [fileAttributes[NSFileSize] unsignedLongLongValue]; 43 [self.bodyStream appendHTTPBodyPart:bodyPart]; 44 45 return YES; 46 }
这个方法其实很好理解,就是通过本地的一个文件的URL获取数据。
我们通过这个URL能够获取到一些和文件相关的信息,然后再进行一些必要的判断,最后生成一个AFHTTPBodyPart模型,最终把这个模型拼接到管道的模型数组中。就完成任务了。也就是说,一个良好的设计,在使用时就会很顺畅。
我们能够在这个方法中学到的“
1. NSParameterAssert() 用来判断参数是否为空,如果为空就抛出异常
2. 使用isFileURL 判断一个URL是否为fileURL 使用checkResourceIsReachableAndReturnError判断路径能够到达
3. 使用 [[NSFileManager defaultManager] attributesOfItemAtPath:[fileURL path] error:error] 获取本地文件属性
1 - (BOOL)appendPartWithFileURL:(NSURL *)fileURL 2 name:(NSString *)name 3 error:(NSError * __autoreleasing *)error 4 { 5 NSParameterAssert(fileURL); 6 NSParameterAssert(name); 7 8 NSString *fileName = [fileURL lastPathComponent]; 9 NSString *mimeType = AFContentTypeForPathExtension([fileURL pathExtension]); 10 11 return [self appendPartWithFileURL:fileURL name:name fileName:fileName mimeType:mimeType error:error]; 12 }
再来看这个方法,参数比上边的那个方法少了几个,不难猜测,应该是有些参数会采取计算或者默认的方法初始化的。
1. lastPathComponent ,https://www.baidu.com/abc.html 结果就是abc.html
2. pathExtension https://www.baidu.com/abc.html 结果就是html
这里边有一个AFContentTypeForPathExtension() 方法,我们看看是怎么实现的:
static inline NSString * AFContentTypeForPathExtension(NSString *extension) { NSString *UTI = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)extension, NULL); NSString *contentType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass((__bridge CFStringRef)UTI, kUTTagClassMIMEType); if (!contentType) { return @"application/octet-stream"; } else { return contentType; } }
首先这算是一个内联函数,可以根据一个后缀名获取contentType 这个记住就行了,中间的两个函数包含在
#import <MobileCoreServices/MobileCoreServices.h>中。
1 - (void)appendPartWithInputStream:(NSInputStream *)inputStream 2 name:(NSString *)name 3 fileName:(NSString *)fileName 4 length:(int64_t)length 5 mimeType:(NSString *)mimeType 6 { 7 NSParameterAssert(name); 8 NSParameterAssert(fileName); 9 NSParameterAssert(mimeType); 10 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 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 21 bodyPart.bodyContentLength = (unsigned long long)length; 22 23 [self.bodyStream appendHTTPBodyPart:bodyPart]; 24 }
通过流来获取数据。这个没什么好说的。
1 - (void)appendPartWithFileData:(NSData *)data 2 name:(NSString *)name 3 fileName:(NSString *)fileName 4 mimeType:(NSString *)mimeType 5 { 6 NSParameterAssert(name); 7 NSParameterAssert(fileName); 8 NSParameterAssert(mimeType); 9 10 NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary]; 11 [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name="%@"; filename="%@"", name, fileName] forKey:@"Content-Disposition"]; 12 [mutableHeaders setValue:mimeType forKey:@"Content-Type"]; 13 14 [self appendPartWithHeaders:mutableHeaders body:data]; 15 } 16 17 - (void)appendPartWithFormData:(NSData *)data 18 name:(NSString *)name 19 { 20 NSParameterAssert(name); 21 22 NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary]; 23 [mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name="%@"", name] forKey:@"Content-Disposition"]; 24 25 [self appendPartWithHeaders:mutableHeaders body:data]; 26 } 27 28 - (void)appendPartWithHeaders:(NSDictionary *)headers 29 body:(NSData *)body 30 { 31 NSParameterAssert(body); 32 33 AFHTTPBodyPart *bodyPart = [[AFHTTPBodyPart alloc] init]; 34 bodyPart.stringEncoding = self.stringEncoding; 35 bodyPart.headers = headers; 36 bodyPart.boundary = self.boundary; 37 bodyPart.bodyContentLength = [body length]; 38 bodyPart.body = body; 39 40 [self.bodyStream appendHTTPBodyPart:bodyPart]; 41 }
这三个方法,是根据NSData 获取数据的方法,为了尽量不出现重复的代码,抽象了这个方法
- (void)appendPartWithHeaders:(NSDictionary *)headers
body:(NSData *)body
- (void)throttleBandwidthWithPacketSize:(NSUInteger)numberOfBytes delay:(NSTimeInterval)delay { self.bodyStream.numberOfBytesInPacket = numberOfBytes; self.bodyStream.delay = delay; }
设置管道的两个属性
1 - (NSMutableURLRequest *)requestByFinalizingMultipartFormData { 2 if ([self.bodyStream isEmpty]) { 3 return self.request; 4 } 5 6 // Reset the initial and final boundaries to ensure correct Content-Length 7 [self.bodyStream setInitialAndFinalBoundaries]; 8 [self.request setHTTPBodyStream:self.bodyStream]; 9 10 [self.request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", self.boundary] forHTTPHeaderField:@"Content-Type"]; 11 [self.request setValue:[NSString stringWithFormat:@"%llu", [self.bodyStream contentLength]] forHTTPHeaderField:@"Content-Length"]; 12 13 return self.request; 14 }
这个是把数据跟请求建立联系的核心方法,通过 [self.request setHTTPBodyStream:self.bodyStream];这个方法建立联系,然后设置Content-Type
和 Content-Length 最后返回一个NSMutableURLRequest。
================================= 分割线 =====================================
看完了上边所有的算是辅助功能的小工具类后,我们进入正题,也正验证了那句话,一切复杂的问题,分割成若干子问题后,就很容已解决。
我们先看看AFHTTPRequestSerializer 暴露出来的接口有什么
我们给每一个暴露出来的属性或方法添加一个标记,到后边实现方法的时候我们就使用标记来代替名称
1. 标记①
2. 标记②
3. 标记③
这个要仔细介绍下,在某些特殊的场景下还是能用到的。我们点开NSURLRequestCachePolicy 可以看到是一个枚举值
1 typedef NS_ENUM(NSUInteger, NSURLRequestCachePolicy) 2 { 3 NSURLRequestUseProtocolCachePolicy = 0, 4 5 NSURLRequestReloadIgnoringLocalCacheData = 1, 6 NSURLRequestReloadIgnoringLocalAndRemoteCacheData = 4, // Unimplemented 7 NSURLRequestReloadIgnoringCacheData = NSURLRequestReloadIgnoringLocalCacheData, 8 9 NSURLRequestReturnCacheDataElseLoad = 2, 10 NSURLRequestReturnCacheDataDontLoad = 3, 11 12 NSURLRequestReloadRevalidatingCacheData = 5, // Unimplemented 13 };
NSURLRequestUseProtocolCachePolicy 这个是默认的缓存策略,缓存不存在,就请求服务器,缓存存在,会根据response中的Cache-Control字段判断下一步操作,如: Cache-Control字段为must-revalidata, 则询问服务端该数据是否有更新,无更新的话直接返回给用户缓存数据,若已更新,则请求服务端。
NSURLRequestReloadIgnoringLocalCacheData 这个策略是不管有没有本地缓存,都请求服务器。
NSURLRequestReloadIgnoringLocalAndRemoteCacheData 这个策略会忽略本地缓存和中间代理 直接访问源server
NSURLRequestReturnCacheDataElseLoad 这个策略指,有缓存就是用,不管其有效性,即Cache-Control字段 ,没有就访问源server
NSURLRequestReturnCacheDataDontLoad 这个策略只加载本地数据,不做其他操作,适用于没有网路的情况
NSURLRequestReloadRevalidatingCacheData 这个策略标示缓存数据必须得到服务器确认才能使用,未实现。
4. 标记④
在HTTP连接中,一般都是一个请求对应一个连接,每次简历tcp连接是需要一定时间的。管线化,允许一次发送一组请求而不必等到相应。但由于目前并不是所有的服务器都支持这项功能,因此这个属性默认是不开启的。管线化使用同一tcp连接完成任务,因此能够大大提交请求的时间。但是响应要和请求的顺序 保持一致才行。使用场景也有,比如说首页要发送很多请求,可以考虑这种技术。但前提是建立连接成功后才可以使用。
5. 标记⑤
网络服务类型 是一个枚举值
typedef NS_ENUM(NSUInteger, NSURLRequestNetworkServiceType) { NSURLNetworkServiceTypeDefault = 0, // Standard internet traffic NSURLNetworkServiceTypeVoIP = 1, // Voice over IP control traffic NSURLNetworkServiceTypeVideo = 2, // Video traffic NSURLNetworkServiceTypeBackground = 3, // Background traffic NSURLNetworkServiceTypeVoice = 4 // Voice data };
可以通过这个值来指定当前的网络类型,系统会跟据制定的网络类型对很多方面进行优化,这个就设计到很细微的编程技巧了,可作为一个优化的点备用。
6. 标记⑥
设置请求超时时间,以秒为单位,默认为60秒。
7. 标记⑦
8. 标记⑧
9. 标记⑨
10. 标记⑩
这两个方法Authorization 这个词有关,上边的那个方法是根据用户名和密码 生成一个 Authorization 和值,拼接到请求头中规则是这样的
Authorization: Basic YWRtaW46YWRtaW4= 其中Basic表示基础认证,当然还有其他认证,如果感兴趣,可以看看本文开始提出的那本书。后边的YWRtaW46YWRtaW4= 是根据username:password 拼接后然后在经过Base64编码后的结果。
如果header中有 Authorization这个字段,那么服务器会验证用户名和密码,如果不正确的话会返回401错误。
11. 标记⑪
12. 标记⑫
13. 标记⑬
ps 这个要介绍一下 ,当我们需要一些东西或控件或对象需要自定义的时候,我们可以把我们的要求封装到一个block中。通过定制这个block达到自由定制的目的。
举个简单的例子,加入我们需要创建一个view,这个view跟两个参数相关,title,subtitle。 我们就可以使用上边的这个思想,返回一个返回值为view的block,这样通过参数,我们可以自由定制各种各样的view。
这样的场景还是很多,我现在也还没想好具体怎么用,只是突然有了这样的想法,大家如果有好的想法,可以留言。
14. 标记⑭
下边这三个是核心方法了,用来创建NSMutableURLRequest 这个对象,这个对象的创建又与上边①--⑬的设置息息相关
参数就不必介绍了,大家应该都懂,根据这个方法的注释,当method为GET/HEAD/DELETE 时,参数会被拼接到URL中,其他情况则会当做requset的body处理。
这个方法支持上传数据,值得注意的是之所以能够把本地磁盘或者内存中的数据发送到服务器,是因为NSURLRequest 有两个属性 :
NSData *HTTPBody;
NSInputStream *HTTPBodyStream;
这个方法可以把一个请求中的body数据保存到一个文件中,然后返回一个HTTPBodyStream为nil的请求,按照注释说的,NSURLSessionTask在使用流传数据时。如果没拼接Content-Length 会有问题。然后可以把这文件上传或者把它转为二进制文件上传。
=================================== 分割线 =====================================
我们已经知道了很多了,这篇真的很长,但耐心看完,一定会受益匪浅,知道了每个属性或者方法的作用,那么它的实现也基本上有点思路了。
这个是需要监听的属性,但看这些属性而言,要想实现当属性变化时,就调用监听方法,就需要我们手动实现监听方法。这也就说明,如果在平时开发中想要监听一个对象中某个自定义的属性时,只需要手动实现监听方法就行了。看看是怎么做的:
1 // Workarounds for crashing behavior using Key-Value Observing with XCTest 2 // See https://github.com/AFNetworking/AFNetworking/issues/2523 3 /** 4 * 下边的这几个setter方法,主要目的是触发kvo监听 5 */ 6 - (void)setAllowsCellularAccess:(BOOL)allowsCellularAccess { 7 [self willChangeValueForKey:NSStringFromSelector(@selector(allowsCellularAccess))]; 8 _allowsCellularAccess = allowsCellularAccess; 9 [self didChangeValueForKey:NSStringFromSelector(@selector(allowsCellularAccess))]; 10 } 11 12 - (void)setCachePolicy:(NSURLRequestCachePolicy)cachePolicy { 13 [self willChangeValueForKey:NSStringFromSelector(@selector(cachePolicy))]; 14 _cachePolicy = cachePolicy; 15 [self didChangeValueForKey:NSStringFromSelector(@selector(cachePolicy))]; 16 } 17 18 - (void)setHTTPShouldHandleCookies:(BOOL)HTTPShouldHandleCookies { 19 [self willChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldHandleCookies))]; 20 _HTTPShouldHandleCookies = HTTPShouldHandleCookies; 21 [self didChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldHandleCookies))]; 22 } 23 24 - (void)setHTTPShouldUsePipelining:(BOOL)HTTPShouldUsePipelining { 25 [self willChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldUsePipelining))]; 26 _HTTPShouldUsePipelining = HTTPShouldUsePipelining; 27 [self didChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldUsePipelining))]; 28 } 29 30 - (void)setNetworkServiceType:(NSURLRequestNetworkServiceType)networkServiceType { 31 [self willChangeValueForKey:NSStringFromSelector(@selector(networkServiceType))]; 32 _networkServiceType = networkServiceType; 33 [self didChangeValueForKey:NSStringFromSelector(@selector(networkServiceType))]; 34 } 35 36 - (void)setTimeoutInterval:(NSTimeInterval)timeoutInterval { 37 [self willChangeValueForKey:NSStringFromSelector(@selector(timeoutInterval))]; 38 _timeoutInterval = timeoutInterval; 39 [self didChangeValueForKey:NSStringFromSelector(@selector(timeoutInterval))]; 40 }
可以看出只要在setter方法中加入两行代码就行了。
1 - (instancetype)init { 2 self = [super init]; 3 if (!self) { 4 return nil; 5 } 6 7 self.stringEncoding = NSUTF8StringEncoding; 8 9 self.mutableHTTPRequestHeaders = [NSMutableDictionary dictionary]; 10 11 // Accept-Language HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4 12 /** 13 * 传递可接受的语言,q代表对语言的喜好程度,默认是取出前5个的数据,不足5个,取实际的个数 14 */ 15 NSMutableArray *acceptLanguagesComponents = [NSMutableArray array]; 16 [[NSLocale preferredLanguages] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) { 17 float q = 1.0f - (idx * 0.1f); 18 [acceptLanguagesComponents addObject:[NSString stringWithFormat:@"%@;q=%0.1g", obj, q]]; 19 *stop = q <= 0.5f; 20 }]; 21 22 // 设置请求头 23 [self setValue:[acceptLanguagesComponents componentsJoinedByString:@", "] forHTTPHeaderField:@"Accept-Language"]; 24 25 // 获取信息 26 NSString *userAgent = nil; 27 #pragma clang diagnostic push 28 #pragma clang diagnostic ignored "-Wgnu" 29 #if TARGET_OS_IOS 30 // User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43 31 userAgent = [NSString stringWithFormat:@"%@/%@ (%@; iOS %@; Scale/%0.2f)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[UIDevice currentDevice] model], [[UIDevice currentDevice] systemVersion], [[UIScreen mainScreen] scale]]; 32 #elif TARGET_OS_WATCH 33 // User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43 34 userAgent = [NSString stringWithFormat:@"%@/%@ (%@; watchOS %@; Scale/%0.2f)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[WKInterfaceDevice currentDevice] model], [[WKInterfaceDevice currentDevice] systemVersion], [[WKInterfaceDevice currentDevice] screenScale]]; 35 #elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED) 36 userAgent = [NSString stringWithFormat:@"%@/%@ (Mac OS X %@)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[NSProcessInfo processInfo] operatingSystemVersionString]]; 37 #endif 38 #pragma clang diagnostic pop 39 if (userAgent) { 40 if (![userAgent canBeConvertedToEncoding:NSASCIIStringEncoding]) { 41 NSMutableString *mutableUserAgent = [userAgent mutableCopy]; 42 43 // 转换字符串的方法 http://nshipster.com/cfstringtransform/ 44 if (CFStringTransform((__bridge CFMutableStringRef)(mutableUserAgent), NULL, (__bridge CFStringRef)@"Any-Latin; Latin-ASCII; [:^ASCII:] Remove", false)) { 45 userAgent = mutableUserAgent; 46 } 47 } 48 [self setValue:userAgent forHTTPHeaderField:@"User-Agent"]; 49 } 50 51 52 // HTTP Method Definitions; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html 53 self.HTTPMethodsEncodingParametersInURI = [NSSet setWithObjects:@"GET", @"HEAD", @"DELETE", nil]; 54 55 // 设置监听 56 self.mutableObservedChangedKeyPaths = [NSMutableSet set]; 57 for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) { 58 if ([self respondsToSelector:NSSelectorFromString(keyPath)]) { 59 [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext]; 60 } 61 } 62 63 return self; 64 } 65 66 - (void)dealloc { 67 68 // 取消监听 69 for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) { 70 if ([self respondsToSelector:NSSelectorFromString(keyPath)]) { 71 [self removeObserver:self forKeyPath:keyPath context:AFHTTPRequestSerializerObserverContext]; 72 } 73 } 74 }
这个比较好理解,就是对请求头字典的操作。
这两个方法是标记⑩的实现部分,上边也说明了,拼接Authorization 只要按照一定的规则就可以了。
这个是对标记⑫ 和 标记⑬ 的实现部分,再次声明下,关于URI中查询字段 也就是 query部分,的转换是通过block来转换的。
1 - (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request 2 withParameters:(id)parameters 3 error:(NSError *__autoreleasing *)error 4 { 5 NSParameterAssert(request); 6 7 NSMutableURLRequest *mutableRequest = [request mutableCopy]; 8 9 // 设置请求头 10 [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) { 11 if (![request valueForHTTPHeaderField:field]) { 12 [mutableRequest setValue:value forHTTPHeaderField:field]; 13 } 14 }]; 15 16 // 设置查询字段 17 NSString *query = nil; 18 if (parameters) { 19 if (self.queryStringSerialization) { 20 NSError *serializationError; 21 query = self.queryStringSerialization(request, parameters, &serializationError); 22 23 if (serializationError) { 24 if (error) { 25 *error = serializationError; 26 } 27 28 return nil; 29 } 30 } else { 31 switch (self.queryStringSerializationStyle) { 32 case AFHTTPRequestQueryStringDefaultStyle: 33 query = AFQueryStringFromParameters(parameters); 34 break; 35 } 36 } 37 } 38 39 // 如果请求的method 为 GET/HEAD/DELETE 直接把查询拼接到URL中 40 if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) { 41 if (query && query.length > 0) { 42 mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]]; 43 } 44 } 45 46 // 其他的 要设置下边的内容,然后给请求的HTTPBody 赋值就可以了 47 else { 48 // #2864: an empty string is a valid x-www-form-urlencoded payload 49 if (!query) { 50 query = @""; 51 } 52 if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) { 53 [mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"]; 54 } 55 [mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]]; 56 } 57 58 return mutableRequest; 59 }
这个方法也不是很复杂,主要的作用就是根据参数对NSURLRequest 进行设置,设置包括
1. 请求头
2. query字段,如果是GET/HEAD/DELETE 直接拼接到URL中,其他情况拼接到HTTPBody中。
注意:这个方法不处理数据流,只处理参数类型的数据。
1 - (NSMutableURLRequest *)requestWithMethod:(NSString *)method 2 URLString:(NSString *)URLString 3 parameters:(id)parameters 4 error:(NSError *__autoreleasing *)error 5 { 6 NSParameterAssert(method); 7 NSParameterAssert(URLString); 8 9 NSURL *url = [NSURL URLWithString:URLString]; 10 11 NSParameterAssert(url); 12 13 NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url]; 14 mutableRequest.HTTPMethod = method; 15 16 // 设置mutableRequest的一些属性,这些属性就是AFHTTPRequestSerializerObservedKeyPaths() f返回的数组, 17 for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) { 18 if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) { 19 [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath]; 20 } 21 } 22 23 mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy]; 24 25 return mutableRequest; 26 }
这个是一个创建NSMutableURLRequest 的方法,也是对上边的标记⑭的实现部分,简单说一下创建的过程
1. 新建一个NSMutableURLRequest
2. HTTPMethod 赋值
3. 根据 mutableObservedChangedKeyPaths 设置请求的一些属性
4. 通过
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(id)parameters
error:(NSError *__autoreleasing *)error 方法过滤和设置请求
1 - (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method 2 URLString:(NSString *)URLString 3 parameters:(NSDictionary *)parameters 4 constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block 5 error:(NSError *__autoreleasing *)error 6 { 7 // method 不能为空 8 // method 不能是GET 和 HEAD 9 NSParameterAssert(method); 10 NSParameterAssert(![method isEqualToString:@"GET"] && ![method isEqualToString:@"HEAD"]); 11 12 // 通过私有方法获取NSMutableURLRequest 13 NSMutableURLRequest *mutableRequest = [self requestWithMethod:method URLString:URLString parameters:nil error:error]; 14 15 // 创建一个AFStreamingMultipartFormData实例,用来处理数据。 16 __block AFStreamingMultipartFormData *formData = [[AFStreamingMultipartFormData alloc] initWithURLRequest:mutableRequest stringEncoding:NSUTF8StringEncoding]; 17 18 if (parameters) { 19 20 // 遍历parameters后 把value转成NSData然后拼接到formData中 21 for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) { 22 NSData *data = nil; 23 if ([pair.value isKindOfClass:[NSData class]]) { 24 data = pair.value; 25 } else if ([pair.value isEqual:[NSNull null]]) { 26 data = [NSData data]; 27 } else { 28 data = [[pair.value description] dataUsingEncoding:self.stringEncoding]; 29 } 30 31 if (data) { 32 [formData appendPartWithFormData:data name:[pair.field description]]; 33 } 34 } 35 } 36 37 if (block) { 38 block(formData); 39 } 40 41 return [formData requestByFinalizingMultipartFormData]; 42 }
这个方法是专门处理上传数据的方法,这里就不允许使用GET / HEAD HTTPMethod了。而且会把参数拼到formdata中了。
1 - (NSMutableURLRequest *)requestWithMultipartFormRequest:(NSURLRequest *)request 2 writingStreamContentsToFile:(NSURL *)fileURL 3 completionHandler:(void (^)(NSError *error))handler 4 { 5 NSParameterAssert(request.HTTPBodyStream); 6 NSParameterAssert([fileURL isFileURL]); 7 8 // 加上上边的两个判断,下边的这些代码就是把文件写到另一个地方的典型使用方法了 9 NSInputStream *inputStream = request.HTTPBodyStream; 10 NSOutputStream *outputStream = [[NSOutputStream alloc] initWithURL:fileURL append:NO]; 11 __block NSError *error = nil; 12 13 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 14 [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; 15 [outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; 16 17 [inputStream open]; 18 [outputStream open]; 19 20 // 读取数据 21 while ([inputStream hasBytesAvailable] && [outputStream hasSpaceAvailable]) { 22 uint8_t buffer[1024]; 23 24 NSInteger bytesRead = [inputStream read:buffer maxLength:1024]; 25 if (inputStream.streamError || bytesRead < 0) { 26 error = inputStream.streamError; 27 break; 28 } 29 30 NSInteger bytesWritten = [outputStream write:buffer maxLength:(NSUInteger)bytesRead]; 31 if (outputStream.streamError || bytesWritten < 0) { 32 error = outputStream.streamError; 33 break; 34 } 35 36 if (bytesRead == 0 && bytesWritten == 0) { 37 break; 38 } 39 } 40 41 [outputStream close]; 42 [inputStream close]; 43 44 if (handler) { 45 dispatch_async(dispatch_get_main_queue(), ^{ 46 handler(error); 47 }); 48 } 49 }); 50 51 NSMutableURLRequest *mutableRequest = [request mutableCopy]; 52 mutableRequest.HTTPBodyStream = nil; 53 54 return mutableRequest; 55 }
这个方法可以说是一个关于使用NSInputStream和NSOutputSteam 的经典案例,用法可以记下来或那这个方法的代码做参考。
1 + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key { 2 if ([AFHTTPRequestSerializerObservedKeyPaths() containsObject:key]) { 3 return NO; 4 } 5 6 return [super automaticallyNotifiesObserversForKey:key]; 7 } 8 9 - (void)observeValueForKeyPath:(NSString *)keyPath 10 ofObject:(__unused id)object 11 change:(NSDictionary *)change 12 context:(void *)context 13 { 14 if (context == AFHTTPRequestSerializerObserverContext) { 15 if ([change[NSKeyValueChangeNewKey] isEqual:[NSNull null]]) { 16 [self.mutableObservedChangedKeyPaths removeObject:keyPath]; 17 } else { 18 [self.mutableObservedChangedKeyPaths addObject:keyPath]; 19 } 20 } 21 }
这两个方法是关于kvo的。 值得学习的地方是我们通过判断change[NSKeyValueChangeNewKey] 是不是等于[NSNull null] 来写出不同的结果。
1 + (BOOL)supportsSecureCoding { 2 return YES; 3 } 4 5 - (instancetype)initWithCoder:(NSCoder *)decoder { 6 self = [self init]; 7 if (!self) { 8 return nil; 9 } 10 11 self.mutableHTTPRequestHeaders = [[decoder decodeObjectOfClass:[NSDictionary class] forKey:NSStringFromSelector(@selector(mutableHTTPRequestHeaders))] mutableCopy]; 12 self.queryStringSerializationStyle = (AFHTTPRequestQueryStringSerializationStyle)[[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(queryStringSerializationStyle))] unsignedIntegerValue]; 13 14 return self; 15 } 16 17 - (void)encodeWithCoder:(NSCoder *)coder { 18 [coder encodeObject:self.mutableHTTPRequestHeaders forKey:NSStringFromSelector(@selector(mutableHTTPRequestHeaders))]; 19 [coder encodeInteger:self.queryStringSerializationStyle forKey:NSStringFromSelector(@selector(queryStringSerializationStyle))]; 20 }
1 #pragma mark - NSCopying 2 3 - (instancetype)copyWithZone:(NSZone *)zone { 4 AFHTTPRequestSerializer *serializer = [[[self class] allocWithZone:zone] init]; 5 serializer.mutableHTTPRequestHeaders = [self.mutableHTTPRequestHeaders mutableCopyWithZone:zone]; 6 serializer.queryStringSerializationStyle = self.queryStringSerializationStyle; 7 serializer.queryStringSerialization = self.queryStringSerialization; 8 9 return serializer; 10 }
============================== 分割线 ==============================
1 @implementation AFJSONRequestSerializer 2 3 + (instancetype)serializer { 4 return [self serializerWithWritingOptions:(NSJSONWritingOptions)0]; 5 } 6 7 + (instancetype)serializerWithWritingOptions:(NSJSONWritingOptions)writingOptions 8 { 9 AFJSONRequestSerializer *serializer = [[self alloc] init]; 10 serializer.writingOptions = writingOptions; 11 12 return serializer; 13 } 14 15 #pragma mark - AFURLRequestSerialization 16 17 - (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request 18 withParameters:(id)parameters 19 error:(NSError *__autoreleasing *)error 20 { 21 NSParameterAssert(request); 22 23 if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) { 24 return [super requestBySerializingRequest:request withParameters:parameters error:error]; 25 } 26 27 NSMutableURLRequest *mutableRequest = [request mutableCopy]; 28 29 [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) { 30 if (![request valueForHTTPHeaderField:field]) { 31 [mutableRequest setValue:value forHTTPHeaderField:field]; 32 } 33 }]; 34 35 if (parameters) { 36 if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) { 37 [mutableRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; 38 } 39 40 [mutableRequest setHTTPBody:[NSJSONSerialization dataWithJSONObject:parameters options:self.writingOptions error:error]]; 41 } 42 43 return mutableRequest; 44 } 45 46 #pragma mark - NSSecureCoding 47 48 - (instancetype)initWithCoder:(NSCoder *)decoder { 49 self = [super initWithCoder:decoder]; 50 if (!self) { 51 return nil; 52 } 53 54 self.writingOptions = [[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(writingOptions))] unsignedIntegerValue]; 55 56 return self; 57 } 58 59 - (void)encodeWithCoder:(NSCoder *)coder { 60 [super encodeWithCoder:coder]; 61 62 [coder encodeInteger:self.writingOptions forKey:NSStringFromSelector(@selector(writingOptions))]; 63 } 64 65 #pragma mark - NSCopying 66 67 - (instancetype)copyWithZone:(NSZone *)zone { 68 AFJSONRequestSerializer *serializer = [super copyWithZone:zone]; 69 serializer.writingOptions = self.writingOptions; 70 71 return serializer; 72 } 73 74 @end
AFJSONRequestSerializer 这个类呢,可以把参数 转为json进行上传,当服务器要求我们上传的数据格式是json的时候呢,就用上了
============================== 分割线 ============================
1 @implementation AFPropertyListRequestSerializer 2 3 + (instancetype)serializer { 4 return [self serializerWithFormat:NSPropertyListXMLFormat_v1_0 writeOptions:0]; 5 } 6 7 + (instancetype)serializerWithFormat:(NSPropertyListFormat)format 8 writeOptions:(NSPropertyListWriteOptions)writeOptions 9 { 10 AFPropertyListRequestSerializer *serializer = [[self alloc] init]; 11 serializer.format = format; 12 serializer.writeOptions = writeOptions; 13 14 return serializer; 15 } 16 17 #pragma mark - AFURLRequestSerializer 18 19 - (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request 20 withParameters:(id)parameters 21 error:(NSError *__autoreleasing *)error 22 { 23 NSParameterAssert(request); 24 25 if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) { 26 return [super requestBySerializingRequest:request withParameters:parameters error:error]; 27 } 28 29 NSMutableURLRequest *mutableRequest = [request mutableCopy]; 30 31 [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) { 32 if (![request valueForHTTPHeaderField:field]) { 33 [mutableRequest setValue:value forHTTPHeaderField:field]; 34 } 35 }]; 36 37 if (parameters) { 38 if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) { 39 [mutableRequest setValue:@"application/x-plist" forHTTPHeaderField:@"Content-Type"]; 40 } 41 42 [mutableRequest setHTTPBody:[NSPropertyListSerialization dataWithPropertyList:parameters format:self.format options:self.writeOptions error:error]]; 43 } 44 45 return mutableRequest; 46 } 47 48 #pragma mark - NSSecureCoding 49 50 - (instancetype)initWithCoder:(NSCoder *)decoder { 51 self = [super initWithCoder:decoder]; 52 if (!self) { 53 return nil; 54 } 55 56 self.format = (NSPropertyListFormat)[[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(format))] unsignedIntegerValue]; 57 self.writeOptions = [[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(writeOptions))] unsignedIntegerValue]; 58 59 return self; 60 } 61 62 - (void)encodeWithCoder:(NSCoder *)coder { 63 [super encodeWithCoder:coder]; 64 65 [coder encodeInteger:self.format forKey:NSStringFromSelector(@selector(format))]; 66 [coder encodeObject:@(self.writeOptions) forKey:NSStringFromSelector(@selector(writeOptions))]; 67 } 68 69 #pragma mark - NSCopying 70 71 - (instancetype)copyWithZone:(NSZone *)zone { 72 AFPropertyListRequestSerializer *serializer = [super copyWithZone:zone]; 73 serializer.format = self.format; 74 serializer.writeOptions = self.writeOptions; 75 76 return serializer; 77 } 78 79 @end
好了,关于这篇文章,就到这了,其中还是设计了很多的知识点,而且设计上也非常完美,只有反复阅读,才能领悟到精髓所在。