本文转载请注明出处 —— polobymulberry-博客园javascript
上一篇中说起到了Multipart Request的构建方法- [AFHTTPRequestSerializer multipartFormRequestWithMethod:URLString:parameters:constructingBodyWithBlock:error:],不过并无深刻研究,部分函数也只是简单地一笔带过。因此本篇文章今后入手,一方面把Multipart协议问题解决掉,另外一方面就此把AFURLRequestSerialization文件遗留问题解决了。html
除了AFURLRequestSerialization的分析,这一篇还会介绍AFURLResponseSerialization。java
前面咱们简单介绍了Multipart协议的结构,并举了个例子:git
--${bound} // 该bound表示pdf的文件名 Content-Disposition: form-data; name="Filename" HTTP.pdf
--${bound} // 该bound表示pdf的文件内容 Content-Disposition: form-data; name="file000"; filename="HTTP协议详解.pdf" Content-Type: application/octet-stream %PDF-1.5 file content %%EOF
--${bound} // 该bound表示字符串 Content-Disposition: form-data; name="Upload" Submit Query
--${bound}—// 表示body结束了
咱们此次换个思路来学习AFNetworking中处理multipart格式的代码。咱们先来解决作什么,再看源码中的怎么作。github
首先,无论咱们作什么,最终都是为了产生一个request。咱们都知道request是由三个部分组成的:①请求行(request-line) ②请求头(headers) ③请求体(request body)。下面我就这三个方面逐一攻破。json
这个没啥好说的,就是POST。数组
multipart说白了和普通request大部分都很相似,因此普通request请求头的构造方法它也受用。而普通request的请求头构造方式有两个地方:服务器
multipart除了使用普通协议请求头的构建方法。还会在- [AFStreamingMultipartFormData requestByFinalizingMultipartFormData]构建本身独有的请求头。app
能够看到上面红框中的代码就是用来构建上上面那张图的红框中的请求头。其中咱们注意到这两个变量:dom
@property (nonatomic, copy) NSString *boundary; // multipart协议中的分割符 @property (readwrite, nonatomic, strong) AFMultipartBodyStream *bodyStream; // 表明了消息体
既然已经提到了boundary,此处就把他就地解决吧。至于bodyStream后面介绍消息体时候详解。
boundary的构建方式
boundary是用来分割不一样数据内容的,其实就是上面举的那个例子中的${bound}。咱们注意到boundary须要处理如下几个状况:
此处AFNetworking自定义了个函数建立boundary字符串。
static NSString * AFCreateMultipartFormBoundary() { // 使用两个十六进制随机数拼接在Boundary后面来表示分隔符 return [NSString stringWithFormat:@"Boundary+%08X%08X", arc4random(), arc4random()]; }
static inline NSString * AFMultipartFormInitialBoundary(NSString *boundary) { return [NSString stringWithFormat:@"--%@%@", boundary, kAFMultipartFormCRLF]; }
static inline NSString * AFMultipartFormEncapsulationBoundary(NSString *boundary) { return [NSString stringWithFormat:@"%@--%@%@", kAFMultipartFormCRLF, boundary, kAFMultipartFormCRLF]; }
static inline NSString * AFMultipartFormFinalBoundary(NSString *boundary) { return [NSString stringWithFormat:@"%@--%@--%@", kAFMultipartFormCRLF, boundary, kAFMultipartFormCRLF]; }
讲boundary有什么用呢?除了设置Content-Type外,在设置Content-Length时使用的[self.bodyStream contentLength]中会使用到boundary的这些相关函数:
// AFMultipartBodyStream函数 // 计算上面那个bodyStream的总长度做为Content-Length - (unsigned long long)contentLength { unsigned long long length = 0; // 注意bodyStream是由多个AFHTTPBodyPart对象组成的,好比上面那个例子就是有三个对象组成 for (AFHTTPBodyPart *bodyPart in self.HTTPBodyParts) { length += [bodyPart contentLength]; } return length; } // AFHTTPBodyPart函数 // 计算上面每一个AFHTTPBodyPart对象的长度// 使用AFHTTPBodyPart中hasInitialBoundary和hasFinalBoundary属性表示开头bodyPart和结尾bodyPart
- (unsigned long long)contentLength { unsigned long long length = 0; // 须要拼接上分割符 NSData *encapsulationBoundaryData = [([self hasInitialBoundary] ? AFMultipartFormInitialBoundary(self.boundary) : AFMultipartFormEncapsulationBoundary(self.boundary)) dataUsingEncoding:self.stringEncoding]; length += [encapsulationBoundaryData length]; // 每一个AFHTTPBodyPart对象中还有Content-Disposition等header-使用stringForHeader获取 NSData *headersData = [[self stringForHeaders] dataUsingEncoding:self.stringEncoding]; length += [headersData length]; // 加上每一个AFHTTPBodyPart对象具体的数据(好比文件内容)长度 length += _bodyContentLength; // 若是是最后一个AFHTTPBodyPart,还须要加上“--分隔符--”的长度 NSData *closingBoundaryData = ([self hasFinalBoundary] ? [AFMultipartFormFinalBoundary(self.boundary) dataUsingEncoding:self.stringEncoding] : [NSData data]); length += [closingBoundaryData length]; return length; }
至于setInitialAndFinalBoundaries函数,其实就是为了后面设置Content-Length作下预处理,使用这里不赘述了。咱们把目光放在bodyStream的具体构建上。事实上对于bodyStream的构建就是对AFStreamingMultipartFormData对象的处理,好比函数- [AFHTTPRequestSerializer multipartFormRequestWithMethod:URLString:parameters:constructingBodyWithBlock:error:]的那个formData就是一个AFStreamingMultipartFormData对象,下面我简单示意下AFStreamingMultipartFormData的结构:
结合上图,咱们就能够大胆推测,AFStreamingMultipartFormData类中的appendPart*函数最终落脚点就是给bodyStream中HTTPBodyParts添加一个AFHTTPBodyPart对象(HTTPBodyParts数组中的元素)。
注意这些appendPart*函数的主要区别在于数据的来源:
(BOOL) - appendPartWithFileURL:name:error: | 根据文件位置构造数据源,使用文件类型名做为mimeType |
(BOOL) - appendPartWithFileURL:name:fileName:mimeType:error: | 根据文件位置构造数据源,须要提供mimeType |
(void) - appendPartWithInputStream:name:fileName:length:mimeType: | 直接使用NSInputStream做为数据源 |
(void) - appendPartWithFileData:name:fileName:mimeType: | 使用NSData做为数据源 |
(void) - appendPartWithFormData:name: | 使用NSData做为数据源,NSData并非一个文件,可能只是一个字符串 |
这些函数的实现步骤基本都是一致的,都是新建一个AFHTTPBodyPart对象bodyPart,而后给bodyPart设置各类参数,其中比较重要的参数是headers和body这两个。最后使用appendHTTPBodyPart:方法,将bodyPart添加到bodyStream的HTTPBodyParts上。
这些函数实现没什么难度,你们能够自行研究。提两个稍微要注意的地方:
咱们先来看看这个函数的注释:
/** 将原来request中的HTTPBodyStream内容异步写入到指定文件中,随后调用completionHandler处理。最后返回新的request。 @param request multipart形式的request,其中HTTPBodyStream属性不能为nil @param fileURL multipart request中的HTTPBodyStream内容写入的文件位置 @param handler 用于处理的block @discussion NSURLSessionTask中有一个bug,当HTTP body的内容是来自NSStream的时候,request没法发送Content-Length到服务器端,此问题在Amazon S3的Web服务中尤其显著。做为一个解决方案,该函数的request参数使用的是multipartFormRequestWithMethod:URLString:parameters:constructingBodyWithBlock:error:构建出的request,或者其余HTTPBodyStream属性不为空的request。接着将HTTPBodyStream的内容先写到指定的文件中,再返回一个原来那个request的拷贝,其中该拷贝的HTTPBodyStream属性值要置为空。至此,可使用AFURLSessionManager -uploadTaskWithRequest:fromFile:progress:completionHandler:函数构建一个上传任务,或者将文件内容转变为NSData类型,而且指定给新request的HTTPBody属性。 @see https://github.com/AFNetworking/AFNetworking/issues/1398 */ - (NSMutableURLRequest *)requestWithMultipartFormRequest:(NSURLRequest *)request writingStreamContentsToFile:(NSURL *)fileURL completionHandler:(nullable void (^)(NSError * _Nullable error))handler;
知道这个函数是作什么以后,那么它的实现就相对容易理解了:
- (NSMutableURLRequest *)requestWithMultipartFormRequest:(NSURLRequest *)request writingStreamContentsToFile:(NSURL *)fileURL completionHandler:(void (^)(NSError *error))handler { NSParameterAssert(request.HTTPBodyStream); // 原先request的HTTPBodyStream不能为空 NSParameterAssert([fileURL isFileURL]); // 文件路径要合法 NSInputStream *inputStream = request.HTTPBodyStream; // 使用outputStream将HTTPBodyStream的内容写入到路径为fileURL的文件中 NSOutputStream *outputStream = [[NSOutputStream alloc] initWithURL:fileURL append:NO]; __block NSError *error = nil; // 异步执行写入操做 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // 指定在当前RunLoop中(currentRunLoop)运行inputStreamm/outputStream,意味着在currentRunLoop中处理流操做 [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; [outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; // 打开 [inputStream open]; [outputStream open]; while ([inputStream hasBytesAvailable] && [outputStream hasSpaceAvailable]) { uint8_t buffer[1024]; // 每次从inputStream中读取最多1024bytes大小的数据,放在buffer中,给outputStream写入file NSInteger bytesRead = [inputStream read:buffer maxLength:1024]; // 出现streamError或者bytesRead小于0都表示读取出错 if (inputStream.streamError || bytesRead < 0) { error = inputStream.streamError; break; } // 将上面读取的buffer写入到outputStream中,即写入文件 NSInteger bytesWritten = [outputStream write:buffer maxLength:(NSUInteger)bytesRead]; // 出现streamError或者bytesWritten小于0都表示写入出错 if (outputStream.streamError || bytesWritten < 0) { error = outputStream.streamError; break; } // 表示读取写入完成 if (bytesRead == 0 && bytesWritten == 0) { break; } } [outputStream close]; [inputStream close]; // 回到主进程执行handler if (handler) { dispatch_async(dispatch_get_main_queue(), ^{ handler(error); }); } }); // 获取到新的request,并将新的request的HTTPBodyStream置为空 NSMutableURLRequest *mutableRequest = [request mutableCopy]; mutableRequest.HTTPBodyStream = nil; return mutableRequest; }
上面函数中稍微陌生一点的就是- [AFMultipartBodyStream read:maxLength:]和- [NSOutputStream write:maxLength:],因为后者只是简单地将前者读出的数据写到文件中,因此真正的难点仍是在- [AFMultipartBodyStream read:maxLength:]函数。
- [AFMultipartBodyStream read:maxLength:]函数深刻进去仍是不少问题要解决的。不过咱们先来看看其实现的方式:
- (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)length { // 输入流关闭状态,没法读取 if ([self streamStatus] == NSStreamStatusClosed) { return 0; } NSInteger totalNumberOfBytesRead = 0; #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wgnu" // 通常来讲都是直接读取length长度的数据,可是考虑到最后一次须要读出的数据长度(self.numberOfBytesInPacket)通常是小于length // 因此此处使用了MIN(length, self.numberOfBytesInPacket) while ((NSUInteger)totalNumberOfBytesRead < MIN(length, self.numberOfBytesInPacket)) { // 相似于咱们构建request的逆向过程,咱们对于HTTPBodyStream的读取也是分红一个一个AFHTTPBodyPart来的 // 若是当前AFHTTPBodyPart对象读取完成,那么就使用enumerator读取下一个AFHTTPBodyPart if (!self.currentHTTPBodyPart || ![self.currentHTTPBodyPart hasBytesAvailable]) { if (!(self.currentHTTPBodyPart = [self.HTTPBodyPartEnumerator nextObject])) { break; } } else { // 读取当前AFHTTPBodyPart对象 NSUInteger maxLength = MIN(length, self.numberOfBytesInPacket) - (NSUInteger)totalNumberOfBytesRead; // 使用的是AFHTTPBodyPart的read:maxLength:函数 NSInteger numberOfBytesRead = [self.currentHTTPBodyPart read:&buffer[totalNumberOfBytesRead] maxLength:maxLength]; // 读取出错 if (numberOfBytesRead == -1) { self.streamError = self.currentHTTPBodyPart.inputStream.streamError; break; } else { // totalNumberOfBytesRead表示目前已经读取的字节数,能够做为读取后的数据放置于buffer的起始位置,如buffer[totalNumberOfBytesRead] totalNumberOfBytesRead += numberOfBytesRead; if (self.delay > 0.0f) { [NSThread sleepForTimeInterval:self.delay]; } } } } #pragma clang diagnostic pop return totalNumberOfBytesRead; }
对于单个AFHTTPBodyPart的读取函数- [read:maxLength:]:
- (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)length { NSInteger totalNumberOfBytesRead = 0; // 使用分隔符将对应bodyPart数据封装起来 if (_phase == AFEncapsulationBoundaryPhase) { NSData *encapsulationBoundaryData = [([self hasInitialBoundary] ? AFMultipartFormInitialBoundary(self.boundary) : AFMultipartFormEncapsulationBoundary(self.boundary)) dataUsingEncoding:self.stringEncoding]; totalNumberOfBytesRead += [self readData:encapsulationBoundaryData intoBuffer:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)]; } // 若是读取到的是bodyPart对应的header部分,那么使用stringForHeaders获取到对应header,并读取到buffer中 if (_phase == AFHeaderPhase) { NSData *headersData = [[self stringForHeaders] dataUsingEncoding:self.stringEncoding]; totalNumberOfBytesRead += [self readData:headersData intoBuffer:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)]; } // 若是读取到的是bodyPart的内容主体,即inputStream,那么就直接使用inputStream写入数据到buffer中 if (_phase == AFBodyPhase) { NSInteger numberOfBytesRead = 0; // 使用系统自带的NSInputStream的read:maxLength:函数读取 numberOfBytesRead = [self.inputStream read:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)]; if (numberOfBytesRead == -1) { return -1; } else { totalNumberOfBytesRead += numberOfBytesRead; // 若是内容主体都读取完了,那么颇有可能下一次读取的就是下一个bodyPart的header // 因此此处要调用transitionToNextPhase,调整对应_phase if ([self.inputStream streamStatus] >= NSStreamStatusAtEnd) { [self transitionToNextPhase]; } } } // 若是是最后一个AFHTTPBodyPart对象,那么就须要添加在末尾”--分隔符--" if (_phase == AFFinalBoundaryPhase) { NSData *closingBoundaryData = ([self hasFinalBoundary] ? [AFMultipartFormFinalBoundary(self.boundary) dataUsingEncoding:self.stringEncoding] : [NSData data]); totalNumberOfBytesRead += [self readData:closingBoundaryData intoBuffer:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)]; } return totalNumberOfBytesRead; } // 上面那个函数中大量使用了read:intoBuffer:maxLength:函数 // 这里咱们将read:intoBuffer:maxLength:理解成一种将NSData类型的data转化为(uint8_t *)类型的buffer的手段,核心是使用了NSData的getBytes:range:函数 - (NSInteger)readData:(NSData *)data intoBuffer:(uint8_t *)buffer maxLength:(NSUInteger)length { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wgnu" // 求取range,须要考虑文件末尾比maxLength会小的状况 NSRange range = NSMakeRange((NSUInteger)_phaseReadOffset, MIN([data length] - ((NSUInteger)_phaseReadOffset), length)); // 核心:NSData *---->uint8_t* [data getBytes:buffer range:range]; #pragma clang diagnostic pop _phaseReadOffset += range.length; // 读取完成就更新_phase的状态 if (((NSUInteger)_phaseReadOffset) >= [data length]) { [self transitionToNextPhase]; } return (NSInteger)range.length; }
另外,具体的_phase状态转换,你们参考transitionToNextPhase函数,不是很难,此处就不赘述了。
这两个类都是继承自AFHTTPRequestSerializer,和父类不一样的是:
还记得咱们在说AFURLSessionManager的时候,在NSURLSessionTaskDelegate中的- URLSession:task:didCompeleteWithErrror这个代理方法中提到过:
responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];
responseObjectForResponse:函数就是为了将返回的data转化为用户所需的格式,好比若是你的responseSerializer是AFJSONResponseSerializer的对象,那么解析出来的data就是JSON格式。
咱们先来看看AFURLResponseSerialization这个文件中类的大概结构:
简单讲一下这个结构,好比说我如今想自定义一个ResponseSerializer,名叫AFCustomResponseSerializer,继承自AFHTTPResponseSerializer。
@interface AFCustomResponseSerializer : AFHTTPResponseSerializer
那么AFCustomResponseSerializer须要实现AFURLResponseSerialization协议的responseObjectForResponse:方法。此方法就是将data转化你定义的格式。可能你还须要实现你本身的serializer方法,并在init中定义本身的acceptableContentTypes。咱们接下来就先看AFHTTPResponseSerializer这个父类,而后逐个看看AFHTTPResponseSerializer这些个子类。
注意到AFHTTPResponseSerializer实现的responseObjectForResponse:函数,只是简单调用了validateResponse:这个函数,并且validateResponse:中并无对data作任何改变,也就是说父类AFHTTPResponseSerializer中的responseObjectForResponse:返回的就是最原始的data。对于data的处理,就交给了各个子类具体实现。
这里主要说起的就是validResponse:这个函数,挺重要的,主要是判断返回的response是否可用。有用的话,才会作下一步操做。
- (BOOL)validateResponse:(NSHTTPURLResponse *)response data:(NSData *)data error:(NSError * __autoreleasing *)error { // 初始response是可用的,不过下面还须要要过三关斩六将 BOOL responseIsValid = YES; NSError *validationError = nil; // 简单的为空判断和类型判断,注意若是response为空或类型不对,反而responseValid为YES if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) { // 若是response对应的mimeType不被这个ResponseSerializer所接受,那么就认为Response不可用 if (self.acceptableContentTypes && ![self.acceptableContentTypes containsObject:[response MIMEType]]) { // 会返回unacceptable content-type的信息,并将错误信息记录在了mutableUserInfo中 if ([data length] > 0 && [response URL]) { NSMutableDictionary *mutableUserInfo = [@{ NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Request failed: unacceptable content-type: %@", @"AFNetworking", nil), [response MIMEType]], NSURLErrorFailingURLErrorKey:[response URL], AFNetworkingOperationFailingURLResponseErrorKey: response, } mutableCopy]; if (data) { mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data; } // 利用mutableUserInfo构建一个NSError对象 validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:mutableUserInfo], validationError); } responseIsValid = NO; } // 判断返回的statusCode是否被容许 if (self.acceptableStatusCodes && ![self.acceptableStatusCodes containsIndex:(NSUInteger)response.statusCode] && [response URL]) { NSMutableDictionary *mutableUserInfo = [@{ NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Request failed: %@ (%ld)", @"AFNetworking", nil), [NSHTTPURLResponse localizedStringForStatusCode:response.statusCode], (long)response.statusCode], NSURLErrorFailingURLErrorKey:[response URL], AFNetworkingOperationFailingURLResponseErrorKey: response, } mutableCopy]; if (data) { mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data; } validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorBadServerResponse userInfo:mutableUserInfo], validationError); responseIsValid = NO; } } // 将error设置为validationError if (error && !responseIsValid) { *error = validationError; } return responseIsValid; }
AFJSONResponseSerializer接受的content-type有@"application/json", @"text/json", @"text/javascript"。
再让咱们看看responseObjectForResponse:data:error:的实现:
- (id)responseObjectForResponse:(NSURLResponse *)response data:(NSData *)data error:(NSError *__autoreleasing *)error { // 判断当前response是否有效 if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) { // 还记得validateResponse:中若是content-type不知足,那么产生的validationError就是Domain为AFURLResponseSerializationErrorDomain,code为NSURLErrorCannotDecodeContentData if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) { // 由于不支持这个content-type,因此不用解析了,直接返回nil return nil; } } id responseObject = nil; NSError *serializationError = nil; // 对于'head :ok',Rails返回的是一个空格 (这是Safari上的一个bug),而且这样的JSON格式不会被NSJSONSerialization解析。 // See https://github.com/rails/rails/issues/1742 // 若是是单个空格,就不解析 BOOL isSpace = [data isEqualToData:[NSData dataWithBytes:" " length:1]]; if (data.length > 0 && !isSpace) { // 使用系统自带的NSJSONSerialization来解析NSData数据 responseObject = [NSJSONSerialization JSONObjectWithData:data options:self.readingOptions error:&serializationError]; } else { return nil; } // 若是须要移除JSON数据中对应value为空(nil或NSNull)的key,那么就使用AFJSONObjectByRemovingKeysWithNullValues函数 // AFJSONObjectByRemovingKeysWithNullValues经过递归的方法,把JSON中NSDictionary的数据(不包括NSArray)中的对应value为空的key移除 if (self.removesKeysWithNullValues && responseObject) { responseObject = AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions); } if (error) { // 若是serializationError不为空,那么最终的error其实就是serializationError *error = AFErrorWithUnderlyingError(serializationError, *error); } return responseObject; }
AFXMLParserResponseSerializer接受的content-type有@"application/xml", @"text/xml"。
再让咱们看看responseObjectForResponse:data:error:的实现:
- (id)responseObjectForResponse:(NSHTTPURLResponse *)response data:(NSData *)data error:(NSError *__autoreleasing *)error { if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) { // 若是不支持该content-type if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) { return nil; } } // 使用NSXMLParser解析NSData数据 return [[NSXMLParser alloc] initWithData:data]; }
至于下面的AFXMLDocumentResponseSerializer,那是MAC上所用到的,这里不赘述了。
AFPropertyListResponseSerializer接受的content-type有@"application/x-plist"。
再让咱们看看responseObjectForResponse:data:error:的实现:
- (id)responseObjectForResponse:(NSURLResponse *)response data:(NSData *)data error:(NSError *__autoreleasing *)error { if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) { // 若是不支持该content-type,返回nil if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) { return nil; } } id responseObject; NSError *serializationError = nil; // 使用NSPropertyListSerialization来解析NSData数据 if (data) { responseObject = [NSPropertyListSerialization propertyListWithData:data options:self.readOptions format:NULL error:&serializationError]; } // 若是serializationError不为空,那么最终的error其实就是serializationError if (error) { *error = AFErrorWithUnderlyingError(serializationError, *error); } return responseObject; }
AFImageResponseSerializer接受的content-type有@"image/tiff", @"image/jpeg", @"image/gif", @"image/png", @"image/ico", @"image/x-icon", @"image/bmp", @"image/x-bmp", @"image/x-xbitmap", @"image/x-win-bitmap"。
再让咱们看看responseObjectForResponse:data:error:的实现:
- (id)responseObjectForResponse:(NSURLResponse *)response data:(NSData *)data error:(NSError *__autoreleasing *)error { if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) { // 若是不支持该content-type,返回nil if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) { return nil; } } #if TARGET_OS_IOS || TARGET_OS_TV || TARGET_OS_WATCH // iOS和TV平台默认automaticallyInflatesResponseImage为YES // 下面的NSData转图片的方法,以前SDWebImage分析过,就不赘述了 // 感兴趣的话能够查看【原】SDWebImage源码阅读(四) if (self.automaticallyInflatesResponseImage) { return AFInflatedImageFromResponseWithDataAtScale((NSHTTPURLResponse *)response, data, self.imageScale); } else { return AFImageWithDataAtScale(data, self.imageScale); } #else // 只关心iOS // Ensure that the image is set to it's correct pixel width and height NSBitmapImageRep *bitimage = [[NSBitmapImageRep alloc] initWithData:data]; NSImage *image = [[NSImage alloc] initWithSize:NSMakeSize([bitimage pixelsWide], [bitimage pixelsHigh])]; [image addRepresentation:bitimage]; return image; #endif return nil; }
该类里面有一个成员属性为
@property (readwrite, nonatomic, copy) NSArray *responseSerializers;
可见AFCompoundResponseSerializer是表示一组Serializer的集合,不信,你能够看它的responseObjectForResponse:data:error:函数实现:
- (id)responseObjectForResponse:(NSURLResponse *)response data:(NSData *)data error:(NSError *__autoreleasing *)error { // 可能确实不能肯定返回的responsed的content-type,此时可使用AFCompoundResponseSerializer
// 总会找到合适的Serializer
for (id <AFURLResponseSerialization> serializer in self.responseSerializers) { if (![serializer isKindOfClass:[AFHTTPResponseSerializer class]]) { continue; } NSError *serializerError = nil; id responseObject = [serializer responseObjectForResponse:response data:data error:&serializerError]; // 终于遍历到合适的Serializer if (responseObject) { if (error) { *error = AFErrorWithUnderlyingError(serializerError, *error); } return responseObject; } } return [super responseObjectForResponse:response data:data error:error]; }