[iOS] AFNetworking源码学习—Serialize
还记得在AFURLSessionManager里面如何将reponse解析为object的么?
responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];
这里就来看看库里面的AFURLResponseSerialization和AFURLRequestSerialization是怎么实现的吧~
AFURLRequestSerialization
刚开始看到有这么个文件,我以为这个是一个class,其实并不是,它定义了一个协议,而文件内的AFHTTPRequestSerializer实现了这个协议:
@protocol AFURLRequestSerialization <NSObject, NSSecureCoding, NSCopying>
/**
Returns a request with the specified parameters encoded into a copy of the original request.
@param request The original request.
@param parameters The parameters to be encoded.
@param error The error that occurred while attempting to encode the request parameters.
@return A serialized request.
*/
- (nullable NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(nullable id)parameters
error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;
@end
所以下面先来看下AFHTTPRequestSerializer吧~
AFHTTPRequestSerializer
首先来看下它的init方法:
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
self.stringEncoding = NSUTF8StringEncoding;
self.mutableHTTPRequestHeaders = [NSMutableDictionary dictionary];
// 创建并行queue用于mutableHTTPRequestHeaders的读取和修改
self.requestHeaderModificationQueue = dispatch_queue_create("requestHeaderModificationQueue", DISPATCH_QUEUE_CONCURRENT);
// Accept-Language HTTP Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
// 生成Accept-Language的键值对
// 格式类似于Accept-Language: da, en-gb;q=0.8, en;q=0.7
// 遍历当前用户语言,然后设置优先级
NSMutableArray *acceptLanguagesComponents = [NSMutableArray array];
[[NSLocale preferredLanguages] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
float q = 1.0f - (idx * 0.1f);
[acceptLanguagesComponents addObject:[NSString stringWithFormat:@"%@;q=%0.1g", obj, q]];
*stop = q <= 0.5f;
}];
[self setValue:[acceptLanguagesComponents componentsJoinedByString:@", "] forHTTPHeaderField:@"Accept-Language"];
// 设置userAgent,用户设备的键值对
// 类似User-Agent: CERN-LineMode/2.15 libwww/2.17b3
NSString *userAgent = nil;
#if TARGET_OS_IOS
// User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
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]];
#elif TARGET_OS_WATCH
……
if (userAgent) {
if (![userAgent canBeConvertedToEncoding:NSASCIIStringEncoding]) {
NSMutableString *mutableUserAgent = [userAgent mutableCopy];
if (CFStringTransform((__bridge CFMutableStringRef)(mutableUserAgent), NULL, (__bridge CFStringRef)@"Any-Latin; Latin-ASCII; [:^ASCII:] Remove", false)) {
userAgent = mutableUserAgent;
}
}
[self setValue:userAgent forHTTPHeaderField:@"User-Agent"];
}
// HTTP Method Definitions; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec9.html
// 这几种method参数是append到url的,post方法需要放到body里面
self.HTTPMethodsEncodingParametersInURI = [NSSet setWithObjects:@"GET", @"HEAD", @"DELETE", nil];
self.mutableObservedChangedKeyPaths = [NSMutableSet set];
// 增加监听AFHTTPRequestSerializerObservedKeyPaths里面的属性
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
[self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
}
}
return self;
}
也就是这一段初始化了编码方式stringEncoding、记录header的字典mutableHTTPRequestHeaders、用于保证读写安全的并行queue requestHeaderModificationQueue、用于保存哪些属性变化了的set mutableObservedChangedKeyPaths。
并且将Accept-Language
、User-Agent
设置到header里面,通过[self setValue:userAgent forHTTPHeaderField:@"User-Agent"]
这种方式确保了读写安全,因为在setValue里面通过并行队列修改了mutableHTTPRequestHeaders:
- (void)setValue:(NSString *)value
forHTTPHeaderField:(NSString *)field
{
dispatch_barrier_async(self.requestHeaderModificationQueue, ^{
[self.mutableHTTPRequestHeaders setValue:value forKey:field];
});
}
这里是一个很常见的利用并行queue保证读写安全的方式:
- 读使用sync读
- 写使用栅栏任务异步dispatch_barrier_async写
- 监听属性
在init的时候循环遍历了AFHTTPRequestSerializerObservedKeyPaths,并且add了KVO监听。
static NSArray * AFHTTPRequestSerializerObservedKeyPaths() {
static NSArray *_AFHTTPRequestSerializerObservedKeyPaths = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_AFHTTPRequestSerializerObservedKeyPaths = @[NSStringFromSelector(@selector(allowsCellularAccess)), NSStringFromSelector(@selector(cachePolicy)), NSStringFromSelector(@selector(HTTPShouldHandleCookies)), NSStringFromSelector(@selector(HTTPShouldUsePipelining)), NSStringFromSelector(@selector(networkServiceType)), NSStringFromSelector(@selector(timeoutInterval))];
});
return _AFHTTPRequestSerializerObservedKeyPaths;
}
监听的就是AFHTTPRequestSerializer的六个属性。(allowsCellularAccess
、cachePolicy
、HTTPShouldHandleCookies
、HTTPShouldUsePipelining
、networkServiceType
、timeoutInterval
)
比较神奇并且我一直木有搞明白的是,作者通过关闭自动KVO解决了一个bug(https://github.com/AFNetworking/AFNetworking/issues/2523),这个bug听起来就是两个实例都走了init以后就会crash,但我实在木有明白为什么QAQ
关闭自动KVO是通过:
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
if ([AFHTTPRequestSerializerObservedKeyPaths() containsObject:key]) {
return NO;
}
return [super automaticallyNotifiesObserversForKey:key];
}
在关闭以后需要添加手动KVO代码,否则就监听不到属性变化啦:
- (void)setAllowsCellularAccess:(BOOL)allowsCellularAccess {
[self willChangeValueForKey:NSStringFromSelector(@selector(allowsCellularAccess))];
_allowsCellularAccess = allowsCellularAccess;
[self didChangeValueForKey:NSStringFromSelector(@selector(allowsCellularAccess))];
}
其实自动KVO也是通过在setter前后分别调用willChangeValueForKey和didChangeValueForKey实现的,所以这里感觉作者就是实现了自动的那种,可能是自动KVO会做一些神奇的事情吧。。
那么监听到属性变化以后,AFHTTPRequestSerializer做了什么呢?
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(__unused id)object
change:(NSDictionary *)change
context:(void *)context
{
if (context == AFHTTPRequestSerializerObserverContext) {
if ([change[NSKeyValueChangeNewKey] isEqual:[NSNull null]]) {
[self.mutableObservedChangedKeyPaths removeObject:keyPath];
} else {
[self.mutableObservedChangedKeyPaths addObject:keyPath];
}
}
}
如果改变后的值为nil,就将这个属性从self.mutableObservedChangedKeyPaths里面移除,如果不为nil,则加入到self.mutableObservedChangedKeyPaths队列中。
所以其实self.mutableObservedChangedKeyPaths就是用于存储被修改过的属性,并且修改后的值不为空。
- 如何生成request
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
NSParameterAssert(request);
// 生成一个mutableRequest
NSMutableURLRequest *mutableRequest = [request mutableCopy];
// 遍历self.mutableHTTPRequestHeaders,如果传入的request里面没有相关的key,就加进去
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
if (![request valueForHTTPHeaderField:field]) {
[mutableRequest setValue:value forHTTPHeaderField:field];
}
}];
// 如果有parameters就生成query string
NSString *query = nil;
if (parameters) {
// 如果设置了queryStringSerialization block,就用用户定义的block对parameter进行字符串化
if (self.queryStringSerialization) {
NSError *serializationError;
query = self.queryStringSerialization(request, parameters, &serializationError);
if (serializationError) {
if (error) {
*error = serializationError;
}
return nil;
}
} else {
// 如果没有设置queryStringSerialization block,就通过queryStringSerializationStyle来判断要怎么字符串化
switch (self.queryStringSerializationStyle) {
case AFHTTPRequestQueryStringDefaultStyle:
query = AFQueryStringFromParameters(parameters);
break;
}
}
}
// 如果是get,head,delete,就可以将query string直接拼到url后面
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
if (query && query.length > 0) {
mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
}
} else {
// #2864: an empty string is a valid x-www-form-urlencoded payload
if (!query) {
query = @"";
}
// 如果是post或者其他的,就得把参数放进http body啦
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
[mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
}
[mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
}
return mutableRequest;
}
关于几种method网络请求方法可以参考:https://blog.csdn.net/u010244522/article/details/79385502
所以这里先把self.mutableHTTPRequestHeaders里面的key和value加入到request里面,如果request里面已经有了就不加进去。
然后得到query string。先看有没有设置self.queryStringSerialization(AFQueryStringSerializationBlock):
typedef NSString * (^AFQueryStringSerializationBlock)(NSURLRequest *request, id parameters, NSError *__autoreleasing *error);
如果没有就检查self.queryStringSerializationStyle,这里只写了default一种,default是怎么生成query string的呢?
NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
NSMutableArray *mutablePairs = [NSMutableArray array];
for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
[mutablePairs addObject:[pair URLEncodedStringValue]];
}
return [mutablePairs componentsJoinedByString:@"&"];
}
NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) {
return AFQueryStringPairsFromKeyAndValue(nil, dictionary);
}
NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) {
NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)];
if ([value isKindOfClass:[NSDictionary class]]) {
NSDictionary *dictionary = value;
// Sort dictionary keys to ensure consistent ordering in query string, which is important when deserializing potentially ambiguous sequences, such as an array of dictionaries
for (id nestedKey in [dictionary.allKeys sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
id nestedValue = dictionary[nestedKey];
if (nestedValue) {
// dict会拆成 上层key[key]=value,如果没有上层key就是key=value
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)];
}
}
} else if ([value isKindOfClass:[NSArray class]]) {
NSArray *array = value;
for (id nestedValue in array) {
// array会拆成 上层key[]=value
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[]", key], nestedValue)];
}
} else if ([value isKindOfClass:[NSSet class]]) {
NSSet *set = value;
for (id obj in [set sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
// set会拆成 上层key=value
[mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue(key, obj)];
}
} else {
[mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]];
}
return mutableQueryStringComponents;
}
AFQueryStringPairsFromKeyAndValue是生成query的主要函数,它会递归调用自己来打开嵌套,对set/dict/array进行平铺,然后生成AFQueryStringPair。
Pair就很单纯啦,直接改成field=value字符串化一下:
@implementation AFQueryStringPair
- (instancetype)initWithField:(id)field value:(id)value {
self = [super init];
if (!self) {
return nil;
}
self.field = field;
self.value = value;
return self;
}
- (NSString *)URLEncodedStringValue {
if (!self.value || [self.value isEqual:[NSNull null]]) {
return AFPercentEscapedStringFromString([self.field description]);
} else {
return [NSString stringWithFormat:@"%@=%@", AFPercentEscapedStringFromString([self.field description]), AFPercentEscapedStringFromString([self.value description])];
}
}
@end
我们来测试一下~
parameter: @{@"user":@"name", @"password":@"111"}
query: @"password=111&user=name"
parameter: @[@"user", @"name", @"password", @"111"]
query: %28null%29%5B%5D=user&%28null%29%5B%5D=name&%28null%29%5B%5D=password&%28null%29%5B%5D=111
parameter: [NSSet setWithObjects:@"user", @"name", @"password", @"111", nil]
query: "=111&=name&=password&=user"
- requestWithMethod和requestBySerializingRequest有啥区别类?
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
NSParameterAssert(method);
NSParameterAssert(URLString);
NSURL *url = [NSURL URLWithString:URLString];
NSParameterAssert(url);
NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
mutableRequest.HTTPMethod = method;
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
}
}
mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];
return mutableRequest;
}
requestWithMethod这个里面就用到了我们之前存储了哪些key被改变过的array mutableObservedChangedKeyPaths啦。
它会遍历被改过的key,然后给mutableRequest设置成和serializer同样的值。最后仍旧是调用了requestBySerializingRequest生成request。
所以来测试一下两者区别吼:
// 打断点看mutableRequest的allowsCellularAccess是YES
AFHTTPRequestSerializer *serializer = [AFHTTPRequestSerializer serializer];
serializer.allowsCellularAccess = NO;
NSURLRequest *request = [serializer requestBySerializingRequest:[NSURLRequest requestWithURL:url] withParameters:@[@"user", @"name", @"password", @"111"] error:nil];
----
// 打断点看mutableRequest的allowsCellularAccess是NO
AFHTTPRequestSerializer *serializer = [AFHTTPRequestSerializer serializer];
serializer.allowsCellularAccess = NO;
NSURLRequest *request = [serializer requestWithMethod:@"GET" URLString:@"https://downloads.slack-edge.com/mac_releases_beta/Slack-4.1.2-beta1-macOS.dmg" parameters:@[@"user", @"name", @"password", @"111"] error:nil];
也就是requestWithMethod创建出的request的六个属性会遗传serializer被改过的设置,而requestBySerializingRequest不会。
AFPropertyListRequestSerializer和AFJSONRequestSerializer
这两个都是AFHTTPRequestSerializer的子类,主要是重写了requestBySerializingRequest方法,用json的举例吧,plist的大同小异:
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
withParameters:(id)parameters
error:(NSError *__autoreleasing *)error
{
NSParameterAssert(request);
// 如果是get,head,delete就仍旧用AFHTTPRequestSerializer的方式,也就是在url后面追加string
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
return [super requestBySerializingRequest:request withParameters:parameters error:error];
}
// 如果是其他method,就先遍历自己的mutableHTTPRequestHeaders,设置给mutableRequest
NSMutableURLRequest *mutableRequest = [request mutableCopy];
[self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
if (![request valueForHTTPHeaderField:field]) {
[mutableRequest setValue:value forHTTPHeaderField:field];
}
}];
// 如果有parameters就把parameter转为jsonData,并且设置给mutableRequest的body
if (parameters) {
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
[mutableRequest setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
}
if (![NSJSONSerialization isValidJSONObject:parameters]) {
if (error) {
NSDictionary *userInfo = @{NSLocalizedFailureReasonErrorKey: NSLocalizedStringFromTable(@"The `parameters` argument is not valid JSON.", @"AFNetworking", nil)};
*error = [[NSError alloc] initWithDomain:AFURLRequestSerializationErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:userInfo];
}
return nil;
}
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:parameters options:self.writingOptions error:error];
if (!jsonData) {
return nil;
}
[mutableRequest setHTTPBody:jsonData];
}
return mutableRequest;
}
plist其实差不多,除了:
if (parameters) {
if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
[mutableRequest setValue:@"application/x-plist" forHTTPHeaderField:@"Content-Type"];
}
NSData *plistData = [NSPropertyListSerialization dataWithPropertyList:parameters format:self.format options:self.writeOptions error:error];
if (!plistData) {
return nil;
}
[mutableRequest setHTTPBody:plistData];
}
也就是其实这两种serializer只对非get/head/delete的方法有效,并且会将@"Content-Type"设为相应的格式,将body设置为该格式的数据。(纯字符串是x-www-form-urlencoded,可参考HTTP的实现)
Mutipart
POST有两种方式:
- 第一种直接把数据放在body中,用contentType来区分类型是text还是json或者是别的什么数据。这个最简单,不做赘述。
- 第二种是表单的形式,通过boundaries来区分放置的是那些数据,很像一个字典,用K,V放置对象,即mutipart。
举个Mutipart请求的例子:
OST /upload_file/UploadFile HTTP/1.1
Accept: text/plain, */*
Accept-Language: zh-cn
Host: 192.168.29.65:80
Content-Type:multipart/form-data;boundary=BoundaryAaB03x
User-Agent: Mozilla/4.0 (compatible; OpenOffice.org)
Content-Length: 424
Connection: Keep-Alive --BoundaryAaB03x
content-disposition: form-data; name="name"
//空行
abcdef...
--BoundaryAaB03x
content-disposition: form-data; name=”pic”; filename=“content.txt”
Content-Type: text/plain
//空行
...contents of abc.txt...
--BoundaryAaB03x
content-disposition: form-data; name=”pic”; filename=“content.txt”
Content-Type: text/plain
//空行
...contents of abc.txt...
--BoundaryAaB03x--
格式大概就是在Content-Type:multipart/form-data;boundary=
之后会告诉服务器boundary是什么,然后就可以用boundary作为分割线分割请求块啦。
分割线 = 【--】 +【boundary】
结束 = 【--】 +【boundary】+ 【--】
每块的结构是:
头部字段
//空行
数据实体
如果你想构建下面的请求块的部分,可以用循环的方式,POST_BOUNDS就是分割字符,可以自己随便定义:
for(NSString *key in dicData.allKeys){
id value = [dicData objectForKey:key];
[bodyContent appendFormat:@"--%@\r\n",POST_BOUNDS];
[bodyContent appendFormat:@"Content-Disposition: form-data; name=\"%@\"\r\n\r\n",key];
[bodyContent appendFormat:@"%@\r\n",value];
}
[bodyContent appendFormat:@"--%@--\r\n",POST_BOUNDS];
当上传的字段是文件时,会有Content-Type来表名文件类型;content-disposition,用来说明字段的一些信息。
由于有boundary隔离,所以multipart/form-data既可以上传文件,也可以上传键值对,它采用了键值对的方式,所以可以上传多个文件,这也是他的优势,所以常用与邮箱附件或者表格,一次上传多个文件。
现在来看下AF是如何实现的~
首先是使用:
NSString *documentFolder = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
NSURL *fileURL1 = [NSURL fileURLWithPath:[documentFolder stringByAppendingPathComponent:@"1.txt"]];
NSURL *fileURL2 = [NSURL fileURLWithPath:[documentFolder stringByAppendingPathComponent:@"2.jpg"]];
NSMutableURLRequest *request = [[AFHTTPRequestSerializer serializer] multipartFormRequestWithMethod:@"POST" URLString:uploadURLString parameters:parameter constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
[formData appendPartWithFileURL:fileURL1 name:@"userfile[]" fileName:@"1.txt" mimeType:@"text/plain" error:nil];
[formData appendPartWithFileURL:fileURL2 name:@"userfile[]" fileName:@"aaa.jpg" mimeType:@"image/jpeg" error:nil];
} error:nil];
multipartFormRequestWithMethod做了什么呢?
- (NSMutableURLRequest *)multipartFormRequestWithMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(NSDictionary *)parameters
constructingBodyWithBlock:(void (^)(id <AFMultipartFormData> formData))block
error:(NSError *__autoreleasing *)error
{
NSParameterAssert(method);
NSParameterAssert(![method isEqualToString:@"GET"] && ![method isEqualToString:@"HEAD"]);
NSMutableURLRequest *mutableRequest = [self requestWithMethod:method URLString:URLString parameters:nil error:error];
__block AFStreamingMultipartFormData *formData = [[AFStreamingMultipartFormData alloc] initWithURLRequest:mutableRequest stringEncoding:NSUTF8StringEncoding];
if (parameters) {
for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
NSData *data = nil;
if ([pair.value isKindOfClass:[NSData class]]) {
data = pair.value;
} else if ([pair.value isEqual:[NSNull null]]) {
data = [NSData data];
} else {
data = [[pair.value description] dataUsingEncoding:self.stringEncoding];
}
if (data) {
[formData appendPartWithFormData:data name:[pair.field description]];
}
}
}
if (block) {
block(formData);
}
return [formData requestByFinalizingMultipartFormData];
}
先创建了一个没有parameter的mutableRequest,然后创建了一个AFStreamingMultipartFormData,遍历AFQueryStringPairsFromDictionary(parameter)生成的AFQueryStringPair array,把pair.value转为data并追加给AFStreamingMultipartFormData,最后调用block把formData传给它,然后返回[formData requestByFinalizingMultipartFormData]
。
所以如果我们如果想给formData追加一些数据(文件),可以通过传入block修改formData。
AFStreamingMultipartFormData的初始化方法中设置了request、stringEncoding、boundary([NSString stringWithFormat:@"Boundary+%08X%08X", arc4random(), arc4random()]
)以及bodyStream(AFMultipartBodyStream)。
@implementation AFStreamingMultipartFormData
- (instancetype)initWithURLRequest:(NSMutableURLRequest *)urlRequest
stringEncoding:(NSStringEncoding)encoding
{
self = [super init];
if (!self) {
return nil;
}
self.request = urlRequest;
self.stringEncoding = encoding;
self.boundary = AFCreateMultipartFormBoundary();
self.bodyStream = [[AFMultipartBodyStream alloc] initWithStringEncoding:encoding];
return self;
}
追加pair的field和data:
- (void)appendPartWithFormData:(NSData *)data
name:(NSString *)name
{
NSParameterAssert(name);
NSMutableDictionary *mutableHeaders = [NSMutableDictionary dictionary];
[mutableHeaders setValue:[NSString stringWithFormat:@"form-data; name=\"%@\"", name] forKey:@"Content-Disposition"];
[self appendPartWithHeaders:mutableHeaders body:data];
}
- (void)appendPartWithHeaders:(NSDictionary *)headers
body:(NSData *)body
{
NSParameterAssert(body);
AFHTTPBodyPart *bodyPart = [[AFHTTPBodyPart alloc] init];
bodyPart.stringEncoding = self.stringEncoding;
bodyPart.headers = headers;
bodyPart.boundary = self.boundary;
bodyPart.bodyContentLength = [body length];
bodyPart.body = body;
[self.bodyStream appendHTTPBodyPart:bodyPart];
}
- (void)appendHTTPBodyPart:(AFHTTPBodyPart *)bodyPart {
[self.HTTPBodyParts addObject:bodyPart];
}
这段其实把field转为{@"Content-Disposition":@"form-data; name=\"%@\"", name}
,放入header;吧data直接赋值给bodyPart.body,最后把每个pair生成的bodyPart都加入到AFMultipartBodyStream的HTTPBodyParts。
最后调用了requestByFinalizingMultipartFormData才是真正生成request请求体的地方:
- (NSMutableURLRequest *)requestByFinalizingMultipartFormData {
// 如果stream的bodyPart数组是空,则直接返回request
if ([self.bodyStream isEmpty]) {
return self.request;
}
// 将数组里面第一个bodyPart设为hasInitialBoundary为yes,最后一个bodyPart设置hasFinalBoundary为yes
[self.bodyStream setInitialAndFinalBoundaries];
// 设置request的InputStream为bodyStream,会自动使用这个stream
[self.request setHTTPBodyStream:self.bodyStream];
// 把头部的multipart/form-data; boundary=设置一下,以及计算长度,长度由bodyStream提供
[self.request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", self.boundary] forHTTPHeaderField:@"Content-Type"];
[self.request setValue:[NSString stringWithFormat:@"%llu", [self.bodyStream contentLength]] forHTTPHeaderField:@"Content-Length"];
return self.request;
}
也就是说这里处理了request的头部,然后把bodyStream赋值给了request,之后的请求体就由bodyStream输出了。
AFMultipartBodyStream是继承了NSInputStream,并且实现了NSStreamDelegate的。
@interface AFMultipartBodyStream : NSInputStream <NSStreamDelegate>
AFMultipartBodyStream输出的时候会:
- (NSInteger)read:(uint8_t *)buffer
maxLength:(NSUInteger)length
{
if ([self streamStatus] == NSStreamStatusClosed) {
return 0;
}
NSInteger totalNumberOfBytesRead = 0;
// 只要totalNumberOfBytesRead不大于maxLength并且不大于packet最大要求就可以继续读
while ((NSUInteger)totalNumberOfBytesRead < MIN(length, self.numberOfBytesInPacket)) {
// 如果没有self.currentHTTPBodyPart或者self.currentHTTPBodyPart不availbale则赋值current为[self.HTTPBodyPartEnumerator nextObject]
if (!self.currentHTTPBodyPart || ![self.currentHTTPBodyPart hasBytesAvailable]) {
if (!(self.currentHTTPBodyPart = [self.HTTPBodyPartEnumerator nextObject])) {
break;
}
} else {
//把buffer地址以及最多读的长度传给HTTPBodyPart让它自己读,它会返回读了多少,然后加给totalNumberOfBytesRead
NSUInteger maxLength = MIN(length, self.numberOfBytesInPacket) - (NSUInteger)totalNumberOfBytesRead;
NSInteger numberOfBytesRead = [self.currentHTTPBodyPart read:&buffer[totalNumberOfBytesRead] maxLength:maxLength];
if (numberOfBytesRead == -1) {
self.streamError = self.currentHTTPBodyPart.inputStream.streamError;
break;
} else {
totalNumberOfBytesRead += numberOfBytesRead;
if (self.delay > 0.0f) {
[NSThread sleepForTimeInterval:self.delay];
}
}
}
}
return totalNumberOfBytesRead;
}
这里其实AFMultipartBodyStream循环自己的HTTPBodyPartEnumerator枚举器,然后让currentHTTPBodyPart自己read写入buffer。
那么currentHTTPBodyPart是如何做的呢?
- (NSInteger)read:(uint8_t *)buffer
maxLength:(NSUInteger)length
{
NSInteger totalNumberOfBytesRead = 0;
// 写开头的分割线,如果是hasInitialBoundary那么就不先回车,只分割线+回车;如果不是第一个分割线,就回车+分割线+回车
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)];
}
// 输出header
if (_phase == AFHeaderPhase) {
NSData *headersData = [[self stringForHeaders] dataUsingEncoding:self.stringEncoding];
totalNumberOfBytesRead += [self readData:headersData intoBuffer:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)];
}
// 输出body,直接调用data的inputStream(self.inputStream)输出data
if (_phase == AFBodyPhase) {
NSInteger numberOfBytesRead = 0;
numberOfBytesRead = [self.inputStream read:&buffer[totalNumberOfBytesRead] maxLength:(length - (NSUInteger)totalNumberOfBytesRead)];
if (numberOfBytesRead == -1) {
return -1;
} else {
totalNumberOfBytesRead += numberOfBytesRead;
if ([self.inputStream streamStatus] >= NSStreamStatusAtEnd) {
[self transitionToNextPhase];
}
}
}
// 最后一个分割线,比正常多两个--
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;
}
_phase用于记录当前AFHTTPBodyPart的状态,也就是是处于输出什么内容的阶段,他有四种枚举值:
typedef enum {
AFEncapsulationBoundaryPhase = 1, //还没开始上分割线
AFHeaderPhase = 2, //写入header
AFBodyPhase = 3, //写入body
AFFinalBoundaryPhase = 4, //写入下分割线
} AFHTTPBodyPartReadPhase;
AFHTTPResponseSerializer
看完了request的自动设置请求体,现在来看一下response自动解析。
AFHTTPResponseSerializer是用于解析的基类,它遵从了AFURLResponseSerialization协议:
@interface AFHTTPResponseSerializer : NSObject <AFURLResponseSerialization>
@protocol AFURLResponseSerialization <NSObject, NSSecureCoding, NSCopying>
- (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response
data:(nullable NSData *)data
error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;
@end
然后和request的AFPropertyListRequestSerializer和AFJSONRequestSerializer类似,AFHTTPResponseSerializer也有各种格式的子类,实现了不同的解析方法,它的子类包括:
@interface AFJSONResponseSerializer : AFHTTPResponseSerializer
@interface AFXMLParserResponseSerializer : AFHTTPResponseSerializer
@interface AFXMLDocumentResponseSerializer : AFHTTPResponseSerializer
@interface AFPropertyListResponseSerializer : AFHTTPResponseSerializer
@interface AFImageResponseSerializer : AFHTTPResponseSerializer
@interface AFCompoundResponseSerializer : AFHTTPResponseSerializer
AFHTTPResponseSerializer
基类的responseObjectForResponse非常简单,就是检查了一下response的是不是valid,然后就返回了data。
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
[self validateResponse:(NSHTTPURLResponse *)response data:data error:error];
return data;
}
AFHTTPResponseSerializer有两个属性acceptableContentTypes和acceptableStatusCodes(初始化时200和100),分别存储可以接受的response内容类型以及状态码。
检查valid的方法就是看response的mimetype是不是在acceptableContentTypes内,以及response的状态码是不是在acceptableStatusCodes内,如果不是就返回false。
所以其实基类其实没解析data,幸好AFURLSessionManager里面用的也不是基类,它默认的解析方式是AFJSONResponseSerializer,我们下面看一下JSON。
AFJSONResponseSerializer
它的初始化时酱紫的:
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
self.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript", nil];
return self;
}
只是将self.acceptableContentTypes加入了三种json的mimetype。
然后解析是这样的:
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
// 先判断是不是valid response,没有覆写父类的,所以s仍旧用http的检查有效性的方法
if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) {
return nil;
}
}
// Workaround for behavior of Rails to return a single space for `head :ok` (a workaround for a bug in Safari), which is not interpreted as valid input by NSJSONSerialization.
// See https://github.com/rails/rails/issues/1742
BOOL isSpace = [data isEqualToData:[NSData dataWithBytes:" " length:1]];
if (data.length == 0 || isSpace) {
return nil;
}
NSError *serializationError = nil;
id responseObject = [NSJSONSerialization JSONObjectWithData:data options:self.readingOptions error:&serializationError];
if (!responseObject)
{
if (error) {
*error = AFErrorWithUnderlyingError(serializationError, *error);
}
return nil;
}
if (self.removesKeysWithNullValues) {
return AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions);
}
return responseObject;
}
逻辑还是蛮简单的,如果valid且不为空字符串,就调用NSJSONSerialization来解析data生成json,最后会判断self.removesKeysWithNullValues,执行AFJSONObjectByRemovingKeysWithNullValues移除空value的键值对/值。
这个主要是为了避免在服务器返回的 json 数据若出现 “somevalue”:null,会被解析成 NSNull 的对象。我们向这个 NSNull 对象发送消息的时候就会 crash。AFJSONResponseSerializer 提供的这个属性可以控制是否将此类数据过滤掉。
其余几个parser是如何得到responseObject的
- AFXMLParserResponseSerializer
[[NSXMLParser alloc] initWithData:data] - AFXMLDocumentResponseSerializer
[[NSXMLDocument alloc] initWithData:data options:self.options error:&serializationError] - AFPropertyListResponseSerializer
[NSPropertyListSerialization propertyListWithData:data options:self.readOptions format:NULL error:&serializationError] - AFImageResponseSerializer
[[NSBitmapImageRep alloc] initWithData:data] - AFCompoundResponseSerializer
循环它的responseSerializers,如果哪个可以解析就返回解析结果,否则最后就调用super的responseObjectForResponse
Finally,其实Serializer非常方便,我们可以直接拿到结果以及发送请求,传入一个dict就可以构建request,都是得益于库里面写了很多子类用于解析/生成各种格式的data。
感觉比较棒的几个小美好~
- multipart通过传如入formData给block让block可以自由的增加data
- AFCompoundResponseSerializer用于提供多个serializer,哪个能行哪个就上,非常方便
- AFQueryStringPairsFromKeyAndValue递归生成Pair
- multipart通过inputStream设置,解耦了request请求体的输出,让一个类专门负责输出
references:
http://ju.outofmemory.cn/entry/295593