美文网首页iOS面试&笔试
[iOS] AFNetworking源码学习—Serialize

[iOS] AFNetworking源码学习—Serialize

作者: 木小易Ying | 来源:发表于2019-10-24 22:31 被阅读0次

    还记得在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就是用于存储被修改过的属性,并且修改后的值不为空。


    • 如何生成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。

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

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

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

    相关文章

      网友评论

        本文标题:[iOS] AFNetworking源码学习—Serialize

        本文链接:https://www.haomeiwen.com/subject/emalvctx.html