AFNetworking之AFURLRequestSerialization深入学习

AFURLRequestSerialization主要是对请求进行编码.

字符转码
当发送的网络请求URL中包含了特殊字符, 造成URL无法被解析时, 那这个请求就会发送失败, 出现问题.
所以在发送网络请求之前, 需要对URL进行编码, 对特殊字符进行处理. RFC 3986规定URL中允许包含的字符有英文字母(a~z A~Z) 数字(0~9)以及下列保留字符等.
RFC 3986中定义的保留字符有

 - General Delimiters: ":", "#", "[", "]", "@", "?", "/"
 - Sub-Delimiters: "!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "="

URL的编码通常采用百分号编码, 百分号编码是使用% + (0123456789ABCDEF)中的两位代表一个字符的十六进制形式, URL的编码字符集是US-ASCII, 比如a的ASCII是0x61, 那url编码以后就是%61. 再比如abc经过URL编码以后就是%61%62%63.

1. AFURLRequestSerialization协议

AFURLRequestSerialization是一个协议, 请求序列化将参数编码为查询字符串、HTTP主体、必要时设置适当的HTTP头字段.
AFURLRequestSerialization协议中声明了一个方法:

- (nullable NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
                               withParameters:(nullable id)parameters
                                        error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;

根据指定的参数parametersrequest进行编码, 并将编码以后的request进行返回.

AFURLRequestSerialization文件中声明了三种序列化器, 分别为:
AFHTTPRequestSerializer实现了AFURLRequestSerialization协议, 查询字符串/URL表单编码的参数序列化和默认的请求头,以及响应状态代码和内容类型验证.
AFJSONRequestSerializerAFHTTPRequestSerializer的一个子类, 将parameters参数使用NSJSONSerialization序列化为JSON, 并且设置Content-Typeapplication/json.
AFPropertyListRequestSerializerAFHTTPRequestSerializer的一个子类, 将parameters参数使用NSPropertyListSerializer序列化为JSON, 并且设置Content-Typeapplication/x-plist.

2. 对外提供的序列化函数

FOUNDATION_EXPORT NSString * AFPercentEscapedStringFromString(NSString *string);
FOUNDATION_EXPORT NSString * AFQueryStringFromParameters(NSDictionary *parameters);

个人理解为对外提供了两个全局函数
AFPercentEscapedStringFromString:将指定的string字符串进行百分号编码.
AFQueryStringFromParameters:将指定的parameters字典转换为查询字符串,

3. 在AFN中的使用

我们来看一下AFN是如何使用AFHTTPRequestSerializer进行编码的, 在AFHTTPSessionManagerdataTaskWithHTTPMethod方法中进行构建NSMutableURLRequest的方法如下.

- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
                                       URLString:(NSString *)URLString
                                      parameters:(id)parameters
                                  uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress
                                downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress
                                         success:(void (^)(NSURLSessionDataTask *, id))success
                                         failure:(void (^)(NSURLSessionDataTask *, NSError *))failure
{
    NSError *serializationError = nil;
    NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
  
    ......
}

self.requestSerializer如果没有特殊设置这里默认的是AFHTTPRequestSerializer, 所以这里我们看一下AFHTTPRequestSerializer的序列化方法.

4. AFHTTPRequestSerializer构建

- (instancetype)init {
    self = [super init];
    if (!self) {
        return nil;
    }

    self.stringEncoding = NSUTF8StringEncoding;

    // 存储请求头的字典
    self.mutableHTTPRequestHeaders = [NSMutableDictionary dictionary];
    // 请求头改变的时候在本队列执行, 并行队列
    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
    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"];

    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
    // User-Agent Header; see http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.43
    userAgent = [NSString stringWithFormat:@"%@/%@ (%@; watchOS %@; 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], [[WKInterfaceDevice currentDevice] model], [[WKInterfaceDevice currentDevice] systemVersion], [[WKInterfaceDevice currentDevice] screenScale]];
#elif defined(__MAC_OS_X_VERSION_MIN_REQUIRED)
    userAgent = [NSString stringWithFormat:@"%@/%@ (Mac OS X %@)", [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleExecutableKey] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleIdentifierKey], [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"] ?: [[NSBundle mainBundle] infoDictionary][(__bridge NSString *)kCFBundleVersionKey], [[NSProcessInfo processInfo] operatingSystemVersionString]];
#endif
    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
    self.HTTPMethodsEncodingParametersInURI = [NSSet setWithObjects:@"GET", @"HEAD", @"DELETE", nil];

    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;
}

AFHTTPRequestSerializer的创建也是很重要的一部分.
stringEncoding指定默认的编码方式为UTF8编码.

mutableHTTPRequestHeaders保存了我们修改的请求头的信息, 当对请求头做修改时会在requestHeaderModificationQueue并行队列中执行, 将修改的信息保存在mutableHTTPRequestHeaders字典中, 在调用requestBySerializingRequest对请求编码的时候遍历这个字典, 给request设置header.

acceptLanguagesComponents表示客户端支持的语言.
userAgent将客户端的环境通过User-Agent字段传给服务器.
通过AFHTTPRequestSerializerObservedKeyPaths函数获取AFN监听哪些头部字段的变化, 并且提供监听方法, 在监听方法中如果有值改变, 就赋值给mutableHTTPRequestHeaders字典.

5. AFURLRequestSerialization序列化方法

- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
                               withParameters:(id)parameters
                                        error:(NSError *__autoreleasing *)error
{
    NSParameterAssert(request);

    NSMutableURLRequest *mutableRequest = [request mutableCopy];
    
    // 从自己的head遍历, 如果有值就给request的header赋值
    [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
        if (![request valueForHTTPHeaderField:field]) {
            [mutableRequest setValue:value forHTTPHeaderField:field];
        }
    }];

    // 把各种类型的参数, 转成string
    NSString *query = nil;
    if (parameters) {
        // 按照自定义的方式解析
        if (self.queryStringSerialization) {
            NSError *serializationError;
            query = self.queryStringSerialization(request, parameters, &serializationError);

            if (serializationError) {
                if (error) {
                    *error = serializationError;
                }

                return nil;
            }
        } else {
            // 默认的方式解析参数
            switch (self.queryStringSerializationStyle) {
                case AFHTTPRequestQueryStringDefaultStyle:
                    // 将传入的paramter参数, 用=号链接(key1=name1&key2=name2)
                    query = AFQueryStringFromParameters(parameters);
                    break;
            }
        }
    }

    // HTTPMethodsEncodingParametersInURI, 默认为`GET`, `HEAD`, and `DELETE`
    if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {
        if (query && query.length > 0) {
            // 拼接URL
            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 = @"";
        }
        if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
            [mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
        }
        [mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
    }

    return mutableRequest;
}

将传入的parameters参数序列化到request中.
先遍历HTTPRequestHeaders字典, 将遍历到的值设置到request请求头中.
如果有自定的queryStringSerialization序列化方式, 就执行自定义, 否则按照默认的方式序列化. 这里调用了AFN的自定义函数AFQueryStringFromParameters进行序列化, 下边会讲解这个函数.
比如我们传进来的参数是@{@"key1" : @"name1", @"key2" : @"name2", @"key3" : @"哈哈"}, 参数会经过百分号编码, 执行AFQueryStringFromParameters序列化之后是key1=name1&key2=name2&key3=%E5%93%88%E5%93%88.

如果request的HTTPMethod是GET, HEAD或者DELETE, 就把经过序列化以后的查询字符串拼接到request的URL中. 否则将查询字符串query设置到request的请求体中, 至此request请求设置完毕.

application/x-www-form-urlencoded:数据被编码为名称/值对。这是标准的编码格式。(默认使用这种格式) multipart/form-data: 数据被编码为一条消息,页上的每个控件对应消息中的一个部分。(上传文件需要用到这种) text/plain: 数据以纯文本形式(text/json/xml/html)进行编码,其中不含任何控件或格式字符。postman软件里标的是RAW。

6.AFURLRequestSerialization中的辅助函数

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);
}

/*{
 @"key1" : @"value1",
 @"key2" : @"value2"
 }*/

/**
 对参数进行转码,
 */
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 {
        [mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]];
    }

    return mutableQueryStringComponents;
}

你可能感兴趣的:(AFNetworking之AFURLRequestSerialization深入学习)