美文网首页
IOS框架:AFNetworking(上)

IOS框架:AFNetworking(上)

作者: 时光啊混蛋_97boy | 来源:发表于2020-10-26 06:19 被阅读0次

    原创:知识点总结性文章
    创作不易,请珍惜,之后会持续更新,不断完善
    个人比较喜欢做笔记和写总结,毕竟好记性不如烂笔头哈哈,这些文章记录了我的IOS成长历程,希望能与大家一起进步
    温馨提示:由于简书不支持目录跳转,大家可通过command + F 输入目录标题后迅速寻找到你所需要的内容

    目录

    • 一、功能模块
    • 二、源码解析
      • 1、初始化方法
      • 2、AFHTTPSessionManager中GET请求方法的源码实现
        • a、request的拼接
        • b、生成dataTask
      • 3、AFURLSessionManager 中代理方法的实现
        • a、NSURLSessionDelegate的实现
        • b、NSURLSessionDelegate转发到AF自定义的deleagate
      • 4、AFURLResponseSerialization 如何解析数据
      • 5、AF整个流程和线程的关系
    • 三、AF2.x版本的核心实现
      • 1、Get请求
      • 2、AFHTTPRequestOperationManager的初始化方法
      • 3、Get方法的实现
      • 4、connection的代理方法
      • 5、通过setCompletionBlockWithSuccess方法接收responseData
      • 6、数据解析
      • 7、问题:为什么AF2.x需要一条常驻线程?
    • 四、AFNetworking的作用总结
    • 五、AFSecurityPolicy实现https认证需求
      • 1、NSURLSessionDelegate中的代理方法:didReceiveChallenge
      • 2、AFSecurityPolicy实现https认证
        • a、创建AFSecurityPolicy
        • b、evaluateServerTrust:方法的内部实现
      • 3、自签名的证书
    • 六、UIKit扩展与缓存实现
      • 1、AFNetworkActivityIndicatorManager :网络请求时状态栏的小菊花
        • a、使用方式
        • b、初始化
      • 2、UIImageView+AFNetworking :请求网络图片
        • a、图片下载类AFImageDownloader的初始化方法
        • b、图片下载类AFImageDownloader创建请求task的方法
        • c、图片缓存类AFAutoPurgingImageCache的初始化方法
        • d、图片缓存类AFAutoPurgingImageCache的核心方法
        • e、setImageWithURL 设置图片方法
        • f、总结请求图片、缓存、设置图片的流程
    • Demo
    • 参考文献

    作为一个iOS开发,也许你不知道NSUrlRequest、不知道NSUrlConnection、也不知道NSURLSession...(说不下去了...怎么会什么都不知道...)但是你一定知道AFNetworking。大多数人习惯了只要是请求网络都用AF,但是你真的知道AF做了什么吗?为什么我们不用原生的NSURLSession而选择AFNetworking? 本文将从源码的角度去分析AF的实际作用。

    一、功能模块

    • 网络通信模块(AFURLSessionManagerAFHTTPSessionManger)
    • 网络状态监听模块(Reachability)
    • 网络通信安全策略模块(Security)
    • 网络通信信息序列化/反序列化模块(Serialization)
    • 对于iOS UIKit库的扩展(UIKit)
    功能模块

    核心当然是网络通信模块AFURLSessionManager。AF3.x是基于NSURLSession来封装的。所以这个类围绕着NSURLSession做了一系列的封装。而其余的四个模块,均是为了配合网络通信或对已有UIKit的一个扩展工具包。

    这五个模块所对应的类的结构关系图如下所示:

    AF架构图

    其中AFHTTPSessionManager是继承于AFURLSessionManager的,我们一般做网络请求都是用这个类,但是它本身是没有做实事的,只是做了一些简单的封装,把请求逻辑分发给父类AFURLSessionManager或者其它类去做。


    二、源码解析

    简单的写个get请求:

    AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] init];
    
    [manager GET:@"http://localhost" parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
     
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        
    }];
    

    1、初始化方法

    可以看到,调用了初始化方法生成了一个manager,点进去看看初始化做了什么:

    + (instancetype)manager {
        return [[[self class] alloc] initWithBaseURL:nil];
    }
    
    - (instancetype)init {
        return [self initWithBaseURL:nil];
    }
    
    - (instancetype)initWithBaseURL:(NSURL *)url {
        return [self initWithBaseURL:url sessionConfiguration:nil];
    }
    
    - (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
        return [self initWithBaseURL:nil sessionConfiguration:configuration];
    }
    

    经过层层调用,终于到了- (instancetype)initWithBaseURL:(NSURL *)url sessionConfiguration:(NSURLSessionConfiguration *)configuration方法,看看它的实现:

    /*
     1.调用父类的方法
     2.给url添加“/”
     3.给requestSerializer、responseSerializer设置默认值
     */
    - (instancetype)initWithBaseURL:(NSURL *)url
               sessionConfiguration:(NSURLSessionConfiguration *)configuration
    {
        // 调用父类初始化方法
        self = [super initWithSessionConfiguration:configuration];
        if (!self) {
            return nil;
        }
    
        // Ensure terminal slash for baseURL path, so that NSURL +URLWithString:relativeToURL: works as expected
        // 为了确保NSURL +URLWithString:relativeToURL: works可以正确执行,在baseurlpath的最后添加‘/’
        // url有值且最后不包含‘/’,那么在url的末尾添加‘/’
        if ([[url path] length] > 0 && ![[url absoluteString] hasSuffix:@"/"]) {
            url = [url URLByAppendingPathComponent:@""];
        }
        self.baseURL = url;
        
        // 给requestSerializer、responseSerializer设置默认值
        self.requestSerializer = [AFHTTPRequestSerializer serializer];
        self.responseSerializer = [AFJSONResponseSerializer serializer];
    
        return self;
    }
    

    初始化方法都调用父类的初始化方法。父类也就是AF3.x最最核心的类AFURLSessionManager。除此之外,方法中把baseURL存了起来,还生成了一个请求序列对象和一个响应序列对象。

    那么看看父类AFURLSessionManager的初始化方法又作了什么:

    // 构造函数
    - (instancetype)init {
        return [self initWithSessionConfiguration:nil];
    }
    
    /*
     1.初始化一个session
     2.给manager的属性设置初始值
     */
    - (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
        self = [super init];
        if (!self) {
            return nil;
        }
    
        // 设置默认的configuration,配置我们的session
        if (!configuration) {
            configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
        }
    
        // 持有configuration
        self.sessionConfiguration = configuration;
    
        // 设置为delegate的操作队列并发的线程数量1,也就是串行队列
        self.operationQueue = [[NSOperationQueue alloc] init];
        self.operationQueue.maxConcurrentOperationCount = 1;
    
        /*
         * 如果完成后需要做复杂(耗时)的处理,可以选择异步队列
         * 如果完成后直接更新UI,可以选择主队列 [NSOperationQueue mainQueue]
         */
        self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
    
        // 默认为json解析
        self.responseSerializer = [AFJSONResponseSerializer serializer];
    
        // 设置默认证书 无条件信任证书https认证
        self.securityPolicy = [AFSecurityPolicy defaultPolicy];
    
    #if !TARGET_OS_WATCH
        // 网络状态监听
        self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];
    #endif
    
        // delegate= value taskid = key
        // 设置存储NSURL task与AFURLSessionManagerTaskDelegate的词典
        // 每一个task都会被匹配一个AFURLSessionManagerTaskDelegate来做task的delegate,进行事件处理
        self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];
    
        // 使用NSLock确保词典在多线程访问时的线程安全
        self.lock = [[NSLock alloc] init];
        self.lock.name = AFURLSessionManagerLockName;
    
        
        // 置空task关联的代理
        // 异步的获取当前session的所有未完成的task
        // 其实讲道理来说在初始化中调用这个方法应该里面一个task都不会有,打断点去看,也确实如此,里面的数组都是空的
        // 当后台任务重新回来初始化session,可能就会有先前的请求任务,导致程序的crash
        [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
            for (NSURLSessionDataTask *task in dataTasks) {
                [self addDelegateForDataTask:task uploadProgress:nil downloadProgress:nil completionHandler:nil];
            }
    
            for (NSURLSessionUploadTask *uploadTask in uploadTasks) {
                [self addDelegateForUploadTask:uploadTask progress:nil completionHandler:nil];
            }
    
            for (NSURLSessionDownloadTask *downloadTask in downloadTasks) {
                [self addDelegateForDownloadTask:downloadTask progress:nil destination:nil completionHandler:nil];
            }
        }];
    
        return self;
    }
    

    2、AFHTTPSessionManager中GET请求方法的源码实现

    方法走到类AFHTTPSessionManager中来,调用父类,也就是我们整个AF3.x的核心类AFURLSessionManager的方法,生成了一个系统的NSURLSessionDataTask实例,并且开始网络请求。

    - (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
    {
    
       // 返回一个task,然后开始网络请求
       NSURLSessionDataTask *dataTask = [self dataTaskWithHTTPMethod:@"GET"
                                                           URLString:URLString
                                                          parameters:parameters
                                                      uploadProgress:nil
                                                    downloadProgress:downloadProgress
                                                             success:success
                                                             failure:failure];
    
       // 开始网络请求
       [dataTask resume];
    
       return dataTask;
    }
    

    进入- dataTaskWithHTTPMethod:方法中,看下具体实现:

    //1.生成request
    //2.通过request生成task
    - (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
    {
        // 处理request构建产生的错误
        NSError *serializationError = nil;
    
        // 1、先调用AFHTTPRequestSerializer的requestWithMethod函数构建request
        // relativeToURL表示将URLString拼接到baseURL后面
        NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
        
        // 如果解析错误,直接返回
        if (serializationError) {
            if (failure) {
                // diagnostic:诊断的,常用push pop搭配,来忽略一些编译器的警告
                // 这里是用来忽略:?带来的警告
    #pragma clang diagnostic push
    #pragma clang diagnostic ignored "-Wgnu"
                // completionQueue是我们自定义的,是一个GCD的Queue
                // 如果设置了那么从这个Queue中回调结果,不存在则从主队列回调
                dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
                    failure(nil, serializationError);
                });
    #pragma clang diagnostic pop
            }
    
            return nil;
        }
    
        // 2、此时的request已经将参数拼接在url后面,根据request来生成dataTask
        __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);
                }
            }
        }];
    
        return dataTask;
    }
    

    该方法做了两件事:
    1、先调用AFHTTPRequestSerializerrequestWithMethod函数构建request
    2、此时的request已经将参数拼接在url后面,根据request来生成dataTask
    所以分别从requestdataTask的生成来探究。

    a、生成request

    继续深入到- requestSerializer:方法中,探究如何拼接成成需要的request

    /**
     * 使用指定的HTTP method和URLString来构建一个NSMutableURLRequest对象实例
     * 如果method是GET、HEAD、DELETE,那parameter将会被用来构建一个基于url编码的查询字符串(query url),并且这个字符串会直接加到request的url后面。对于其他的Method,比如POST/PUT,它们会根
    据parameterEncoding属性进行编码,而后加到request的http body上。
     * @param method request的HTTP methodt,比如 `GET`, `POST`, `PUT`, or `DELETE`. 该参数不能为空
     * @param URLString 用来创建request的URL
     * @param parameters 既可以对method为GET的request设置一个查询字符串(query string),也可以设置到request的HTTP body上
     * @param error 构建request时发生的错误
     * @return  一个NSMutableURLRequest的对象
     */
    - (NSMutableURLRequest *)requestWithMethod:(NSString *)method
                                     URLString:(NSString *)URLString
                                    parameters:(id)parameters
                                         error:(NSError *__autoreleasing *)error
    {
        // 断言,debug模式下,如果缺少改参数为nil,crash并直接打印出来
        NSParameterAssert(method);
        NSParameterAssert(URLString);
    
        // 我们传进来的是一个字符串,在这里它帮你转成url
        NSURL *url = [NSURL URLWithString:URLString];
    
        NSParameterAssert(url);
    
        NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url];
        // 设置请求方式(get、post、put.....)
        mutableRequest.HTTPMethod = method;
    
        // 将request的各种属性遍历
        for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
            // 将观察到的发生变化的属性,添加到NSMutableURLRequest(如:timeout)
            if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) {
                // 通过kvc动态的给mutableRequest添加value
                [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
            }
        }
    
        // 将传入的parameters进行编码,拼接到url后并返回 count=5&start=1
        mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];
        NSLog(@"request'''''''''%@",mutableRequest);
    
        return mutableRequest;
    }
    

    其中AFHTTPRequestSerializerObservedKeyPaths()是一个c函数,返回一个数组,我们来看看这个函数:

    static NSArray * AFHTTPRequestSerializerObservedKeyPaths() {
        static NSArray *_AFHTTPRequestSerializerObservedKeyPaths = nil;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            // 此处需要observer的keypath为allowsCellularAccess、cachePolicy、HTTPShouldHandleCookies、HTTPShouldUsePipelining、networkServiceType、timeoutInterval
            _AFHTTPRequestSerializerObservedKeyPaths =
            @[NSStringFromSelector(@selector(allowsCellularAccess)), NSStringFromSelector(@selector(cachePolicy)), NSStringFromSelector(@selector(HTTPShouldHandleCookies)), NSStringFromSelector(@selector(HTTPShouldUsePipelining)), NSStringFromSelector(@selector(networkServiceType)), NSStringFromSelector(@selector(timeoutInterval))];
        });
        // 这个函数就是封装了一些属性的名字,这些都是NSUrlRequest的属性
        return _AFHTTPRequestSerializerObservedKeyPaths;
    }
    

    该函数返回一个方法名的数组。定义了一个static的方法,表示该方法只能在本文件中使用。

    • allowsCellularAccess:是否允许使用设备的蜂窝移动网络来创建request,默认为允许。
    • cachePolicy:创建的request所使用的缓存策略,默认使用NSURLRequestUseProtocolCachePolicy,该策略表示
      如果缓存不存在,直接从服务端获取。如果缓存存在,会根据response中的Cache-Control字段判断
      下一步操作,如: Cache-Control字段为must-revalidata,则询问服务端该数据是否有更新,无更新的话
      直接返回给用户缓存数据,若已更新,则请求服务端。
    • HTTPShouldHandleCookies:如果设置HTTPShouldHandleCookiesYES,就处理存储在NSHTTPCookieStore中的cookiesHTTPShouldHandleCookies表示是否应该给request设置cookie并随request一起发送出去。
    • HTTPShouldUsePipelining:表示receiver(理解为iOS客户端)的下一个信息是否必须等到上一个请求回复才能发送。如果为YES表示可以,NO表示必须等receiver收到先前的回复才能发送下个信息。
    • networkServiceType:设定requestnetwork service类型,默认是NSURLNetworkServiceTypeDefault。这个network service是为了告诉系统网络层这个request使用的目的,比如NSURLNetworkServiceTypeVoIP表示的就这个request是用来请求网际协议通话技术(Voice over IP)。系统能根据提供的信息来优化网络处理,从而优化电池寿命,网络性能等等,客户端基本不使用。
    • timeoutInterval:超时机制,默认60秒

    再进入self.mutableObservedChangedKeyPaths属性探索下:

    //某个request需要观察的属性集合
    @property (readwrite, nonatomic, strong) NSMutableSet *mutableObservedChangedKeyPaths;
    

    -init方法里对这个集合进行了初始化,并且对当前类的和NSUrlRequest相关的那些属性添加了KVO监听:

    - (instancetype)init {
        .......
        //每次都会重置变化
        self.mutableObservedChangedKeyPaths = [NSMutableSet set];
        for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
            if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
                //自己给自己的方法添加观察者,就是request各种属性的set方法
                [self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
            }
        }
        .......
    }
    

    对应的KVO触发的方法:

    #pragma mark - NSKeyValueObserving
    /**
     如果kvo的触发机制是默认出发。则返回true,否则返回false。在这里,只要是`AFHTTPRequestSerializerObservedKeyPaths`里面的属性,我们都取消自动出发kvo机制,使用手动触发。
     为什么手动,我猜应该是为了在监听这些属性时可以用于某些特殊的操作,比如测试这些属性变化是否崩溃等。
     @param key kvo的key
     @return bool值
     */
    + (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) {
            // 当观察到这些set方法被调用了,而且不为Null就会添加到集合里,否则移除
            if ([change[NSKeyValueChangeNewKey] isEqual:[NSNull null]]) {
                [self.mutableObservedChangedKeyPaths removeObject:keyPath];
            } else {
                [self.mutableObservedChangedKeyPaths addObject:keyPath];
            }
        }
    }
    

    接下来通过kvc动态的给mutableRequest添加valuekeyPath为观察到的发生变化的属性:

    [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
    

    最后将传入的parameters进行编码,拼接到url后设置到request中去 :

    mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy];
    

    其对应的实现方法- requestBySerializingRequest:方法为:

    #pragma mark - AFURLRequestSerialization
    - (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request
                                   withParameters:(id)parameters
                                            error:(NSError *__autoreleasing *)error
    {
        NSParameterAssert(request);
    
        NSMutableURLRequest *mutableRequest = [request mutableCopy];
    
        // 1、从self.HTTPRequestHeaders里去遍历拿到设置的参数,如果request此项无值则给mutableRequest.headfiled赋值
        /*
         1.请求行(状态行):get,url,http协议1.1
         2.请求头:conttent-type,accept-language
         3.请求体:get/post get参数拼接在url后面 post数据放在body
         */
        [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) {
            if (![request valueForHTTPHeaderField:field]) {
                [mutableRequest setValue:value forHTTPHeaderField:field];
            }
        }];
    
        // 2、将我们传入的字典转成字符串,具体转码方式,我们可以使用自定义的方式,也可以用AF默认的转码方式
        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 {
                //默认解析方式,dictionary  count=5&start=1
                switch (self.queryStringSerializationStyle) {
                    case AFHTTPRequestQueryStringDefaultStyle:
                        // 将parameters传入这个c函数
                        query = AFQueryStringFromParameters(parameters);
                        break;
                }
            }
        }
        // count=5&start=1
        NSLog(@"query:%@",query);
        
        // 3、最后判断该request中是否包含了GET、HEAD、DELETE(都包含在HTTPMethodsEncodingParametersInURI)
        // 因为这几个method的query是拼接到url后面的
        // 而POST、PUT是把query拼接到http body中的
        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 {// post put请求
            // #2864: an empty string is a valid x-www-form-urlencoded payload
            if (!query) {
                query = @"";
            }
            
            //函数会判断request的Content-Type是否设置了,如果没有,就默认设置为application/x-www-form-urlencoded
            //application/x-www-form-urlencoded是常用的表单发包方式,普通的表单提交,或者js发包,默认都是通过这种方式
            if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
                [mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
            }
            //设置请求体
            [mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
        }
    
        return mutableRequest;
    }
    

    自定义的方式想怎么去解析由你自己来决定,而默认的方式:

    NSString * AFQueryStringFromParameters(NSDictionary *parameters) {
        NSMutableArray *mutablePairs = [NSMutableArray array];//一对
        //把参数传给AFQueryStringPairsFromDictionary,AFQueryStringPair数据处理类
        for (AFQueryStringPair *pair in AFQueryStringPairsFromDictionary(parameters)) {
            //百分号编码
            [mutablePairs addObject:[pair URLEncodedStringValue]];
        }
        //拆分数组返回参数字符串
        return [mutablePairs componentsJoinedByString:@"&"];
    }
    

    这个方法�,是遍历数组中的AFQueryStringPair,然后以&符号拼接。

    AFQueryStringPair是一个数据处理类,只有两个属性:fieldvalue;一个方法:- URLEncodedStringValue。它的作用就是上面我们说的,以key=value的形式,用URL Encode编码,拼接成字符串。

    @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
    
    // 百分号编码 count=5 ASCII uinicode
    - (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])];
        }
    }
    

    至于AFQueryStringPairsFromDictionary,它只是起过渡作用,往下继续调用:

    NSArray * AFQueryStringPairsFromDictionary(NSDictionary *dictionary) {
        return AFQueryStringPairsFromKeyAndValue(nil, dictionary);
    }
    

    AFQueryStringPairsFromKeyAndValue中使用了递归,会对value的类型进行判断,有NSDictionaryNSArrayNSSet类型。

    不过有人就会问了,在AFQueryStringPairsFromDictionary中给AFQueryStringPairsFromKeyAndValue函数传入的value不是NSDictionary嘛?还要判断那么多类型干啥?对,问得很好,这就是AFQueryStringPairsFromKeyAndValue的核心----递归调用并解析,你不能保证NSDictionaryvalue中存放的不是一个NSArrayNSSet

    既然是递归,那么就要有结束递归的情况,比如解析到最后,对应value是一个NSString,那么就得调用函数中最后的else语句,

    NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value) {//key=count value = 5
        NSMutableArray *mutableQueryStringComponents = [NSMutableArray array];
    
        // 1、根据需要排列的对象的description来进行升序排列,并且selector使用的是compare:
        // 因为对象的description返回的是NSString,所以此处compare:使用的是NSString的compare函数
        // 比如:@[@"foo", @"bar", @"bae"] ----> @[@"bae", @"bar",@"foo"]
        NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"description" ascending:YES selector:@selector(compare:)];
    
        // 2、判断vaLue是什么类型的,然后去递归调用自己,直到解析的是除了array dic set以外的元素,然后把得到的参数数组返回。
        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];//nestedkey=count nestedvalue=5
                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 {//既然是递归,那么就要有结束递归的情况,比如解析到最后,对应value是一个NSString,那么就得调用函数中最后的else语句,
            //AFQueryStringPair数据处理类,mutableQueryStringComponents中的元素类型是AFQueryStringPair
            [mutableQueryStringComponents addObject:[[AFQueryStringPair alloc] initWithField:key value:value]];
        }
    
        return mutableQueryStringComponents;
    }
    

    举个例子演示下整个转码过程:

    @{ 
         @"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
    

    目的是将原来的容器类型的参数变成字符串类型。

    紧接着该方法还根据request中请求类型,来判断参数字符串应该如何设置到request中去。如果是GETHEADDELETE,则把参数quey拼接到url后面。如果是POSTPUT,则把query拼接到http body中:

        if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) {// GET、HEAD、DELETE
            if (query && query.length > 0) {
                mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]];
            }
        } else {// post put请求
            // #2864: an empty string is a valid x-www-form-urlencoded payload
            if (!query) {
                query = @"";
            }
            
            //函数会判断request的Content-Type是否设置了,如果没有,就默认设置为application/x-www-form-urlencoded
            //application/x-www-form-urlencoded是常用的表单发包方式,普通的表单提交,或者js发包,默认都是通过这种方式
            if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) {
                [mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
            }
            //设置请求体
            [mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]];
        }
    

    最后,返回生成的request即可。

    b、生成dataTask

    生成的方法在核心类AFURLSessionManger中:

    - (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 {
        // iOS 8.0以下版本中会并发地创建多个task对象,而同步有没有做好,导致taskIdentifiers 不唯一
        // 1、为了解决这个bug,调用一个串行队列来创建dataTask
        __block NSURLSessionDataTask *dataTask = nil;
        url_session_manager_create_task_safely(^{
            // 系统原生的方法,使用session来创建一个NSURLSessionDataTask对象
            dataTask = [self.session dataTaskWithRequest:request];
            
        });
        
        // 2、为什么要给task添加代理呢?进去看下
        [self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];
    
        return dataTask;
    }
    

    调用了一个url_session_manager_create_task_safely()函数,传了一个Block进去,Block里就是iOS原生生成dataTask的方法,看看url_session_manager_create_task_safely()的实现:

    static void url_session_manager_create_task_safely(dispatch_block_t block) {
        NSLog(@"NSFoundationVersionNumber = %f",NSFoundationVersionNumber);
        if (NSFoundationVersionNumber < NSFoundationVersionNumber_With_Fixed_5871104061079552_bug) {
            // 为什么用sync,因为是想要主线程等在这,等执行完再返回,因为必须执行完dataTask才有数据,传值才有意义。
            // 为什么要用串行队列,因为这块是为了防止ios8以下NSURLSession内部的dataTaskWithRequest是并发创建的
            // 这样会导致taskIdentifiers这个属性值不唯一,因为后续要用taskIdentifiers来作为Key对应delegate
            dispatch_sync(url_session_manager_creation_queue(), block);//同步
        } else {
            block();
        }
    }
    
    // 创建一个用于创建task的串行队列
    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;
    }
    

    上面的疑问解决了,那么为什么要给task添加代理呢?也点进去看下:

    - (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask
                    uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
                  downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
                 completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
    {
        AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] init];
        delegate.manager = self;
        delegate.completionHandler = completionHandler;
        dataTask.taskDescription = self.taskDescriptionForSessionTasks;
        [self setDelegate:delegate forTask:dataTask];
        delegate.uploadProgressBlock = uploadProgressBlock;
        delegate.downloadProgressBlock = downloadProgressBlock;
    }
    

    首先注意- addDelegateForDataTask:这个方法并不是AFURLSessionManagerTaskDelegate的方法,而只是AFURLSessionManager的一个方法。 然后因为这个方法内部实现有点儿绕,所以一步步分析下:

        // 初始化delegate,请求传来的参数,都赋值给这个AF的代理了
        AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] init];
        // 代理把AFURLSessionManager这个类作为属性了
        delegate.manager = self;
    
    //weak防止循环引用(manager持有task,task和delegate是绑定的,相当于manager是持有delegate的)
    @property (nonatomic, weak) AFURLSessionManager *manager;
    

    taskDescription是自行设置的,区分是否是当前的session创建的。其在用来发送开始和挂起通知的时候会用到,就是用这个值来Post通知。

        dataTask.taskDescription = self.taskDescriptionForSessionTasks;
    

    函数字面意思是将一个sessiontask和一个AFURLSessionManagerTaskDelegate类型的delegate变量绑在一起,而这个绑在一起的工作是由我们的AFURLSessionManager所做。至于绑定的过程,就是以该session tasktaskIdentifierkeydelegatevalue,需要确保task唯一,赋值给mutableTaskDelegatesKeyedByTaskIdentifier这个NSMutableDictionary类型的变量。知道了这两者是关联在一起的话,马上就会产生另外的问题 —— 为什么要关联以及怎么关联在一起?

    [self setDelegate:delegate forTask:dataTask];
    
    //为task设置关联的delegate
    - (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
                forTask:(NSURLSessionTask *)task
    {
        //task和delegate都不能为空
        NSParameterAssert(task);
        NSParameterAssert(delegate);
    
        //加锁确保中间代码块是原子操作,线程安全
        [self.lock lock];
        //将delegate存入字典,以taskid作为key,说明每个task都有各自的代理
        self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
        //设置两个NSProgress的变量 - uploadProgress和downloadProgress,给session task添加了两个KVO监听事件
        [delegate setupProgressForTask:task];
        //添加task开始和暂停的通知
        [self addNotificationObserverForTask:task];
        [self.lock unlock];
    }
    

    进入到[delegate setupProgressForTask:task];探索下:

    #pragma mark - NSProgress Tracking
    
    - (void)setupProgressForTask:(NSURLSessionTask *)task {
        __weak __typeof__(task) weakTask = task;
    
        //拿到上传下载期望的数据大小
        self.uploadProgress.totalUnitCount = task.countOfBytesExpectedToSend;
        self.downloadProgress.totalUnitCount = task.countOfBytesExpectedToReceive;
        
        //设置这两个NSProgress对应的cancel、pause和resume这三个状态,正好对应session task的cancel、suspend和resume三个状态
        //所以可以将上传与下载进度和任务绑定在一起,直接cancel suspend resume进度条,可以cancel、suspend和resume任务
        [self.uploadProgress setCancellable:YES];
        [self.uploadProgress setCancellationHandler:^{
            __typeof__(weakTask) strongTask = weakTask;
            [strongTask cancel];
        }];
        [self.uploadProgress setPausable:YES];
        [self.uploadProgress setPausingHandler:^{
            __typeof__(weakTask) strongTask = weakTask;
            [strongTask suspend];
        }];
        if ([self.uploadProgress respondsToSelector:@selector(setResumingHandler:)]) {
            [self.uploadProgress setResumingHandler:^{
                __typeof__(weakTask) strongTask = weakTask;
                [strongTask resume];
            }];
        }
    
        [self.downloadProgress setCancellable:YES];
        [self.downloadProgress setCancellationHandler:^{
            __typeof__(weakTask) strongTask = weakTask;
            [strongTask cancel];
        }];
        [self.downloadProgress setPausable:YES];
        [self.downloadProgress setPausingHandler:^{
            __typeof__(weakTask) strongTask = weakTask;
            [strongTask suspend];
        }];
    
        if ([self.downloadProgress respondsToSelector:@selector(setResumingHandler:)]) {
            [self.downloadProgress setResumingHandler:^{
                __typeof__(weakTask) strongTask = weakTask;
                [strongTask resume];
            }];
        }
    
    //给task和progress添加kvo
        //观察task的这些属性
        [task addObserver:self
               forKeyPath:NSStringFromSelector(@selector(countOfBytesReceived))
                  options:NSKeyValueObservingOptionNew
                  context:NULL];
        [task addObserver:self
               forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))
                  options:NSKeyValueObservingOptionNew
                  context:NULL];
    
        [task addObserver:self
               forKeyPath:NSStringFromSelector(@selector(countOfBytesSent))
                  options:NSKeyValueObservingOptionNew
                  context:NULL];
        [task addObserver:self
               forKeyPath:NSStringFromSelector(@selector(countOfBytesExpectedToSend))
                  options:NSKeyValueObservingOptionNew
                  context:NULL];
    
        //观察progress这两个属性
        //fractionCompleted:任务已经完成的比例,取值为0~1
        [self.downloadProgress addObserver:self
                                forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
                                   options:NSKeyValueObservingOptionNew
                                   context:NULL];
        [self.uploadProgress addObserver:self
                              forKeyPath:NSStringFromSelector(@selector(fractionCompleted))
                                 options:NSKeyValueObservingOptionNew
                                 context:NULL];
    }
    

    而对应KVO的回调方法为:

    //KVO回调方法
    - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
        //是task
        if ([object isKindOfClass:[NSURLSessionTask class]] || [object isKindOfClass:[NSURLSessionDownloadTask class]]) {
            //给进度条赋新值
            if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesReceived))]) {
                self.downloadProgress.completedUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];
            } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToReceive))]) {
                self.downloadProgress.totalUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];
            } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesSent))]) {
                self.uploadProgress.completedUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];
            } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(countOfBytesExpectedToSend))]) {
                self.uploadProgress.totalUnitCount = [change[NSKeyValueChangeNewKey] longLongValue];
            }
        }
        //上面的赋新值会触发这两个调用block的回调,用户可以拿到进度
        //根据NSProgress的状态做用户自定义的行为,比如需要更新UI进度条的状态之类的
        else if ([object isEqual:self.downloadProgress]) {
            if (self.downloadProgressBlock) {
                self.downloadProgressBlock(object);
            }
        }
        else if ([object isEqual:self.uploadProgress]) {
            if (self.uploadProgressBlock) {
                self.uploadProgressBlock(object);
            }
        }
    }
    

    如果task触发KVO,则给progress进度赋值。又因为progress进度赋值了,所以也会触发progressKVO,从而也会调用到这里,然后去执行我们传进来的downloadProgressBlockuploadProgressBlock。说白了主要的作用就是为了让进度实时的传递,不过设计很巧妙。

    setProgress和这个KVO监听,都是在我们AF自定义的delegate内的。有一个task就会有一个delegate,所以每个task都会分别在各自的AF代理内去监听这些属性。

    跳出深度探索回到主线剧情,最后设置AF delegate的上传进度块,下载进度块。

        delegate.uploadProgressBlock = uploadProgressBlock;
        delegate.downloadProgressBlock = downloadProgressBlock;
    

    这样就了解了- addDelegateForDataTask:这个方法,也完成了生成dataTask的探索任务。感觉就是不断地深入深入,可惜我水平太浅,只知道做了什么,却还无法捕获到设计思想。


    3、AFURLSessionManager 中代理方法的实现

    a、NSURLSessionDelegate的实现

    第一部分中,探索父类AFURLSessionManager的初始化方法时,有句代码把AFUrlSessionManager作为了所有的taskdelegate

    - (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
        .......
        /*
         * 如果完成后需要做复杂(耗时)的处理,可以选择异步队列
         * 如果完成后直接更新UI,可以选择主队列 [NSOperationQueue mainQueue]
         */
        self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
        .......
    }
    

    这样当我们请求网络的时候,AFUrlSessionManager实现的这一大堆NSUrlSession相关的代理开始调用了:

    NSUrlSession的代理

    其中有3条重要的,它们转发到了AFURLSessionManagerTaskDelegate即AF自定义的代理,负责把每个task对应的数据回调出去:

    负责把每个task对应的数据回调出去
    @interface AFURLSessionManager : NSObject <NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDataDelegate, NSURLSessionDownloadDelegate, NSSecureCoding, NSCopying>
    
    @protocol NSURLSessionDelegate <NSObject>
    @protocol NSURLSessionTaskDelegate <NSURLSessionDelegate>
    @protocol NSURLSessionDataDelegate <NSURLSessionTaskDelegate>
    @protocol NSURLSessionDownloadDelegate <NSURLSessionTaskDelegate>
    @protocol NSURLSessionStreamDelegate <NSURLSessionTaskDelegate>
    

    可以看到这些代理都是继承关系,而在NSURLSession实现中,只要设置了这个代理,它会去判断这些所有的代理,是否respondsToSelector这些代理中的方法,如果响应了就会去调用。

    AFURLSessionManager重写了respondsToSelector方法。在里面复写了selector的方法,这几个方法是在本类有实现的,但是如果外面的Block没赋值的话,则返回NO,相当于没有实现!这样如果没实现这些我们自定义的Block也不会去回调这些代理。因为本身某些代理,只执行了这些自定义的Block,如果Block都没有赋值,那我们调用代理也没有任何意义。

    //复写了selector的方法,这几个方法是在本类有实现的,但是如果外面的Block没赋值的话,则返回NO,相当于没有实现!
    - (BOOL)respondsToSelector:(SEL)selector {
        if (selector == @selector(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:)) {
            return self.taskWillPerformHTTPRedirection != nil;
        } else if (selector == @selector(URLSession:dataTask:didReceiveResponse:completionHandler:)) {
            return self.dataTaskDidReceiveResponse != nil;
        } else if (selector == @selector(URLSession:dataTask:willCacheResponse:completionHandler:)) {
            return self.dataTaskWillCacheResponse != nil;
        } else if (selector == @selector(URLSessionDidFinishEventsForBackgroundURLSession:)) {
            return self.didFinishEventsForBackgroundURLSession != nil;
        }
    
        return [[self class] instancesRespondToSelector:selector];
    }
    

    AFURLSessionManager的自定义Block包括:

    //block的命名
    typedef void (^AFURLSessionDataTaskDidBecomeDownloadTaskBlock)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLSessionDownloadTask *downloadTask);
    typedef void (^AFURLSessionDataTaskDidReceiveDataBlock)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSData *data);
    .......
    
    @property (readwrite, nonatomic, copy) AFURLSessionDidBecomeInvalidBlock sessionDidBecomeInvalid;
    @property (readwrite, nonatomic, copy) AFURLSessionDidReceiveAuthenticationChallengeBlock sessionDidReceiveAuthenticationChallenge;
    @property (readwrite, nonatomic, copy) AFURLSessionDidFinishEventsForBackgroundURLSessionBlock didFinishEventsForBackgroundURLSession;
    @property (readwrite, nonatomic, copy) AFURLSessionTaskWillPerformHTTPRedirectionBlock taskWillPerformHTTPRedirection;
    @property (readwrite, nonatomic, copy) AFURLSessionTaskDidReceiveAuthenticationChallengeBlock taskDidReceiveAuthenticationChallenge;
    @property (readwrite, nonatomic, copy) AFURLSessionTaskNeedNewBodyStreamBlock taskNeedNewBodyStream;
    @property (readwrite, nonatomic, copy) AFURLSessionTaskDidSendBodyDataBlock taskDidSendBodyData;
    @property (readwrite, nonatomic, copy) AFURLSessionTaskDidCompleteBlock taskDidComplete;
    @property (readwrite, nonatomic, copy) AFURLSessionDataTaskDidReceiveResponseBlock dataTaskDidReceiveResponse;
    @property (readwrite, nonatomic, copy) AFURLSessionDataTaskDidBecomeDownloadTaskBlock dataTaskDidBecomeDownloadTask;
    @property (readwrite, nonatomic, copy) AFURLSessionDataTaskDidReceiveDataBlock dataTaskDidReceiveData;
    @property (readwrite, nonatomic, copy) AFURLSessionDataTaskWillCacheResponseBlock dataTaskWillCacheResponse;
    @property (readwrite, nonatomic, copy) AFURLSessionDownloadTaskDidFinishDownloadingBlock downloadTaskDidFinishDownloading;
    @property (readwrite, nonatomic, copy) AFURLSessionDownloadTaskDidWriteDataBlock downloadTaskDidWriteData;
    @property (readwrite, nonatomic, copy) AFURLSessionDownloadTaskDidResumeBlock downloadTaskDidResume;
    
    // block对应的set方法
    - (void)setDataTaskDidBecomeDownloadTaskBlock:(void (^)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLSessionDownloadTask *downloadTask))block {
        self.dataTaskDidBecomeDownloadTask = block;
    }
    
    - (void)setDataTaskDidReceiveDataBlock:(void (^)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSData *data))block {
        self.dataTaskDidReceiveData = block;
    }
    

    作者用@property把这个些Block属性在.m文件中声明,然后复写了set方法,接着在.h中去声明这些set方法:

    - (void)setDataTaskDidBecomeDownloadTaskBlock:(nullable void (^)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSURLSessionDownloadTask *downloadTask))block;
    
    - (void)setDataTaskDidReceiveDataBlock:(nullable void (^)(NSURLSession *session, NSURLSessionDataTask *dataTask, NSData *data))block;
    .......
    

    为什么要绕这么一大圈呢?前辈说是为了使用人员使用起来方便,调用set方法去设置这些Block,能很清晰的看到Block的各个参数与返回值,精髓的编程思想无处不体现......唉,估计我这条咸鱼就只能看看别人设计好的代码了,叫我自己去如此精细的创造太难了,果然我就是个平凡学校里的平凡人物,跟这些天纵之才差距太大了。

    接下来依次讲述这些代理方法做了什么:
    ❶ NSURLSessionDelegate

    代理一:didBecomeInvalidWithError
    当前session失效,会调用。 如果你使用finishTasksAndInvalidate函数使该session失效,那么session首先会先完成最后一个task,然后再调用URLSession:didBecomeInvalidWithError:代理方法。如果你调用invalidateAndCancel方法来使session失效,那么该session会立即调用这个代理方法。

    - (void)URLSession:(NSURLSession *)session
    didBecomeInvalidWithError:(NSError *)error
    {
        if (self.sessionDidBecomeInvalid) {
            self.sessionDidBecomeInvalid(session, error);
        }
        // 不过源代码中没有举例如何使用这个Notification,所以需要用户自己定义,比如结束进度条的显示啊
        [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDidInvalidateNotification object:session];
    }
    

    代理2:didReceiveChallenge
    web服务器接收到客户端请求时,有时候需要先验证客户端是否为正常用户,再决定是够返回真实数据。这种情况称之为服务端要求客户端接收挑战(NSURLAuthenticationChallenge *challenge)。接收到挑战后,客户端要根据服务端传来的challenge来生成completionHandler所需的NSURLSessionAuthChallengeDisposition dispositionNSURLCredential *credentialdisposition指定应对这个挑战的方法,而credential是客户端生成的挑战证书,注意只有challenge中认证方法为NSURLAuthenticationMethodServerTrust的时候,才需要生成挑战证书)。最后调用completionHandler回应服务器端的挑战。

    //收到服务端的challenge,例如https需要验证证书等 ats开启
    - (void)URLSession:(NSURLSession *)session
    didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
     completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
    {
         /*
         NSURLSessionAuthChallengeUseCredential:使用指定的证书
         NSURLSessionAuthChallengePerformDefaultHandling:默认方式处理
         NSURLSessionAuthChallengeCancelAuthenticationChallenge:取消挑战
         NSURLSessionAuthChallengeRejectProtectionSpace:拒绝此挑战,并尝试下一个验证保护空间;忽略证书参数
         */
    
        //1. 挑战处理类型为默认
        NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
    
        __block NSURLCredential *credential = nil;//证书
    
        
        if (self.sessionDidReceiveAuthenticationChallenge) {//2. 自定义方法,用来如何应对服务器端的认证挑战
            disposition = self.sessionDidReceiveAuthenticationChallenge(session, challenge, &credential);
        } else {// 3. 倘若没有自定义Block
            //  判断接收服务器挑战的方法是否是信任证书
            if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
                // 4. 只需要验证服务端证书是否安全(即https的单向认证,这是AF默认处理的认证方式,其他的认证方式,只能由我们自定义Block的实现
                if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
                     //信任评估通过,就从受保护空间里面拿出证书,回调给服务器,告诉服务器,我信任你,你给我发送数据吧
                    credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
                   // 5. 确定挑战的方式
                    if (credential) {
                        //证书挑战
                        disposition = NSURLSessionAuthChallengeUseCredential;
                    } else {
                        //默认挑战
                        disposition = NSURLSessionAuthChallengePerformDefaultHandling;
                    }
                } else {
                     //取消挑战
                    disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
                }
            } else {
                //默认挑战方式
                disposition = NSURLSessionAuthChallengePerformDefaultHandling;
            }
        }
     //完成挑战
        // 3.将信任凭证发送给服务端
        if (completionHandler) {
            completionHandler(disposition, credential);
        }
    }
    

    代理3:URLSessionDidFinishEventsForBackgroundURLSession
    在iOS中,当一个后台传输任务完成或者后台传输时需要证书,而此时你的app正在后台挂起,那么你的app在后台会自动重新启动运行,并且这个app的UIApplicationDelegate会发送一个application:handleEventsForBackgroundURLSession:completionHandler:消息。该消息包含了对应后台的sessionidentifier,而且这个消息会导致你的app启动。你的app随后应该先存储completion handler,然后再使用相同的identifier创建一个background configuration,并根据这个background configuration创建一个新的session。这个新创建的session会自动与后台任务重新关联在一起。

    当你的app获取了一个URLSessionDidFinishEventsForBackgroundURLSession:消息,这就意味着之前这个session中已经入队的所有消息都转发出去了,这时候再调用先前存取的completion handler是安全的。

    //当session中所有已经入队的消息被发送出去后,会调用该代理方法
    - (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
        if (self.didFinishEventsForBackgroundURLSession) {
            dispatch_async(dispatch_get_main_queue(), ^{
                // 意味着background session中的消息已经全部发送出去了,返回到主进程执行自定义的函数
    
                self.didFinishEventsForBackgroundURLSession(session);
            });
        }
    }
    

    ❷ NSURLSessionTaskDelegate

    代理1:willPerformHTTPRedirection
    客户端告知服务器端需要HTTP重定向。此方法只会在default session或者ephemeral session中调用,而在background session中,session task会自动重定向。

    • +defaultSessionConfiguration:具有相同的共享 NSHTTPCookieStorage,共享 NSURLCache和共享NSURLCredentialStorage
    • +ephemeralSessionConfiguration:返回一个预设配置,这个配置中不会对缓存,Cookie 和证书进行持久性的存储。这对于实现像秘密浏览这种功能来说是很理想的。
    • +backgroundSessionConfiguration:(NSString *)identifier:后台 session 不同于常规的,普通的session,它甚至可以在应用程序挂起,退出或者崩溃的情况下运行上传和下载任务。identifier初始化时指定的标识符,被用于向任何可能在进程外恢复后台传输的守护进程提供上下文。
    - (void)URLSession:(NSURLSession *)session
                  task:(NSURLSessionTask *)task
    willPerformHTTPRedirection:(NSHTTPURLResponse *)response
            newRequest:(NSURLRequest *)request
     completionHandler:(void (^)(NSURLRequest *))completionHandler
    {
        NSURLRequest *redirectRequest = request;
    // 自定义如何处理重定向请求,注意会生成一个新的request
        if (self.taskWillPerformHTTPRedirection) {
            redirectRequest = self.taskWillPerformHTTPRedirection(session, task, response, request);
        }
    
        if (completionHandler) {
            completionHandler(redirectRequest);
        }
    }
    

    代理2:didReceiveChallenge
    之前我们也有一个https认证,功能一样,执行的内容也完全一样。区别在于这个是non-session-level级别的认证,而之前的是session-level级别的。

    代理3:needNewBodyStream
    如果task是由uploadTaskWithStreamedRequest:创建的,那么提供初始的request body stream时候会调用该代理方法。或者因为认证挑战或者其他可恢复的服务器错误,而导致需要客户端重新发送一个含有body streamrequest,这时候也会调用该代理。

    //当一个session task需要发送一个新的request body stream到服务器端的时候,调用该代理方法
    - (void)URLSession:(NSURLSession *)session
                  task:(NSURLSessionTask *)task
     needNewBodyStream:(void (^)(NSInputStream *bodyStream))completionHandler
    {
        NSInputStream *inputStream = nil;
    
        if (self.taskNeedNewBodyStream) {
            // 自定义的获取到新的bodyStream方法
            inputStream = self.taskNeedNewBodyStream(session, task);
        } else if (task.originalRequest.HTTPBodyStream && [task.originalRequest.HTTPBodyStream conformsToProtocol:@protocol(NSCopying)]) {
            // 拷贝一份数据出来到新的bodyStream中(即inputStream)
            inputStream = [task.originalRequest.HTTPBodyStream copy];
        }
    
        if (completionHandler) {
            completionHandler(inputStream);
        }
    }
    

    代理4:didSendBodyData
    每次发送数据给服务器,会回调这个方法,通知已经发送了多少,总共要发送多少。代理方法里也就是仅仅调用了我们自定义的Block而已。

    //上传任务的回调方法
    //周期性地通知代理发送到服务器端数据的进度
    - (void)URLSession:(NSURLSession *)session
                  task:(NSURLSessionTask *)task
       didSendBodyData:(int64_t)bytesSent
        totalBytesSent:(int64_t)totalBytesSent
    totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
    {
    
        // 如果totalUnitCount获取失败,就使用HTTP header中的Content-Length作为totalUnitCount
        int64_t totalUnitCount = totalBytesExpectedToSend;
        if(totalUnitCount == NSURLSessionTransferSizeUnknown) {
            NSString *contentLength = [task.originalRequest valueForHTTPHeaderField:@"Content-Length"];
            if(contentLength) {
                totalUnitCount = (int64_t) [contentLength longLongValue];
            }
        }
        // 每次发送数据后的相关自定义处理,比如根据totalBytesSent来进行UI界面的数据上传显示
        if (self.taskDidSendBodyData) {
            self.taskDidSendBodyData(session, task, bytesSent, totalBytesSent, totalUnitCount);
        }
    }
    

    代理5:didCompleteWithError
    task完成之后的回调,成功和失败都会回调这里。这里的error不会报告服务期端的error,他表示的是客户端这边的error,比如无法解析hostname或者连不上host主机。

    - (void)URLSession:(NSURLSession *)session
                  task:(NSURLSessionTask *)task
    didCompleteWithError:(NSError *)error
    {
      
    
        AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
    
        // 如果task是在后台完成的,可能delegate会为nil
        if (delegate) {
            // 把代理转发给我们绑定的delegate
            [delegate URLSession:session task:task didCompleteWithError:error];
    
            // 转发完,该task结束了,就移除对应的delegate
            [self removeDelegateForTask:task];
        }
        
        //自定义Block回调
        if (self.taskDidComplete) {
            self.taskDidComplete(session, task, error);
        }
    }
    

    在这里根据task获取相关联的AFURLSessionManagerTaskDelegate对象:

    - (AFURLSessionManagerTaskDelegate *)delegateForTask:(NSURLSessionTask *)task {
        //task不能为空
        NSParameterAssert(task);
        //上锁,通过task的唯一taskIdentifier从字典中取值,这个唯一标识是在创建task的时候NSURLSessionTask为其设置的,不需要手动设置,保证唯一性
        AFURLSessionManagerTaskDelegate *delegate = nil;
        [self.lock lock];
        delegate = self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)];
        [self.lock unlock];
    
        return delegate;
    }
    

    移除跟AF代理相关的progress和通知:

    //从字典中删除task对应的delegate的key-value对
    - (void)removeDelegateForTask:(NSURLSessionTask *)task {
        NSParameterAssert(task);
    
        AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
        [self.lock lock];
        [delegate cleanUpProgressForTask:task];
        [self removeNotificationObserverForTask:task];
        [self.mutableTaskDelegatesKeyedByTaskIdentifier removeObjectForKey:@(task.taskIdentifier)];
        [self.lock unlock];
    }
    

    ❸ NSURLSessionDataDelegate

    代理1:didReceiveResponse
    告诉代理,该data task获取到了服务器端传回的最初始回复(response)。注意其中的completionHandler这个block,通过传入一个类型为NSURLSessionResponseDisposition的变量来决定该传输任务接下来该做什么:

    • NSURLSessionResponseAllowtask正常进行
    • NSURLSessionResponseCanceltask会被取消
    • NSURLSessionResponseBecomeDownload 会调用URLSession:dataTask:didBecomeDownloadTask:方法来新建一个download task以代替当前的data task

    该方法是可选的,除非你必须支持“multipart/x-mixed-replace”类型的content-type。因为如果你的request中包含了这种类型的content-type,服务器会将数据分片传回来,而且每次传回来的数据会覆盖之前的数据。每次返回新的数据时,session都会调用该函数,你应该在这个函数中合理地处理先前的数据,否则会被新数据覆盖。如果你没有提供该方法的实现,那么session将会继续任务,也就是说会覆盖之前的数据。

    - (void)URLSession:(NSURLSession *)session
              dataTask:(NSURLSessionDataTask *)dataTask
    didReceiveResponse:(NSURLResponse *)response
     completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler
    {
        //设置默认为继续进行
        NSURLSessionResponseDisposition disposition = NSURLSessionResponseAllow;
     
        //自定义去设置
        if (self.dataTaskDidReceiveResponse) {
            disposition = self.dataTaskDidReceiveResponse(session, dataTask, response);
        }
    
        if (completionHandler) {
            completionHandler(disposition);
        }
    }
    

    代理2:didBecomeDownloadTask
    上面的代理如果设置为NSURLSessionResponseBecomeDownload,则会调用这个方法。比如在- URLSession:dataTask:didReceiveResponse:completionHandler:completionHandler方法传递NSURLSessionResponseBecomeDownload,就会使data task变成download task,而且之前的data task不会再响应代理方法了。同样,如果设置为NSURLSessionResponseBecomeStream则会调用到didBecomeStreamTask代理里去,新生成一个NSURLSessionStreamTask来替换掉之前的dataTask

    - (void)URLSession:(NSURLSession *)session
              dataTask:(NSURLSessionDataTask *)dataTask
    didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask
    {
    
        AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask];
        if (delegate) {
            // 将delegate关联的data task移除,换成新产生的download task
            [self removeDelegateForTask:dataTask];
            [self setDelegate:delegate forTask:downloadTask];
        }
        //执行自定义Block
        if (self.dataTaskDidBecomeDownloadTask) {
            self.dataTaskDidBecomeDownloadTask(session, dataTask, downloadTask);
        }
    }
    

    代理3:didReceiveData
    一个NSData类型的数据通常是由一系列不同的数据整合到一起得到的,不管是不是这样,请使用- enumerateByteRangesUsingBlock:来遍历数据,而不是使用bytes方法,因为bytes缺少enumerateByteRangesUsingBlock方法中的range,有了rangeenumerateByteRangesUsingBlock就可以对NSData不同的数据块进行遍历,而不像bytes把所有NSData看成一个数据块。

    这个方法和上面didCompleteWithError算是NSUrlSession的代理中最重要的两个方法了。
    该代理方法可能会调用多次(比如分片获取数据),你需要自己实现该函数将所有数据整合在一起。

    我们转发了这个方法到AF的代理AFURLSessionManagerTaskDelegate中去,所以数据的拼接都是在AF的代理中进行的。这也是情理中的,毕竟每个响应数据都是对应各个task,各个AF代理的。在AFURLSessionManager只是做一些公共的处理。

    //当接收到部分期望得到的数据(expected data)时,会调用该代理方法
    - (void)URLSession:(NSURLSession *)session
              dataTask:(NSURLSessionDataTask *)dataTask
        didReceiveData:(NSData *)data
    {
        // 调用的是AFURLSessionManagerTaskDelegate的didReceiveData方法
        AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:dataTask];
        [delegate URLSession:session dataTask:dataTask didReceiveData:data];
    
        if (self.dataTaskDidReceiveData) {
            self.dataTaskDidReceiveData(session, dataTask, data);
        }
    }
    

    代理4:willCacheResponse
    task接收到所有期望的数据后,session会调用此代理方法。如果你没有实现该方法,那么就会使用创建session时使用的configuration对象决定缓存策略。这个代理方法最初的目的是为了阻止缓存特定的URLs或者修改NSCacheURLResponse对象相关的userInfo字典。

    该方法只会当request决定缓存response时候调用。作为准则,responses只会当以下条件都成立的时候返回缓存:

    • requestHTTPHTTPS URL的请求(或者你自定义的网络协议,并且确保该协议支持缓存)
    • 确保request请求是成功的(返回的status code为200-299)
    • 返回的response是来自服务器端的,而非缓存中本身就有的
    • 提供的NSURLRequest对象的缓存策略要允许进行缓存
    • 服务器返回的response中与缓存相关的header要允许缓存
    • response的大小不能比提供的缓存空间大太多
    // 询问data task或上传任务(upload task)是否缓存response
    - (void)URLSession:(NSURLSession *)session
              dataTask:(NSURLSessionDataTask *)dataTask
     willCacheResponse:(NSCachedURLResponse *)proposedResponse
     completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler
    {
        NSCachedURLResponse *cachedResponse = proposedResponse;
    
        // 自定义方法,你可以什么都不做,返回原始的cachedResponse,或者使用修改后的cachedResponse
        // 当然,你也可以返回NULL,这就意味着不需要缓存Response
        if (self.dataTaskWillCacheResponse) {
            cachedResponse = self.dataTaskWillCacheResponse(session, dataTask, proposedResponse);
        }
    
        if (completionHandler) {
            completionHandler(cachedResponse);
        }
    }
    

    ❹ NSURLSessionDownloadDelegate

    代理1:didFinishDownloadingToURL
    这个方法在下载完成的时候调用(必须实现),它也被转发到AF自定义delegate中,即AFURLSessionManagerTaskDelegate

    //下载完成的时候调用(必须实现)
    - (void)URLSession:(NSURLSession *)session
          downloadTask:(NSURLSessionDownloadTask *)downloadTask
    didFinishDownloadingToURL:(NSURL *)location
    {
        AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:downloadTask];
        
        //这个是session的,也就是全局的,后面的个人代理也会做同样的这件事
        if (self.downloadTaskDidFinishDownloading) {
            // 自定义函数,根据从服务器端获取到的数据临时地址location等参数构建出你想要将临时文件移动的位置
            NSURL *fileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);
            
            if (fileURL) {
                // 如果fileURL存在的话,表示用户希望把临时数据存起来
                delegate.downloadFileURL = fileURL;
                NSError *error = nil;
                // 将位于location位置的文件全部移到fileURL位置
                [[NSFileManager defaultManager] moveItemAtURL:location toURL:fileURL error:&error];
                
                // 如果移动文件失败,就发送AFURLSessionDownloadTaskDidFailToMoveFileNotification
                if (error) {
                    [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:error.userInfo];
                }
    
                return;
            }
        }
        // 转发代理:这一步比较诡异,感觉有重复的嫌疑。或许是为了兼容以前代码吧
        if (delegate) {
            [delegate URLSession:session downloadTask:downloadTask didFinishDownloadingToURL:location];
        }
    }
    

    代理2:didFinishDownloadingToURL

    • bytesWritten表示自上次调用该方法后,接收到的数据字节数
    • totalBytesWritten表示目前已经接收到的数据字节数
    • totalBytesExpectedToWrite 表示期望收到的文件总字节数,是由Content-Length header提供。如果没有提供,默认是NSURLSessionTransferSizeUnknown
    //周期性地通知下载进度调用
    - (void)URLSession:(NSURLSession *)session
          downloadTask:(NSURLSessionDownloadTask *)downloadTask
          didWriteData:(int64_t)bytesWritten
     totalBytesWritten:(int64_t)totalBytesWritten
    totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
    {
        if (self.downloadTaskDidWriteData) {
            self.downloadTaskDidWriteData(session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
        }
    }
    

    代理3:didResumeAtOffset
    如果一个resumable下载任务被取消或者失败了,你可以请求一个resumeData对象(比如在userInfo字典中通过NSURLSessionDownloadTaskResumeData这个键来获取到resumeData),并使用它来提供足够的信息以重新开始下载任务。随后,你可以使用resumeData作为downloadTaskWithResumeData:downloadTaskWithResumeData:completionHandler:的参数。

    当你调用这些方法时,你将开始一个新的下载任务。一旦你继续下载任务,session会调用它的代理方法URLSession:downloadTask:didResumeAtOffset:expectedTotalBytes:其中的downloadTask参数表示的就是新的下载任务,这也意味着下载重新开始了。

    如果文件缓存策略或者最后文件更新日期阻止重用已经存在的文件内容,那么fileOffset该值为0,否则该值表示已经存在磁盘上的,不需要重新获取的数据,即当前已经下载data的偏移量。

    说白了,这就是断点续传啊!

    // 告诉代理,下载任务重新开始下载了
    - (void)URLSession:(NSURLSession *)session
          downloadTask:(NSURLSessionDownloadTask *)downloadTask
     didResumeAtOffset:(int64_t)fileOffset
    expectedTotalBytes:(int64_t)expectedTotalBytes
    {
        if (self.downloadTaskDidResume) {
            self.downloadTaskDidResume(session, downloadTask, fileOffset, expectedTotalBytes);
        }
    }
    

    至此NSUrlSesssiondelegate讲完了,好多啊啊啊啊啊啊,还是挺考验笔者和读者的耐心的。
    大概总结下:

    • 每个代理方法对应一个我们自定义的Block,如果Block被赋值了,那么就调用它。
    • 在这些代理方法里,我们做的处理都是相对于这个sessionManager所有的request的,是公用的处理。
    • 转发了3个代理方法到AF的deleagate中去了,AF中的deleagate是需要对应每个task去私有化处理的。
    b、NSURLSessionDelegate转发到AF自定义的deleagate

    见下文 IOS框架:AFNetworking(中)


    Demo

    Demo在我的Github上,欢迎下载。
    SourceCodeAnalysisDemo

    参考文献

    AFNetworking到底做了什么?

    相关文章

      网友评论

          本文标题:IOS框架:AFNetworking(上)

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