AFNetworking框架分析(二)——AFURLSessionManager(上)

时间:2022-06-19
本文章向大家介绍AFNetworking框架分析(二)——AFURLSessionManager(上),主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

AFURLSessionManager 这个类是AFN框架的核心类,基本上通过它来实现了大部分核心功能。负责请求的建立、管理、销毁、安全、请求重定向、请求重启等各种功能。他主要实现了NSURLSession和NSRULSessionTask的封装。 首先来对比下系统URLSession网络请求与AFN网络请求的方法

系统提供的URLSession网络POST请求方法使用

    NSURL *securl                   = [NSURL URLWithString:authenticate_appAuthenticate];
    NSMutableURLRequest *secrequest = [[NSMutableURLRequest alloc]initWithURL:securl
                                                                  cachePolicy:NSURLRequestReloadIgnoringCacheData
                                                              timeoutInterval:1000];
    
    [secrequest setValue:[MHXIMPAFOper hashRule:[MHXIMPAFOper getTimeStamp]
                                           uuid:[MHUniqueID uniqueCFUUID]]
      forHTTPHeaderField:@"X-IMPF"];
    
    [secrequest setHTTPMethod:@"POST"];
    SendObject *secsend  = [[SendObject alloc]init];
    NSString *secbodyStr = [[secsend getSend] yy_modelToJSONString];
    NSData *secbody      = [secbodyStr dataUsingEncoding:NSUTF8StringEncoding];
    [secrequest setHTTPBody:secbody];
    NSURLSession *requestSessions = [NSURLSession sharedSession];
    NSURLSessionDataTask *requestTasks = [requestSessions dataTaskWithRequest:secrequest completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        NSLog(@"data ======= %@, response ======= %@, error ======= %@",data ,response, error);

    }];
    [requestTasks resume];

AFN中POST方法使用(这里进行了一层简单的封装)

+(AFHTTPSessionManager *)httpManager{
    // 获取请求对象
    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
    // 设置请求格式
    manager.requestSerializer = [AFJSONRequestSerializer serializer];
    // 设置返回格式
    manager.responseSerializer = [AFJSONResponseSerializer serializer];
    manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"text/plain", @"multipart/form-data", @"application/json", @"text/html", @"image/jpeg", @"image/png", @"application/octet-stream", @"text/json", nil];
    [manager.requestSerializer setValue:@"application/json" forHTTPHeaderField:@"Accept"];
    [manager.requestSerializer setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
    manager.requestSerializer.timeoutInterval = kRequestTimeOut;
    return manager;
}

+(__kindof NSURLSessionTask *)Post:(NSString *)URLString parameters:(id)parameters success:(void(^)(id responseObject))success failure:(void(^)(NSError *))failure{
    AFHTTPSessionManager *manager = [self httpManager];
    return [manager POST:URLString parameters:parameters progress:^(NSProgress * _Nonnull uploadProgress) {
        
    } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        if (success) {
            success(responseObject);
        }
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        if (failure) {
            failure(error);
        }
    }];
}

通过对比可以发现,AFN帮我们将繁琐的网络请求进行了封装,只需要传入访问服务器URL以及参数,就可以通过block返回给对应的正确或者错误数据,而且还可以通过progress的block不断获取进度。 AFHTTPSessionManager中一共提供了四种初始化方法,但最终都会指向调用- (instancetype)initWithBaseURL:(NSURL *)url sessionConfiguration:(NSURLSessionConfiguration *)configuration方法

- (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
    if ([[url path] length] > 0 && ![[url absoluteString] hasSuffix:@"/"]) {
        url = [url URLByAppendingPathComponent:@""];
    }

    self.baseURL = url;

    self.requestSerializer = [AFHTTPRequestSerializer serializer];
    self.responseSerializer = [AFJSONResponseSerializer serializer];

    return self;
}

初始化时,调用了父类的方法[super initWithSessionConfiguration:configuration]

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

    /*
     
     */
    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 的字典
    self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];

    //使用NSLock确保线程安全
    self.lock = [[NSLock alloc] init];
    self.lock.name = AFURLSessionManagerLockName;

    //异步获取当前session所有未完成的task。会让后台任务重新回来初始化session,之前可能存在的任务进行置空处理
    //参考AFN问题汇总https://github.com/AFNetworking/AFNetworking/issues/3499
    [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;
}

从上述代码中可以看出,在AFN初始化过程中,除了对NSURLSession进行初始化之外,还设置了许多默认配置,例如创建串行队列、默认JSON解析、无条件信任证书、保证线程安全、网络状态监听以及旧任务置空处理等操作。

以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方法 进入该方法查看实现,发现除了常用的POST和GET请求之外,还有PUT、HEAD、DELETE、PATCH这些其它请求类型的方法实现,都执行了同一个方法 - (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 在该方法中,首先调用AFHTTPRequestSerializer的requestWithMethod函数构建request;当有错误时,处理request构建产生的错误 - serializationError;其中relativeToURL表示将URLString拼接至baseURL中。接下来,继续查看requestSerializer的创建方法实现。

requestSerializer创建方法实现

其中最重要的部分,就是将request的各种属性进行遍历,用于给mutableRequest自带的属性赋值。 关于AFHTTPRequestSerializerObservedKeyPaths(),查看实现方法可以发现返回一个带有方法名的数组,定义了一个static的方法,表示该方法只能在本文件中使用。利用runtime的反射调用NSStringFromSelector方法,将六个方法名转换成字符串存入数组中。而这个六个方法又分别对应了mutableRequest的六个属性名称。

AFHTTPRequestSerializerObservedKeyPaths()代码实现

从上往下,依次代表: 1. 是否允许使用设备的蜂窝移动网络来创建request,默认为允许; 2.创建的request所使用的缓存策略,默认使用NSURLRequestUseProtocolCachePolicy,该策略表示如果缓存不存在,直接从服务端获取。如果缓存存在,会根据response中的Cache-Control字段判断下一步操作,如: Cache-Control字段为must-revalidata, 则 询问服务端该数据是否有更新,无更新直接返回给用户缓存数据,若已更新,则请求服务端. 3. 如果设置HTTPShouldHandleCookies为YES,就处理存储在NSHTTPCookieStore中的cookies HTTPShouldHandleCookies表示是否应该给request设置cookie并随request一起发送出去 4.HTTPShouldUsePipelining表示receiver(理解为iOS客户端)的下一个信息是否必须等到上一个请求回复才能发送。如果为YES表示可以,NO表示必须等receiver收到先前的回复才能发送下个信息 5.设定request的network service类型. 默认NSURLNetworkServiceTypeDefault. 这个network service是为了告诉系统网络层这个request使用的目的。 比如NSURLNetworkServiceTypeVoIP表示的这个request是用来请求网际协议通话技术(Voice over IP)。系统能根据提供的信息来优化网络处理,从而优化电池寿命,网络性能等等,客户端基本不使用 6.超时机制,默认60秒 至于要在此处实现遍历的目的,是因为在AFHTTPRequestSerializer类的init方法中,AFN给上面指定的6个方法选择器每一个元素添加了KVO观察者

self为自己的方法添加观察者

KVO代理实现

当数组的元素发生变化时,判断新值是否为空。若为空则从mutableObservedChangedKeyPaths可变集合中移除;不为空,则添加至可变集合中。 小提示:关于NSNull,包含了唯一方法+(NSNull *)null,是一个对象,用于表示零值的单独对象。主要用于不能使用nil的场景下,例如可变数组中,想插入一个空对象的情况。 因此,在AFHTTPSessionManager初始完成之后,需要额外添加自定义的request配置时,比如超时时间设置为10秒。这时KVO监听到timeoutInterval的属性发生变化,将keyPath添加到mutableObservedChangedKeyPaths可变集合中。然后,在执行网络请求方法时,会遍历该可变集合,通过KVC动态的给mutableRequest添加value,最终实现将自定义配置添加至request中。 而且在AFHTTPRequestSerializer类的初始化方法中,AFN自动添加了网络请求头部内容

请求头部内容打印

接下来,到了AFN的重点,对传入的参数字典进行处理。 使用AFN传入的参数格式为字典,但在网络请求中,是要转换成key=value&key=value的形式(GET请求直接拼接到URL之后,POST请求放入request body中),才能传给服务端获取有效的数据。

将请求参数字典转化成字符串

NSString * AFQueryStringFromParameters(NSDictionary *parameters)C函数的作用,是遍历数组中的AFQueryStringPair,然后以&符号拼接。AFQueryStringPair是一个数据处理类,只有两个属性:field和value;一个方法:-URLEncodedStringValue。它的作用就是上面我们说的,以key=value的形式,然后用URL Encode编码,拼接成字符串。 在遍历过程中,最终指向了NSArray * AFQueryStringPairsFromKeyAndValue(NSString *key, id value)函数,为了保证参数字典中,所有的value类型确保为字符串类型,这里便使用了递归,针对value可能为字典、数组、集合的类型时进行解析。例如,之前的项目中当需要向后台传输一段用户的定位数据用于分析行动轨迹时,传参的字典中的location的key值嵌套了一个包含分别以经度和纬度为key的字典。

递归确保所有的value最终全部转换成字符串形式

当value为字典、数组、集合的类型时,会执行递归解析,直到value类型都不为上述类型时,向mutableQueryStringComponents添加一个AFQueryStringPair类的对象,其中传入key和value并返回出去。 (这里AFN框架执行了升序排列,这里不是很明白为什么要先将字典的key进行升序排列再进行数据递归解析) 当传参字典中所有数据解析完成之后,会通过遍历返回为AFQueryStringPair类的对象,然后将该对象进行百分号编码,用于处理可能存在包含歧义或者不符合规划的字符(可以自行查找关于百分号编码相关资料),最后将字符串拼接"&"符号。 以上,就是AFN框架中,发起网络请求之前,关于request处理相关的操作全部流程分析,其中最主要的功能就是对传参参数进行了数据的递归解析,其次对request的六个相关属性进行KVO监听,可以在初始化AFHTTPSessionManager对象之后,自定义修改指定request属性时,通过KVO代理方法以及动态KVC最终在request中实现配置修改。