美文网首页iOS
AFNetworking主线梳理(二)

AFNetworking主线梳理(二)

作者: ChinaChong | 来源:发表于2019-11-17 19:23 被阅读0次

    上一篇整理了AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];,这一篇整理AFN的重头戏:请求。从[manager POST:parameters:progress:success:failure:]开始梳理。


    AFHTTPSessionManager 部分

    日常开发中请求主要就是GET和POST两种

    GET 和 POST 的声明

    GET声明

    - (nullable NSURLSessionDataTask *)GET:(NSString *)URLString
                                parameters:(nullable id)parameters
                                  progress:(nullable void (^)(NSProgress *downloadProgress))downloadProgress
                                   success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
                                   failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
    

    POST声明

    - (nullable NSURLSessionDataTask *)POST:(NSString *)URLString
                                 parameters:(nullable id)parameters
                                   progress:(nullable void (^)(NSProgress *uploadProgress))uploadProgress
                                    success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success
                                    failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
    

    GET 和 POST 的实现

    GET实现

    - (NSURLSessionDataTask *)GET:(NSString *)URLString
                       parameters:(id)parameters
                         progress:(void (^)(NSProgress * _Nonnull))downloadProgress
                          success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success
                          failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure
    {
    
        NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET"
                                                            URLString:URLString
                                                           parameters:parameters
                                                       uploadProgress:nil
                                                     downloadProgress:downloadProgress
                                                              success:success
                                                              failure:failure];
    
        [dataTask resume];
    
        return dataTask;
    }
    

    POST实现

    - (NSURLSessionDataTask *)POST:(NSString *)URLString
                        parameters:(id)parameters
                          progress:(void (^)(NSProgress * _Nonnull))uploadProgress
                           success:(void (^)(NSURLSessionDataTask * _Nonnull, id _Nullable))success
                           failure:(void (^)(NSURLSessionDataTask * _Nullable, NSError * _Nonnull))failure
    {
        NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"POST" URLString:URLString parameters:parameters uploadProgress:uploadProgress downloadProgress:nil success:success failure:failure];
    
        [dataTask resume];
    
        return dataTask;
    }
    

    分解说明

    1. 首先要理解,AFHTTPSessionManager类的GET和POST方法内部实现的核心是:调用dataTaskWithHTTPMethod:URLString:parameters:uploadProgress:downloadProgress:success:failure:方法来获取NSURLSessionDataTask。
    2. 这个NSURLSessionDataTask才是真正可以发起网络请求的类。而开启请求的真正方法是resume,当NSURLSessionDataTask调用了resume,网络请求才发起。

    GET、POST 核心方法dataTaskWithHTTPMethod:URLString:parameters:uploadProgress:downloadProgress:success:failure:

    dataTaskWithHTTPMethod...的实现

    第一部分解析

    一句话描述 : 第一部分做的事情就是创建一个NSMutableURLRequest。

    NSError *serializationError = nil;
    NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
    if (serializationError) {
        if (failure) {
            dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
                failure(nil, serializationError);
            });
        }
    
        return nil;
    }
    

    先说整体逻辑

    1. 第一部分创建的NSMutableURLRequest是要提供给第二部分使用。
    2. 这个NSMutableURLRequest不是AFHTTPSessionManager类自己创建的,是通过自己的属性requestSerializer调用requestWithMethod:URLString:parameters:error:方法创建的。如果NSMutableURLRequest创建失败,则直接回调给开发者网络请求失败。

    分解说明:

    1. 属性requestSerializer:requestSerializer是AFHTTPSessionManager类自己独有的属性,父类AFHTTPRequestSerializer没有这个属性。requestSerializer在AFHTTPSessionManager初始化的时候已经默认进行了初始化,开发者也可以随时修改此属性。初始化详情可以参考AFNetworking主线梳理(一)中的介绍。

    2. 参数completionQueue:失败回调的队列默认是主队列,如果设置了completionQueue,则走设置的队列。completionQueue是AFHTTPSessionManager父类AFURLSessionManager的属性,需要开发者来设置。


    接下来,解析requestWithMethod:URLString:parameters:error:,需要跳转到AFHTTPRequestSerializer 部分(往下翻)。

    第二部分解析

    一句话描述:
    通过调用dataTaskWithRequest:uploadProgress:downloadProgress:completionHandler:方法来创建一个NSURLSessionDataTask实例对象。

    __block NSURLSessionDataTask *dataTask = nil;
    dataTask = [self dataTaskWithRequest:request
                          uploadProgress:uploadProgress
                        downloadProgress:downloadProgress
                       completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
        if (error) {
            if (failure) {
                failure(dataTask, error);
            }
        } else {
            if (success) {
                success(dataTask, responseObject);
            }
        }
    }];
    


    分解说明

    1. NSURLSessionDataTask就是前面说的GET和POST方法核心实现所需要的task。
    2. dataTaskWithRequest:uploadProgress:downloadProgress:completionHandler:方法所需要的参数request就是第一部分创建的NSMutableURLRequest实例。
    3. AFHTTPSessionManager自己并没有实现dataTaskWithRequest:uploadProgress:downloadProgress:completionHandler:方法,是它的父类AFURLSessionManager实现了这个方法。


    接下来,解析dataTaskWithRequest:uploadProgress:downloadProgress:completionHandler:方法,跳转到AFURLSessionManager 部分(使劲往下翻)。


    AFHTTPRequestSerializer 部分

    接续 GET、POST 核心方法dataTaskWithHTTPMethod 方法第一部分,AFHTTPRequestSerializer的requestWithMethod:URLString:parameters:error:方法最终目的是创建一个NSMutableURLRequest实例并返回。

    requestWithMethod:URLString:parameters:error: 方法解析

    以下简称:requestWithMethod方法。

    requestWithMethod...方法的实现

    requestWithMethod方法整体逻辑:

    1. 使用传入的参数URLString创建一个NSURL *url
    2. 使用url创建一个NSMutableURLRequest *mutableRequest
    3. 将传入的参数 method 保存到 mutableRequest 的 HTTPMethod 属性中
    4. 第一部分:简单讲就是在配置NSMutableURLRequest的各种参数。
    5. 第二部分:简单讲其实也是在配置NSMutableURLRequest的各种参数。
    6. NSMutableURLRequest配置完成后,返回其实例mutableRequest。

    第一部分

    整体逻辑:

    1. AFHTTPRequestSerializerObservedKeyPaths()函数会创建并返回一个数组,数组中有6个属性名的字符串(keyPath)。
    2. for in 遍历这个数组,取出每一个属性名的字符串(keyPath)。
    3. 判断self.mutableObservedChangedKeyPaths(一个NSMutableSet)中是否包含keyPath,如果包含这个keyPath,则把AFHTTPRequestSerializer自己这个属性的值赋值给mutableRequest。

    分解说明:

    1. AFHTTPRequestSerializer有6个特定的属性,分别是:

      1. @property (nonatomic, assign) BOOL allowsCellularAccess;
      2. @property (nonatomic, assign) NSURLRequestCachePolicy cachePolicy;
      3. @property (nonatomic, assign) BOOL HTTPShouldHandleCookies;
      4. @property (nonatomic, assign) BOOL HTTPShouldUsePipelining;
      5. @property (nonatomic, assign) NSURLRequestNetworkServiceType networkServiceType;
      6. @property (nonatomic, assign) NSTimeInterval timeoutInterval;
    2. 这6个属性开发者可以读写,而这6个属性与NSMutableURLRequest的属性一一对应,换句话说AFHTTPRequestSerializer的6个属性最终目的是开发者设置具体值,然后传递给NSMutableURLRequest,只不过开发者从设置值到mutableRequest接收到值的过程略显复杂。

    3. 在AFHTTPRequestSerializer的init方法中依次对6个属性添加了键值观察(KVO)。
      init方法片段:

    - (instancetype)init {
        self = [super init];
        if (!self) {
            return nil;
        }
    
        ...省略...
        
        for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
            if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
                [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
            }
        }
    
        return self;
    }
    

    1. AFHTTPRequestSerializer重写了每一个属性的setter,成员变量Ivar赋值前后加了KVO的willChangeValueForKeydidChangeValueForKey来手动触发KVO。
      AFHTTPRequestSerializer的6个属性的setter实现方式完全一致,这里只贴两个属性的setter源码:
    - (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))];
    }
    
    等等......
    

    1. AFHTTPRequestSerializerObservedKeyPaths()函数:
      下的源码就是AFHTTPRequestSerializerObservedKeyPaths()函数的实现,其实很简单,只是创建了一个单例数组,数组中是这个6个属性的getter由selector转成的string。
    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;
    }
    

    1. self.mutableObservedChangedKeyPaths 解析
      1. self.mutableObservedChangedKeyPaths是什么?
        • 答:AFHTTPRequestSerializer的一个属性。用来保存被开发者赋值的6个属性中的一个或多个(也可以没有)。
        @property (readwrite, nonatomic, strong) NSMutableSet *mutableObservedChangedKeyPaths;
        
      2. self.mutableObservedChangedKeyPaths是在什么地方被初始化的?
        • 答:是在AFHTTPRequestSerializer的init方法中被初始化的。
        self.mutableObservedChangedKeyPaths = [NSMutableSet set];
        
      3. 案例Demo中并没有调用AFHTTPRequestSerializer的init方法,AFHTTPRequestSerializer的init方法是什么时候调用的?
        • 答:完整的调用顺序是:[AFHTTPSessionManager manager] => [AFHTTPRequestSerializer serializer] => [[self alloc] init]。这里self是指AFHTTPRequestSerializer。
      4. self.mutableObservedChangedKeyPaths在什么时候添加的元素?
        • 答:继续往下看。


    第一部分总结:

    AFHTTPRequestSerializer早在初始化的时候就对6个属性依次添加了键值观察(KVO)。如果我们在调用 AFHTTPSessionManager 的 GET 方法或者 POST 方法之前对这6个属性赋了值,就会调用属性的setter,接着会触发KVO的通知,来到KVO的observeValueForKeyPath:ofObject:change:context:回调方法。

    #pragma mark - NSKeyValueObserving
    
    + (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];
            }
        }
    }
    

    在KVO的observeValueForKeyPath:ofObject:change:context:回调方法中,根据值是否为空,来操作self.mutableObservedChangedKeyPaths是添加该keyPath还是移除。

    我们假定开发者在调用GET或POST方法请求前设置了timeoutInterval属性:

    1. 走timeoutInterval属性的setter
    2. 触发KVO的通知,来到KVO的observeValueForKeyPath:ofObject:change:context:回调方法
    3. timeoutInterval属性被添加到了mutableObservedChangedKeyPaths集合中
    4. 接下来就是调用AFHTTPSessionManager的 GET 或 POST 方法发起请求
    5. 然后就会来到此处(requestWithMethod:URLString:parameters:error:方法的第一部分,就是现在在解析的这里)
    6. 接着把timeoutInterval赋值给NSMutableURLRequest。

    以上就是第一部分做的全部事情。


    一些补充:

    • automaticallyNotifiesObserversForKey:函数用来根据条件阻止KVO发出通知,系统提供的方法,AFN只是进行了重写。
    • 集合(set)自带去重的功能,所以当6个属性被多次赋值时,也就是多次触发observeValueForKeyPath,mutableObservedChangedKeyPaths集合(set)中不会出现多个同样的keyPath。

    为什么AFN要手动触发KVO,而不是自动触发KVO ?

    答:AFN的早期版本也使用KVO观察这6个属性,有人发现在单元测试中会出现崩溃,所以后来增加了automaticallyNotifiesObserversForKey方法来阻止这6个属性通过KVO自动发送通知。但是很快人们发现这样做,这6个属性使用开始出现问题,会有设置后无效的情况。于是作者又改了回去,删了automaticallyNotifiesObserversForKey方法,但是问题又绕回原地了。因此为了能在单元测试中正常使用,并且这6个属性的使用不能受到影响,即正常可以使用KVO,于是增加了6个属性的setter,在setter里面手动触发KVO。

    第二部分 (高能预警)

    一句话描述:
    根据请求方法(GET、POST)的不同,将Parameters按照每种方法的要求组装到NSMutableURLRequest的实例mutableRequest中去。即GET方法是将参数处理后拼接到URL中,POST方法是将参数处理后设置到请求体中。

    第二部分核心方法:requestBySerializingRequest:withParameters:error:

    - (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
                                   withParameters:(id)parameters
                                            error:(NSError *__autoreleasing *)error
    {
        NSParameterAssert(request);
    
        NSMutableURLRequest *mutableRequest = [request mutableCopy];
        
        // 这部分起个名:self.HTTPRequestHeaders部分
        [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
            if (![request valueForHTTPHeaderField:field]) {
                [mutableRequest setValue:value forHTTPHeaderField:field];
            }
        }];
    
        // 这部分起个名:处理传入的parameters部分
        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:
                        query = AFQueryStringFromParameters(parameters);
                        break;
                }
            }
        }
    
        // 这部分起个名:使用query装配mutableRequest部分
        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 = @"";
            }
            if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
                [mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
            }
            [mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
        }
    
        return mutableRequest;
    }
    


    在源码的注释中为了方便叙述我给划分成了3个部分,分别是:

    1. self.HTTPRequestHeaders部分
    2. 处理传入的parameters部分
    3. 使用query装配mutableRequest部分

    按照代码顺序,逐步解析:

    self.HTTPRequestHeaders部分

    [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
        if (![request valueForHTTPHeaderField:field]) {
            [mutableRequest setValue:value forHTTPHeaderField:field];
        }
    }];
    

    一句话描述:
    遍历self.HTTPRequestHeaders,如果我们创建的新 NSURLRequest *request 中没有开发者指定(init中也指定了默认)的请求头字段和值,那么把没有的这部分开发者指定的的请求头字段和值保存一份到request中。

    整体逻辑

    1. 遍历self.HTTPRequestHeaders
      • self.HTTPRequestHeaders只有初始化时AFN默认添加的Accept-LanguageUser-Agent两个键值对。
    2. 如果我们创建的新 NSURLRequest *request 中开发者指定了Accept-LanguageUser-Agent以外的请求头键值对,那么把这些Accept-LanguageUser-Agent以外的请求头键值对保存一份到request中。

    分解说明

    1. self.HTTPRequestHeaders 的声明在 AFHTTPRequestSerializer.h:

      @property (readonly, nonatomic, strong) NSDictionary <NSString *, NSString *> *HTTPRequestHeaders;
      
    2. self.HTTPRequestHeaders 的取值方法,即getter:

      - (NSDictionary *)HTTPRequestHeaders {
          NSDictionary __block *value;
          dispatch_sync(self.requestHeaderModificationQueue, ^{
              value = [NSDictionary dictionaryWithDictionary:self.mutableHTTPRequestHeaders];
          });
          return value;
      }
      

      getter解析:

      • self.HTTPRequestHeaders 的值完全来自于 self.mutableHTTPRequestHeaders
      • requestHeaderModificationQueue 在 AFHTTPRequestSerializer 的 init 方法中初始化,是一个并发队列。具体请参考AFNetworking主线梳理(一)AFHTTPRequestSerializer 部分。
      self.requestHeaderModificationQueue = dispatch_queue_create("requestHeaderModificationQueue",  DISPATCH_QUEUE_CONCURRENT);
      
    3. self.mutableHTTPRequestHeaders

      • self.mutableHTTPRequestHeaders 的声明在 AFHTTPRequestSerializer.m:
      @property (readwrite, nonatomic, strong) NSMutableDictionary *mutableHTTPRequestHeaders;
      
      • self.mutableHTTPRequestHeaders 在 AFHTTPRequestSerializer 的 init 方法中初始化
      self.mutableHTTPRequestHeaders = [NSMutableDictionary dictionary];
      
      • self.mutableHTTPRequestHeaders 如何被赋值?
        • self.mutableHTTPRequestHeaders 是在 setValue:forHTTPHeaderField: 方法中间接赋值的。
        • requestHeaderModificationQueue 在 AFHTTPRequestSerializer 的 init 方法中初始化,和前面第2小点的"self.HTTPRequestHeaders取值方法"用的是同一个并发队列。具体请参考AFNetworking主线梳理(一)AFHTTPRequestSerializer 部分。
    1. 那么 self.mutableHTTPRequestHeaders 是什么时候被赋上默认值的呢?
      • 答:在 AFHTTPRequestSerializer的init 方法中,分两次给 self.mutableHTTPRequestHeaders 赋了默认值,分别是:Accept-Language、User-Agent。具体请参考AFNetworking主线梳理(一)AFHTTPRequestSerializer 部分。

    至此,“第二部分 (高能预警)” => “self.HTTPRequestHeaders部分”的使命完成。

    问:开发者如何给request指定请求头的字段和值呢?
    答:通过 AFHTTPRequestSerializer 的 setValue:forHTTPHeaderField: 方法。

    处理传入的parameters部分

    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:
                    query = AFQueryStringFromParameters(parameters);
                    break;
            }
        }
    }
    

    一句话描述:“处理传入的parameters部分” 就是将parameters按照规则拼接成query string。

    假定开发者传入的parameters为字典{"key1" : "value1", "key2" : "value2"},那么query = "key1=value1&key2=value2"。

    接下来我们看看parameters是如何被拼接成query的。

    “处理传入的parameters部分” 按照 if else被自然分割成两部分,先看 if 这部分。

    if 分支整体逻辑:

    1. 如果 self.queryStringSerialization(一个block)存在,即开发者实现了这个block,则执行 if 分支内的语句。
    2. 把 request、parameters 以及 *error 传递给self.queryStringSerialization这个block,由block处理完成后,返回query。

    分解说明:

    1. self.queryStringSerialization 是一个block,需要开发者实现,声明以及赋值方法 API 如下
    // AFURLRequestSerialization.h
    - (void)setQueryStringSerializationWithBlock:(nullable NSString * (^)(NSURLRequest *request, id parameters, NSError * __autoreleasing *error))block;
    
    // AFURLRequestSerialization.m
    typedef NSString * (^AFQueryStringSerializationBlock)(NSURLRequest *request, id parameters, NSError *__autoreleasing *error);
    
    @property (readwrite, nonatomic, copy) AFQueryStringSerializationBlock queryStringSerialization;
    
    - (void)setQueryStringSerializationWithBlock:(NSString *(^)(NSURLRequest *, id, NSError *__autoreleasing *))block {
        self.queryStringSerialization = block;
    }
    
    1. self.queryStringSerialization 是什么时候、什么地方被实现的?
      答:没有默认的初始化,需要开发者去实现此block。如果开发者没有实现这个block,就不会走 if 分支。

    2. self.queryStringSerialization 的作用
      答:这个block的意义所在,是当开发者如果需要自定义 query string 的拼接规则,则用block传过来的request、parameters 以及 error 自行拼接一个 query string 并返回。

    3. NSError *__autoreleasing *error 干啥的?
      答案:如果开发者在拼接 query string 过程中出现错误,想要抛出Error,就可以利用这个指针的指针来直接传递error。


    else 分支整体逻辑:

    可以说 else 分支直接可以看成 query = AFQueryStringFromParameters(parameters); 这一句代码最为合适。

    如果开发者没有使用(实现)self.queryStringSerialization,则会走 else分支,else分支也是最普通、最常用的分支。

    分解说明:

    1. 虽然 else 分支是一整个switch控制的,但是AFHTTPRequestQueryStringSerializationStyle枚举值只有一个而且还是 0 (NSUInteger)。也就是说开发者设置这个Style也好不设置也好,最终都会走switch的第一个case分支,目前AFN的版本中你没得选。
    typedef NS_ENUM(NSUInteger, AFHTTPRequestQueryStringSerializationStyle) {
        AFHTTPRequestQueryStringDefaultStyle = 0,
    };
    
    1. self.queryStringSerializationStyle 是一个枚举值,需要开发者指定,声明以及赋值方法 API 如下
    // AFURLRequestSerialization.h
    - (void)setQueryStringSerializationWithStyle:(AFHTTPRequestQueryStringSerializationStyle)style;
    
    // AFURLRequestSerialization.m
    @property (readwrite, nonatomic, assign) AFHTTPRequestQueryStringSerializationStyle queryStringSerializationStyle;
    
    - (void)setQueryStringSerializationWithStyle:(AFHTTPRequestQueryStringSerializationStyle)style {
        self.queryStringSerializationStyle = style;
        self.queryStringSerialization = nil;
    }
    


    重点!!!
    else 分支的核心函数 AFQueryStringFromParameters()

    1. AFQueryStringFromParameters() 函数

    一句话描述:按照规则把parameters中所有的key和value拼接成一个字符串。

    NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
        NSMutableArray *mutablePairs = [NSMutableArray array];
        for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
            [mutablePairs addObject:[pair URLEncodedStringValue]];
        }
    
        return [mutablePairs componentsJoinedByString:@"&"];
    }
    


    详细逻辑:

    1. 创建一个空的可变数组,NSMutableArray *mutablePairs
    2. 调用AFQueryStringPairsFromDictionary()函数获得一个数组,一个装满AFQueryStringPair实例对象的数组。AFQueryStringPair是AFURLRequestSerialization的私有类,具体实现贴在文末(往下翻)。
    NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) {
        return AFQueryStringPairsFromKeyAndValue(nil, dictionary);
    }
    
    1. AFQueryStringPairsFromDictionary()函数直接调用AFQueryStringPairsFromKeyAndValue()函数
    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;
    }
    
    1. AFQueryStringPairsFromKeyAndValue()函数采用递归调用方式将传进来的value(假定是字典)一层一层剥开,直到value不再是任何类型的集合,然后组装成AFQueryStringPair实例对象添加到数组中,返回数组。
      • 4.1 如果传进来的value是一个字典套字典的结构:value = {data : {adSource : Admob}}。如果传进来的key是空,则第一次递归不重新组装函数的参数key,直接用下一层的key当做函数递归时的key。
      • 4.2 假定传进来的key = "Hello"
      • 4.3 先把value的allKeys进行升序排序,形成一个新的数组。
      • 4.4 for in 遍历这个新的数组,取出每一个key(data这一层),使用这个key从字典取出{adSource : Admob},即先把value这个字典剥开一层。
      • 4.5 重新组装AFQueryStringPairsFromKeyAndValue(NSString *key, id value)函数的参数,进行递归。
      • 4.6 若函数传进来的key为Hello,则递归时key="Hello[data]",value={adSource : Admob}。若函数传进来的key为空,则递归时key="data",value={adSource : Admob}
      • 4.7 递归到最后,会走到AFQueryStringPairsFromKeyAndValue()函数的最后一个分支,即不是任何一个集合的else分支里,此例的最终key="Hello[data][adSource]"或者"data[adSource]",最终的value="Admob",用这个键值对初始化一个AFQueryStringPair实例对象,添加到数组,返回此数组。
    2. 经过3和4已经获得了2所需要的装满AFQueryStringPair实例对象的数组,for in 遍历这个数组中的每一个AFQueryStringPair实例对象,将[AFQueryStringPair URLEncodedStringValue]的返回值添加到数组。
    3. 第5步的数组组装完成后,大致应该是这样("data[adSource]=Admob", "data[adType]=3")。然后将这个数组使用"&"拆分组装成字符串,返回这个字符串,这个字符串就是query string。这个query大致长这样:"data[adSource]=Admob& data[adType]=3"
    4. query组装完成,AFQueryStringFromParameters()函数完成使命,返回query

    至此,AFQueryStringFromParameters()函数的使命已完成,返回了拼接好的query。
      把文章往上翻🤓
    这个query由else分支的query接收,也就是说else分支的使命也已完成。
      再把文章往上翻🤓
    if分支是由开发者实现的self.queryStringSerialization block来完成query的拼接。ifelse 分支都可完成query的拼接。
      再一次把文章往上翻🤓
    “第二部分 (高能预警)” => “处理传入的parameters部分”就全部完成了,接下来该说一说“第二部分 (高能预警)” => “使用query装配mutableRequest部分”

    使用query装配mutableRequest部分

    一句话描述:将query装配到mutableRequest里。

    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 = @"";
        }
        if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
            [mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
        }
        [mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
    }
    

    详细逻辑:

    1. 这一部分仍然是 if else 天然分割成的两个分支。判断条件是:self.HTTPMethodsEncodingParametersInURI(NSSet)里是否包含 [request HTTPMethod]的请求方法。

    2. if 分支:如果[request HTTPMethod]的方法是GETDELETEHEAD这三种,则属于包含在里面。那么把请求时传进来的URL拆分,连同query一起,添加进去,将URL重新组装,再赋值给mutableRequest.URL,比如GET方法的请求参数就是拼接在URL里面。

    3. else 分支:如果[request HTTPMethod]的方法是POST,则不包含在里面。那么就把query按照UTF8编码成data后,调用[mutableRequest setHTTPBody:]POST 的请求参数(query)设置到请求体中。


    分解说明:

    1. self.HTTPMethodsEncodingParametersInURI 是一个集合(NSSet),AFN给了默认值GET, HEAD, DELETE。开发者也可以自己指定。头文件里声明,AFJSONRequestSerializer 的 init 方法设置的默认值:
    // AFURLRequestSerialization.h
    @property (nonatomic, strong) NSSet <NSString *> *HTTPMethodsEncodingParametersInURI;
    
    // AFURLRequestSerialization.m => init 方法
    
    self.HTTPMethodsEncodingParametersInURI = [NSSet setWithObjects:@"GET", @"HEAD", @"DELETE", nil];
    
    1. [request HTTPMethod] 是什么?
      答:[request HTTPMethod] 方法返回的是开发者设置到request的请求方法,诸如 GET、POST 等。

    2. request的请求方法是什么时候设置的?
      答:完整的调用顺序是

      1. AFHTTPSessionManager:GET或者POST方法
      2. AFHTTPSessionManager:[dataTaskWithHTTPMethod:URLString:parameters:uploadProgress:downloadProgress:success:failure:]方法
      3. AFHTTPRequestSerializer:[requestWithMethod:URLString:parameters:error:] 方法
      4. AFHTTPRequestSerializer:mutableRequest.HTTPMethod = method;
    3. self.stringEncoding 是什么?
      答:字符串编码,默认为NSUTF8StringEncoding

    4. self.stringEncoding 是什么时候设置的默认值?
      答:AFHTTPRequestSerializer 的 init 方法中。

    self.stringEncoding = NSUTF8StringEncoding;
    

    至此,“第二部分 (高能预警)” => “使用query装配mutableRequest部分” 使命已完成,已成功的将query string装配到mutableRequest里。

    同时也意味着“第二部分 (高能预警)”这一部分使命也已完成。

    再加上第一部分,[requestWithMethod:URLString:parameters:error:]方法的使命也已完成,创建一个NSMutableURLRequest实例,并返回给 AFHTTPSessionManager 的 [dataTaskWithHTTPMethod:URLString:parameters:uploadProgress:downloadProgress:success:failure:] 方法。

    AFHTTPRequestSerializer 部分使命全部完成。AFHTTPRequestSerializer 部分已经负责创建 NSURLRequest 并返回给 AFHTTPSessionManager 的 dataTaskWithHTTPMethod:URLString:parameters:uploadProgress:downloadProgress:success:failure: 第一部分。


    AFURLSessionManager 部分

    接续 AFHTTPSessionManager 的 dataTaskWithHTTPMethod:URLString:parameters:uploadProgress:downloadProgress:success:failure: 第二部分,AFURLSessionManager 部分的dataTaskWithRequest:uploadProgress:downloadProgress:completionHandler:函数主要任务就是创建一个 NSURLSessionDataTask 实例对象,并返回。

    dataTaskWithRequest:uploadProgress:downloadProgress:completionHandler: 函数解析

    - (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
                                   uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
                                 downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
                                completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject,  NSError * _Nullable error))completionHandler {
    
        __block NSURLSessionDataTask *dataTask = nil;
        url_session_manager_create_task_safely(^{
            dataTask = [self.session dataTaskWithRequest:request];
        });
    
        [self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];
    
        return dataTask;
    }
    

    整体逻辑

    1. 创建一个局域变量:NSURLSessionDataTask *dataTask,初始化为 nil 。
    2. 实现 url_session_manager_create_task_safely 函数的block。
    3. 在block的回调中,通过属性 session 调用 dataTaskWithRequest:方法。
    4. dataTaskWithRequest:方法会创建一个 NSURLSessionDataTask 实例对象,并返回。
    5. 调用 addDelegateForDataTask:uploadProgress:downloadProgress:completionHandler: 方法(参考 AFNetworking主线梳理(一))。

    分解说明

    1. url_session_manager_create_task_safely 函数
      • 函数的实现
      static void url_session_manager_create_task_safely(dispatch_block_t block) {
          if (NSFoundationVersionNumber < NSFoundationVersionNumber_With_Fixed_5871104061079552_bug) {
              // Fix of bug
              // Open Radar:http://openradar.appspot.com/radar?id=5871104061079552 (status: Fixed in iOS8)
              // Issue about:https://github.com/AFNetworking/AFNetworking/issues/2093
              dispatch_sync(url_session_manager_creation_queue(), block);
          } else {
              block();
          }
      }
      
      • 函数的主要逻辑:
        只有if else, 如果版本号小于iOS修复版本号,则需要把当前任务同步的添加到一个串行队列中,保证不发生并发。否则,直接调用block回调回去。
      • 函数的block回调:
        函数的block回调中的代码最终还是顺序执行的,无论是在if还是else里都是同步顺序执行。简单讲就是dataTask = [self.session dataTaskWithRequest:request];这句代码是顺序执行的,就执行顺序而言与在block外无异。
      • 函数的参数:
        url_session_manager_creation_queue()函数创建并返回一个类似单例管理的串行队列。
      static dispatch_queue_t url_session_manager_creation_queue() {
          static dispatch_queue_t af_url_session_manager_creation_queue;
          static dispatch_once_t onceToken;
          dispatch_once(&onceToken, ^{
              af_url_session_manager_creation_queue = dispatch_queue_create("com.alamofire.networking.session.manager.creation", DISPATCH_QUEUE_SERIAL);
          });
      
          return af_url_session_manager_creation_queue;
      }
      

    1. self.session
      • session是AFURLSessionManager的属性
      @property (readonly, nonatomic, strong) NSURLSession *session;
      
      • session属性是什么时候初始化的?
        在AFURLSessionManager初始化方法initWithSessionConfiguration:中进行的初始化,由AFN指定默认配置。具体请参考AFNetworking主线梳理(一)
      self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
      

    1. dataTaskWithRequest:request 是 NSURLSession 的方法,利用入参NSURLRequest创建一个 NSURLSessionDataTask 实例对象并返回。

    问:为什么创建dataTask的代码要在url_session_manager_create_task_safely函数的block回调中执行?
    答:为了解决 iOS 7 存在的bug。在 iOS 7 某些版本中如果并发创建NSURLSessionTask,会造成 NSURLSessionTask 的 taskIdentifier不唯一(有重复的),而AFN依赖 taskIdentifier 保存了很多信息,会造成混乱。

    问:为什么url_session_manager_creation_queue()函数用单例保存队列?
    个人猜测:因为该队列职责单一,无需多次重复创建新队列。网络请求多数情况下是高频的,所以多次创建不是一个好的选择。


    AFQueryStringPair 部分

    #pragma mark -
    
    @interface AFQueryStringPair : NSObject
    @property (readwrite, nonatomic, strong) id field;
    @property (readwrite, nonatomic, strong) id value;
    
    - (instancetype)initWithField:(id)field value:(id)value;
    
    - (NSString *)URLEncodedStringValue;
    @end
    
    @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
    

    AFPercentEscapedStringFromString()没什么特殊,就是把传进去的字符串做了百分号编码的处理,防止网络请求时出现不符合规则的字符。处理完成后,返回安全的字符串。

    相关文章

      网友评论

        本文标题:AFNetworking主线梳理(二)

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