AFNetworking 2.x 阅读笔记(三)

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

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

AFNetworking 2.x 阅读笔记(三)_第1张图片
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 阅读笔记(三))