美文网首页AFNetworkingiOS第三方库
AFNetworking 2.x 阅读笔记(三)

AFNetworking 2.x 阅读笔记(三)

作者: brownfeng | 来源:发表于2016-08-16 11:16 被阅读142次

    前面两篇文章已经初步介绍了AFNetworking 2.x 的基本情况以及核心类AFXXXXRequestOperation的内容。主要是关于request是如何执行的,responseData如何获取的,然后关于requestSerialization&responseSerialization的内容直接跳过去的。对于requestSerialization的内容主要包括两大块:
    ① request的get&post参数格式化,http headers设置,请求相关其他属性的设置
    ② multipart request相关内容

    具体涉及到的类的结构如下

    requestSerialization.png

    分两篇文章分析这两部分内容,本篇主要是第一部分——参数格式化,headers,timeout等

    1 requestSerialization完成的工作

    对于一个http request,一般需要知道request的method是POST/GET,HTTP headers是什么内容,请求的url中或者HTTP body中的参数是什么,user-agent内容是什么以及request其他属性例如timeout,cache等等如何设置。总之,一切与request相关的设置都是在这个类中完成。在上述工作中,最难的地方就是传入的parameter参数如何转化成request可以使用的参数结构。另一个难点是构建multipart 的http body。

    对于格式化请求参数,在request过程中一般会增加参数例如username=brownfeng&company=webank,POST方法放在http body,GET方法放在URL的?后面。AFNetworking提供了方法,让我们将dict,array,set表示的参数转化成key=value形式,框架中用AFQueryStringPair表示,然后拼接成key1=value1&key2=value2根据http request method的不同,放到url中(进行过url encoded)或者http body中。

    大致的流程如下:参考https://github.com/AFNetworking/AFNetworking/tree/2.x
    对于GET请求

    NSString *URLString = @"http://example.com";
    NSDictionary *parameters = @{@"foo": @"bar", @"baz": @[@1, @2, @3]};
    
    [[AFHTTPRequestSerializer serializer] requestWithMethod:@"GET" URLString:URLString parameters:parameters error:nil];
    --------->
    GET http://example.com?foo=bar&baz[]=1&baz[]=2&baz[]=3
    

    对于POST请求:

    [[AFHTTPRequestSerializer serializer] requestWithMethod:@"POST" URLString:URLString parameters:parameters];
    --------->
    POST http://example.com/
    Content-Type: application/x-www-form-urlencoded
    
    foo=bar&baz[]=1&baz[]=2&baz[]=3
    

    另一个更加细致的parameter -> query string, 参考http://blog.cnbang.net/tech/2371/

    @{
         @"name" : @"bang",
         @"phone": @{@"mobile": @"xx", @"home": @"xx"},
         @"families": @[@"father", @"mother"],
         @"nums": [NSSet setWithObjects:@"1", @"2", nil]
    }
    ->
    @[
         field: @"name", value: @"bang",
         field: @"phone[mobile]", value: @"xx",
         field: @"phone[home]", value: @"xx",
         field: @"families[]", value: @"father",
         field: @"families[]", value: @"mother",
         field: @"nums", value: @"1",
         field: @"nums", value: @"2",
    ]
    ->
    name=bang&phone[mobile]=xx&phone[home]=xx&families[]=father&families[]=mother&nums=1&num=2
    

    AFNetworking不仅仅支持默认的这种格式化,也支持另外两种格式:AFJSONRequestSerializer&AFPropertyListRequestSerializer。这两种格式的转化方法比较简单,直接调用的系统api,本文就不深入讨论。
    另外,如果这三种格式都不满足需求,还可以模仿上述格式化方法自定义的格式——只需要继承AFHTTPRequestSerializer,并实现AFURLRequestSerialization protocol,然后在AFHTTPRequestManager中设置requestSerializer属性为自定义的对象即可。

    2 requestSerialization创建

    如果阅读过第一篇文章,就会有知道, AFHTTPRequestOperationManager有一个AFHTTPRequestSerializer属性,并在designed init 方法中初始化,当我们调用manager方法时,就会触发[AFHTTPRequestSerializer serializer](这里也可以设置成AFJSONRequestSerializer & AFPropertyListRequestSerializer)。这是一个类方法,AFHTTPRequestSerializer中的designed init方法如下,主要设置NSURLRequest的http headers:Accept-Language, User-Agent等等

    - (instancetype)init {
        self = [super init];
        if (!self) {
            return nil;
        }
    
        self.stringEncoding = NSUTF8StringEncoding;//request body 的encoding,如果有的话
        self.mutableHTTPRequestHeaders = [NSMutableDictionary dictionary];//初始化请求的headers
    
    // 设置HTTP请求request的Accept-Language属性
        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"];
    
    //设置 UA
        NSString *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]];
    
        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 需要直接在uri 中 encode parameter 
        self.HTTPMethodsEncodingParametersInURI = [NSSet setWithObjects:@"GET", @"HEAD", @"DELETE", nil];
    
    //对allowsCellularAccess,cachePolicy,HTTPShouldHandleCookies,HTTPShouldUsePipelining,networkServiceType,timeoutInterval等属性进行观察
        self.mutableObservedChangedKeyPaths = [NSMutableSet set];
        for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
            if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
                [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
            }
        }
    
        return self;
    }
    

    3 默认requestSerializer请求序列化的过程

    如果阅读过第一篇文章,就会有知道,requestSerializaiton发生在 AFHTTPRequestOperationManager的
    HTTPRequestOperationWithHTTPMethod:URLString:parameters:success:failure:方法中,会返回一个NSMutableURLRequest。

    NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
    

    而上述方法的实现如下:

    - (NSMutableURLRequest *)requestWithMethod:(NSString *)method
                                     URLString:(NSString *)URLString
                                    parameters:(id)parameters
                                         error:(NSError *__autoreleasing *)error
    {
    //使用宏定义判断是否为nil
        NSParameterAssert(method);
        NSParameterAssert(URLString);
    //创建url
        NSURL *url = [NSURL URLWithString:URLString];
    
        NSParameterAssert(url);
    //创建mutableURLRequest,并设置request method
        NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
        mutableRequest.HTTPMethod = method;
    //十分巧妙的通过serializer的属性设置request的部分属性(值得学习的方法)
        for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
            if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
                [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
            }
        }
    //序列化的核心message
        mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];
    
     return mutableRequest;
    }
    

    方法(NSURLRequest *)requestBySerializingRequest:withParameters:error:是所有序列化方法里面最重要的方法——根据请求是POST or GET设置请求参数到url or body,然后Post情况下设置content-type。重点是如何从paramter中取出参数然后拼成request需要的形式。

    - (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
                                   withParameters:(id)parameters
                                            error:(NSError *__autoreleasing *)error
    {
        NSParameterAssert(request);
    
        NSMutableURLRequest *mutableRequest = [request mutableCopy];
    //通过serializer 的headers属性 设置 request 的headers
        [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
            if (![request valueForHTTPHeaderField:field]) {
                [mutableRequest setValue:value forHTTPHeaderField:field];
            }
        }];
    
        NSString *query = nil;
        if (parameters) {
            if (self.queryStringSerialization) {//自定义的序列化的block,如果不为空
                NSError *serializationError;
                query = self.queryStringSerialization(request, parameters, &serializationError);
    
                if (serializationError) {
                    if (error) {
                        *error = serializationError;
                    }
    
                    return nil;
                }
            } else {
                switch (self.queryStringSerializationStyle) {
                    case AFHTTPRequestQueryStringDefaultStyle:
    //调用系统的parameter -> queryString 方法,生成样式   xxx=xx&xxx=xx,对于转化成json&property模式替换此方法
                        query = AFQueryStringFromParameters(parameters);
                        break;
                }
            }
        }
    //如果HTTP method 是 GET
        if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
            if (query) {
                mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
            }
        } else {//如果HTTP  method 是 POST
            // #2864: an empty string is a valid x-www-form-urlencoded payload
            if (!query) {
                query = @"";
            }
    //设置Content-Type
            if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
                [mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
            }
    //将query 放到HTTPBody中
            [mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
        }
    
        return mutableRequest;
    }
    

    通过源码可以发现如果设置了queryStringSerialization Block,AFNetworking就会调用这个block来序列化query参数,也可以将这部分参数添加到系统中,定义成一个style来选择。
    关键代码是(id)paramters -> (NSString *)query 有关参数的组装拼接主要包括以下方法&属性

    @property (readwrite, nonatomic, copy) AFQueryStringSerializationBlock queryStringSerialization
    static NSString * AFQueryStringFromParameters(NSDictionary *parameters)
    NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary)
    NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value)

    具体的实现如下:

    //网络框架的  parameters->query  入口方法,系统只支持paramters是NSDictionary*的参数列表,如果需要解析其他parameters的形式,比如model||array等等,需要自己实现queryStringSerialization block。也可以在AFHTTPRequestManager进行封装,增加其他的添加参数的形式,本系列最后会给一个传入model当做参数的实例。
    static 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);
    }
    
    //一个递归调用的方法:递归的判断传入的value是否是dict,array,set,非集合类型,如果是value是集合类型,dict,set无序集合需要先排序再处理,继续递归,如果是非集合类型则会返回,最后得到一个pair类型的array,返回上一层进行foreach操作,输出pair 的URLEncodedStringValue(url编码)值的数组,然后组装成'&'间隔的字符串paramters。
    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) {
                    [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue((key ? [NSString stringWithFormat:@"%@[%@]", key, nestedKey] : nestedKey), nestedValue)];
                }
            }
        } else if ([value isKindOfClass:[NSArray class]]) {
            NSArray *array = value;
            for (id nestedValue in array) {
                [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue([NSString stringWithFormat:@"%@[]", key], nestedValue)];
            }
        } else if ([value isKindOfClass:[NSSet class]]) {
            NSSet *set = value;
            for (id obj in [set sortedArrayUsingDescriptors:@[ sortDescriptor ]]) {
                [mutableQueryStringComponents addObjectsFromArray:AFQueryStringPairsFromKeyAndValue(key, obj)];
            }
        } else {//如果非集合类,直接当做key-value pair
            [mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]];
        }
    
        return mutableQueryStringComponents;
    }
    

    总结经验:
    ① 使用#define NSParameterAssert(condition) NSAssert((condition), @"Invalid parameter not satisfying: %@", @#condition)宏定义来判断参数是否为nil
    ② 将parameter转化成key-value pair的方法值得借鉴,在格式化其他内容时候使用递归调用的方式处理。最后一篇文章会给出一个实例。
    ③ 使用KVO的方式设置request的其他属性(如果没有手动设置requestSerializer的这几个属性,以下几个setter方法不会出发,生成的request相关属性都是默认的),使用流程如下:

    • 在init方法中调用方法如下方法,对需要设置的属性进行KVO观察
        for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
            if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
                [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
            }
        }
    
    • 在每个观察属性的getter方法中willChange 和 didChange方法 send message
    - (void)setAllowsCellularAccess:(BOOL)allowsCellularAccess {
        [self willChangeValueForKey:NSStringFromSelector(@selector(allowsCellularAccess))];
        _allowsCellularAccess = allowsCellularAccess;
        [self didChangeValueForKey:NSStringFromSelector(@selector(allowsCellularAccess))];
    }
    - (void)setCachePolicy:(NSURLRequestCachePolicy)cachePolicy {
        [self willChangeValueForKey:NSStringFromSelector(@selector(cachePolicy))];
        _cachePolicy = cachePolicy;
        [self didChangeValueForKey:NSStringFromSelector(@selector(cachePolicy))];
    }
    - (void)setHTTPShouldHandleCookies:(BOOL)HTTPShouldHandleCookies {
        [self willChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldHandleCookies))];
        _HTTPShouldHandleCookies = HTTPShouldHandleCookies;
        [self didChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldHandleCookies))];
    }
    - (void)setHTTPShouldUsePipelining:(BOOL)HTTPShouldUsePipelining {
        [self willChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldUsePipelining))];
        _HTTPShouldUsePipelining = HTTPShouldUsePipelining;
        [self didChangeValueForKey:NSStringFromSelector(@selector(HTTPShouldUsePipelining))];
    }
    - (void)setNetworkServiceType:(NSURLRequestNetworkServiceType)networkServiceType {
        [self willChangeValueForKey:NSStringFromSelector(@selector(networkServiceType))];
        _networkServiceType = networkServiceType;
        [self didChangeValueForKey:NSStringFromSelector(@selector(networkServiceType))];
    }
    - (void)setTimeoutInterval:(NSTimeInterval)timeoutInterval {
        [self willChangeValueForKey:NSStringFromSelector(@selector(timeoutInterval))];
        _timeoutInterval = timeoutInterval;
        [self didChangeValueForKey:NSStringFromSelector(@selector(timeoutInterval))];
    }
    
    • 最后在属性发生变化时,将key加入到mutableObservedChangedKeyPaths中,下次调用requestXXXXX方法生成request对象时,就可以讲设置的相关属性设置到request对象中。
    + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key {
        if ([AFHTTPRequestSerializerObservedKeyPaths() containsObject:key]) {
            return NO;
        }
    
        return [super automaticallyNotifiesObserversForKey:key];
    }
    
    - (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];
            }
        }
    }
    
    • - (NSMutableURLRequest *)requestWithMethod:URLString:parameters:error:方法中调用如下语句,返回mutableRequest时候就会去设置前面加入到mutableObservedChangedKeyPaths的属性
        for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
            if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
                [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
            }
        }
    

    相关文章

      网友评论

        本文标题:AFNetworking 2.x 阅读笔记(三)

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