iOS面试&笔试

[iOS] AFNetworking源码学习—Serialize

2019-10-24  本文已影响0人  木小易Ying

还记得在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-LanguageUser-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的六个属性。(allowsCellularAccesscachePolicyHTTPShouldHandleCookiesHTTPShouldUsePipeliningnetworkServiceTypetimeoutInterval

比较神奇并且我一直木有搞明白的是,作者通过关闭自动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就是用于存储被修改过的属性,并且修改后的值不为空。


- (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"

- (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有两种方式:

举个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的


Finally,其实Serializer非常方便,我们可以直接拿到结果以及发送请求,传入一个dict就可以构建request,都是得益于库里面写了很多子类用于解析/生成各种格式的data。

感觉比较棒的几个小美好~

  1. multipart通过传如入formData给block让block可以自由的增加data
  2. AFCompoundResponseSerializer用于提供多个serializer,哪个能行哪个就上,非常方便
  3. AFQueryStringPairsFromKeyAndValue递归生成Pair
  4. multipart通过inputStream设置,解耦了request请求体的输出,让一个类专门负责输出

references:
http://ju.outofmemory.cn/entry/295593

上一篇下一篇

猜你喜欢

热点阅读