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

IOS框架:AFNetworking(中)

作者: 时光啊混蛋_97boy | 来源:发表于2020-10-26 06:21 被阅读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框架:AFNetworking(上)

b、NSURLSessionDelegate转发到AF自定义的deleagate

代理1:didCompleteWithError
AF实现的代理!被从urlsession那转发到这。

1、生成了一个存储这个task相关信息的字典:userInfo。将responseSerializerdownloadFileURLdata存到userInfo里面,这个字典是用来作为发送任务完成的通知的参数。 userInfo中的key值如下:

  • AFNetworkingTaskDidCompleteResponseDataKeysession 存储task获取到的原始response数据,与序列化后的response有所不同
  • AFNetworkingTaskDidCompleteSerializedResponseKey: 存储经过序列化(serialized)后的response
  • AFNetworkingTaskDidCompleteResponseSerializerKey: 保存序列化response的序列化器(serializer)
  • AFNetworkingTaskDidCompleteAssetPathKey: 存储下载任务后,数据文件存放在磁盘上的位置
  • AFNetworkingTaskDidCompleteErrorKey: 错误信息

2、判断了参数error的值,来区分请求成功还是失败。

#pragma mark - NSURLSessionTaskDelegate
- (void)URLSession:(__unused NSURLSession *)session
              task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{


#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
    //1)强引用self.manager,防止被提前释放;因为self.manager声明为weak,类似Block
    __strong AFURLSessionManager *manager = self.manager;

    __block id responseObject = nil;
    
    // 因为NSNotification这个类中本身有userInfo属性,可作为响应函数的参数
    // 不过我在AFNetworking源码中还未发现使用userInfo作为参数的做法,可能需要用户自己实现
    /**
     * userInfo中的key值例举如下:
     * AFNetworkingTaskDidCompleteResponseDataKey session 存储task获取到的原始response数据,与序列化后的response有所不同
     * AFNetworkingTaskDidCompleteSerializedResponseKey 存储经过序列化(serialized)后的response
     * AFNetworkingTaskDidCompleteResponseSerializerKey 保存序列化response的序列化器(serializer)
     * AFNetworkingTaskDidCompleteAssetPathKey 存储下载任务后,数据文件存放在磁盘上的位置
     * AFNetworkingTaskDidCompleteErrorKey 错误信息
     */
    //用来存储一些相关信息,来发送通知用的
    __block NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
    
    //存储responseSerializer响应解析对象
    userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer;

    //这里主要是针对大文件的时候,性能提升会很明显
    //把请求到的数据data传出去,然后就不要这个值了释放内存
    NSData *data = nil;
    if (self.mutableData) {
        data = [self.mutableData copy];
        //We no longer need the reference, so nil it out to gain back some memory.
        self.mutableData = nil;
    }
    
    //继续给userinfo填数据
    //如果downloadFileURL存在,如果是下载任务就设置下载完成后的文件存储url到字典中
    if (self.downloadFileURL) {
        userInfo[AFNetworkingTaskDidCompleteAssetPathKey] = self.downloadFileURL;
    } else if (data) {
        //否则就设置对应的NSData数据到字典中
        userInfo[AFNetworkingTaskDidCompleteResponseDataKey] = data;
    }
    // 如果task出错了,处理error信息
    if (error) {
        // 所以对应的观察者在处理error的时候,比如可以先判断userInfo[AFNetworkingTaskDidCompleteErrorKey]是否有值,有值的话,就说明是要处理error
        userInfo[AFNetworkingTaskDidCompleteErrorKey] = error;
        
        // 这里用group方式来运行task完成方法,表示当前所有的task任务完成,才会通知执行其他操作
        // 如果没有实现自定义的completionGroup和completionQueue,那么就使用AFNetworking提供的私有的dispatch_group_t和提供的dispatch_get_main_queue内容
        dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
            if (self.completionHandler) {
                self.completionHandler(task.response, responseObject, error);
            }
            //主线程中发送完成通知
            dispatch_async(dispatch_get_main_queue(), ^{
                [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
            });
        });
    } else {//在没有error时,会先对数据进行一次序列化操作,然后下面的处理就和有error的那部分一样了
        dispatch_async(url_session_manager_processing_queue(), ^{
            NSError *serializationError = nil;
            // 根据对应的task和data将response data解析成可用的数据格式,比如JSON serializer就将data解析成JSON格式
            responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];
            
            // 注意如果有downloadFileURL,意味着data存放在了磁盘上了,所以此处responseObject保存的是data存放位置,供后面completionHandler处理。没有downloadFileURL,就直接使用内存中的解析后的data数据
            if (self.downloadFileURL) {
                responseObject = self.downloadFileURL;
            }
            
            //写入userInfo
            if (responseObject) {
                userInfo[AFNetworkingTaskDidCompleteSerializedResponseKey] = responseObject;
            }

            // 序列化的时候出现错误
            if (serializationError) {
                userInfo[AFNetworkingTaskDidCompleteErrorKey] = serializationError;
            }
            
            //回调结果
            //同理,在dispatch组中和特定队列执行回调块
            dispatch_group_async(manager.completionGroup ?: url_session_manager_completion_group(), manager.completionQueue ?: dispatch_get_main_queue(), ^{
                if (self.completionHandler) {
                    self.completionHandler(task.response, responseObject, serializationError);
                }
                //主线程发送通知
                dispatch_async(dispatch_get_main_queue(), ^{
                    [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
                });
            });
        });
    }
#pragma clang diagnostic pop
}

没有error时,在一个并行url_session_manager_processing_queue中进行的数据的序列化操作,因为数据解析这种费时操作,需要用并行线程来做。点进去这个并行的queue看看:

// 创建一个并发队列,用于在网络请求任务完成后处理数据的
// 并发队列实现多线程处理多个请求完成后的数据处理
static dispatch_queue_t url_session_manager_processing_queue() {
    static dispatch_queue_t af_url_session_manager_processing_queue;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        af_url_session_manager_processing_queue = dispatch_queue_create("com.alamofire.networking.session.manager.processing", DISPATCH_QUEUE_CONCURRENT);
    });

    return af_url_session_manager_processing_queue;
}

再来看看如何进行数据的序列化操作的:

// 协议 父类和子类都需要实现。通过这个协议将数据转为更有用的格式。
@protocol AFURLResponseSerialization <NSObject, NSSecureCoding, NSCopying>

// 将response解码成指定的关联的数据
- (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response
                           data:(nullable NSData *)data
                          error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;

@end

发现它是一个协议方法,各种类型的responseSerializer类都会遵守这个协议方法,实现了一个把我们请求到的data转换为我们需要的类型的数据的方法。至于各种类型的responseSerializer具体如何解析数据,之后遇见了再看吧。

如果没有实现自定义的completionGroupcompletionQueue,那么就使用AFNetworking提供的私有的url_session_manager_completion_group和提供的dispatch_get_main_queue内容。url_session_manager_completion_group这是个什么鬼东西?

//处理session,完成回调的队列组
static dispatch_group_t url_session_manager_completion_group() {
    static dispatch_group_t af_url_session_manager_completion_group;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        af_url_session_manager_completion_group = dispatch_group_create();
    });

    return af_url_session_manager_completion_group;
}

AF没有用这个GCD组做任何处理,只是提供这个接口,让我们有需求的自行调用处理,比如有对多个任务完成度的监听,则可以自行处理。

队列的话,如果你不需要回调主线程,则可以自己设置一个回调队列completionQueue
回到主线程,发送了任务完成的通知:

//主线程中发送完成通知
dispatch_async(dispatch_get_main_queue(), ^{
    [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingTaskDidCompleteNotification object:task userInfo:userInfo];
});

在我们对UIKit的扩展中,用到了这个通知。

代理2:didReceiveData

#pragma mark - NSURLSessionDataTaskDelegate

// 回调方法,收到数据
- (void)URLSession:(__unused NSURLSession *)session
          dataTask:(__unused NSURLSessionDataTask *)dataTask
    didReceiveData:(NSData *)data
{
    //拼接数据
    NSLog(@"delete--%@",[NSThread currentThread]);
    [self.mutableData appendData:data];
}

代理3:didFinishDownloadingToURL

#pragma mark - NSURLSessionDownloadTaskDelegate

- (void)URLSession:(NSURLSession *)session
      downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
    NSError *fileManagerError = nil;
    self.downloadFileURL = nil;

    //AF代理的自定义Block
    if (self.downloadTaskDidFinishDownloading) {
        //得到自定义下载路径
        self.downloadFileURL = self.downloadTaskDidFinishDownloading(session, downloadTask, location);

        if (self.downloadFileURL) {
            //把下载路径移动到我们自定义的下载路径
           [[NSFileManager defaultManager] moveItemAtURL:location toURL:self.downloadFileURL error:&fileManagerError];

            //存在错误则发通知
            if (fileManagerError) {
                [[NSNotificationCenter defaultCenter] postNotificationName:AFURLSessionDownloadTaskDidFailToMoveFileNotification object:downloadTask userInfo:fileManagerError.userInfo];
            }
        }
    }
}
@end

之前的NSUrlSession代理和这里都移动了文件到下载路径,而NSUrlSession代理的下载路径是所有request公用的下载路径,一旦设置,所有的request都会下载到之前那个路径,而这个是对应的每个task的,每个task可以设置各自下载路径。

AFHttpManagerdownload方法:

[manager downloadTaskWithRequest:resquest progress:nil destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
    return path;
} completionHandler:^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
}];

其中returnpath就是对应的这个代理方法里的path

调用最终会走到这么一个方法:

// 创建下载文件任务的AFURLSessionManagerTaskDelegate对象,并加入到字典中
- (void)addDelegateForDownloadTask:(NSURLSessionDownloadTask *)downloadTask
                          progress:(void (^)(NSProgress *downloadProgress)) downloadProgressBlock
                       destination:(NSURL * (^)(NSURL *targetPath, NSURLResponse *response))destination
                 completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler
{
    AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] init];
    delegate.manager = self;
    delegate.completionHandler = completionHandler;
 
    /*
     需要注意下,AFURLSessionManagerTaskDelegate中下载文件完成后会调用delegate.downloadTaskDidFinishDownloading回调块
     来获取下载文件要移动到的目录URL
     所以这里就是创建这个回调块,直接返回参数中的destination回调块
     */
 
    //返回地址的Block
    if (destination) {
        // 有点绕,就是把一个block赋值给我们代理的downloadTaskDidFinishDownloading
        // 这个Block里的内部返回也是调用Block去获取到的,这里面的参数都是AF代理传过去的
        delegate.downloadTaskDidFinishDownloading = ^NSURL * (NSURLSession * __unused session, NSURLSessionDownloadTask *task, NSURL *location) {
            //把Block返回的地址返回
            return destination(location, task.response);
        };
    }

    downloadTask.taskDescription = self.taskDescriptionForSessionTasks;

    [self setDelegate:delegate forTask:downloadTask];

    delegate.downloadProgressBlock = downloadProgressBlock;
}

可以看到地址被赋值给AF的Block了。

至此AF自定义的代理也讲完了,数据或错误信息随着AF自定义代理的成功或者失败回调,来到了用户的手中。


4、AFURLResponseSerialization 如何解析数据

@protocol AFURLResponseSerialization <NSObject, NSSecureCoding, NSCopying>
@interface AFHTTPResponseSerializer : NSObject <AFURLResponseSerialization>
@interface AFJSONResponseSerializer : AFHTTPResponseSerializer
@interface AFXMLParserResponseSerializer : AFHTTPResponseSerializer
@interface AFPropertyListResponseSerializer : AFHTTPResponseSerializer
@interface AFImageResponseSerializer : AFHTTPResponseSerializer
@interface AFCompoundResponseSerializer : AFHTTPResponseSerializer

AF用来解析数据的一共包括上述这些类。第一个实际是一个协议,协议中的方法如下:

// 协议,父类和子类都需要实现。通过这个协议将数据转为更有用的格式
@protocol AFURLResponseSerialization <NSObject, NSSecureCoding, NSCopying>

// 将response解码成指定的关联的数据
- (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response
                           data:(nullable NSData *)data
                          error:(NSError * _Nullable __autoreleasing *)error NS_SWIFT_NOTHROW;

@end

后面6个类都是遵守这个协议方法,去做数据解析,接下来一个个看其具体实现方式。

类一:AFHTTPResponseSerializer
// 父类的解析不做任何事情 只做一次response的验证
- (id)responseObjectForResponse:(NSURLResponse *)response
                           data:(NSData *)data
                          error:(NSError *__autoreleasing *)error
{
    [self validateResponse:(NSHTTPURLResponse *)response data:data error:error];

    return data;
}

继续看validateResponse:方法。这个方法就是检测响应的有效性的,设置默认有效,后面只处理无效情况。

  • 根据初始化的acceptableContentTypes 判断MIME 媒体类型是否合法。如果content-type不满足,那么产生的validationError就是DomainAFURLResponseSerializationErrorDomaincodeNSURLErrorCannotDecodeContentData
  • 根据初始化的acceptableStatusCodes 判断状态码是否有效。如果MIME type不满足,,那么产生的validationError就是DomainAFURLResponseSerializationErrorDomaincodeNSURLErrorBadServerResponse
  • self.acceptableContentTypesself.acceptableStatusCodes这两个判断中,如果都出现错误怎么办呢?这就用到了NSUnderlyingErrorKey 这个字段,它表示一个优先的错误,valueNSError对象。
// 判断是不是可接受类型和可接受code,不是则填充error
- (BOOL)validateResponse:(NSHTTPURLResponse *)response
                    data:(NSData *)data
                   error:(NSError * __autoreleasing *)error
{
    //response是否合法标识
    BOOL responseIsValid = YES;
    //验证的error
    NSError *validationError = nil;

    //返回内容无效
    //如果存在且是NSHTTPURLResponse
    if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) {
        
        //主要判断自己能接受的数据类型和response的数据类型是否匹配
        //1、有接受数据类型 2、不匹配response,3、响应类型不为空,数据长度不为0
        if (self.acceptableContentTypes && ![self.acceptableContentTypes containsObject:[response MIMEType]] &&
            !([response MIMEType] == nil && [data length] == 0)) {

            //进入If块说明解析数据肯定是失败的,这时候要把解析错误信息放到error里
            //如果数据长度大于0,而且有响应url
            if ([data length] > 0 && [response URL]) {
                
                //NSLocalizedDescriptionKey是NSError头文件中预定义的键,标识错误的本地化描述,可以通过NSError的localizedDescription方法获得对应的值信息
                //NSURLErrorFailingURLErrorKey相应的值是包含导致加载失败的URL的NSURL。
                
                //生成错误信息字典。会返回unacceptable content-type的信息,并将错误信息记录在了mutableUserInfo中
                NSMutableDictionary *mutableUserInfo = [@{
                                                          NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Request failed: unacceptable content-type: %@", @"AFNetworking", nil), [response MIMEType]],
                                                          NSURLErrorFailingURLErrorKey:[response URL],
                                                          AFNetworkingOperationFailingURLResponseErrorKey: response,
                                                        } mutableCopy];
                if (data) {
                    mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data;
                }
                
                //+errorWithDomain: code: userInfo:创建和初始化NSError对象
                //NSErrorDomain错误域 - 这可以是预定义的NSError域之一,也可以是描述自定义域的任意字符串。 域名不能为空。
                //收到的内容数据具有未知内容编码(解析数据出错)。NSURLErrorCannotDecodeContentData = -1016,NSError错误码
                //出现错误时通过AFErrorWithUnderlyingError函数生成本地格式化的错误
                validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorCannotDecodeContentData userInfo:mutableUserInfo], validationError);
            }
            //返回标识
            responseIsValid = NO;
        }
        
        //返回状态码无效
        //判断自己可接受的状态吗
        //如果和response的状态码不匹配,则进入if块
        if (self.acceptableStatusCodes && ![self.acceptableStatusCodes containsIndex:(NSUInteger)response.statusCode] && [response URL]) {
            
            //-localizedStringForStatusCode:根据状态码获取本地化文本内容
            //填写错误信息字典
            NSMutableDictionary *mutableUserInfo = [@{
                                               NSLocalizedDescriptionKey: [NSString stringWithFormat:NSLocalizedStringFromTable(@"Request failed: %@ (%ld)", @"AFNetworking", nil), [NSHTTPURLResponse localizedStringForStatusCode:response.statusCode], (long)response.statusCode],
                                               NSURLErrorFailingURLErrorKey:[response URL],
                                               AFNetworkingOperationFailingURLResponseErrorKey: response,
                                       } mutableCopy];

            if (data) {
                mutableUserInfo[AFNetworkingOperationFailingURLResponseDataErrorKey] = data;
            }
            
            //收到从服务器来的错误数据 NSURLErrorBadServerResponse = -1011,NSError错误码
            validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorBadServerResponse userInfo:mutableUserInfo], validationError);
            
            //返回标识
            responseIsValid = NO;
        }
    }

    //给我们传过来的错误指针赋值
    if (error && !responseIsValid) {
        *error = validationError;
    }

    return responseIsValid;
}

简单来说,这个方法就是来判断返回数据与咱们使用的解析器是否匹配,需要解析的状态码是否匹配。如果错误,则填充错误信息,并且返回NO,否则返回YES,错误信息为nil。

AFHTTPResponseSerializer仅仅是调用验证方法,然后就返回了data

类二:AFJSONResponseSerializer
// 协议方法 解析response
- (id)responseObjectForResponse:(NSURLResponse *)response
                           data:(NSData *)data
                          error:(NSError *__autoreleasing *)error
{
    //先判断是不是可接受类型和可接受code,如果验证结果失败
    if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
        //在 没有error 或者 错误code:NSURLErrorCannotDecodeContentData 的情况下,是不能解析数据的,就返回nil
        if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) {
            return nil;
        }
    }
    

    id responseObject = nil;
    NSError *serializationError = nil;

    // 排除是否为一个空格的数据
    BOOL isSpace = [data isEqualToData:[NSData dataWithBytes:" " length:1]];
    // 如果数据不空则去json解析
    if (data.length > 0 && !isSpace) {
        responseObject = [NSJSONSerialization JSONObjectWithData:data options:self.readingOptions error:&serializationError];
    } else {
        return nil;
    }

    // 移除json中的null
    if (self.removesKeysWithNullValues && responseObject) {
        responseObject = AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions);
    }

    // 拿着json解析的error去填充错误信息
    if (error) {
        *error = AFErrorWithUnderlyingError(serializationError, *error);
    }

    // 返回解析结果
    return responseObject;
}

探究函数一:AFErrorOrUnderlyingErrorHasCodeInDomain

// 判断是不是我们自己之前生成的错误信息,是的话返回YES
// 检测错误是否匹配code或者domain
static BOOL AFErrorOrUnderlyingErrorHasCodeInDomain(NSError *error, NSInteger code, NSString *domain) {
    // domain和code相同
    if ([error.domain isEqualToString:domain] && error.code == code) {
        return YES;
    } else if (error.userInfo[NSUnderlyingErrorKey]) {
        // 如果userInfo的NSUnderlyingErrorKey有值
        // 则再判断一次附属error的code domain是否匹配
        return AFErrorOrUnderlyingErrorHasCodeInDomain(error.userInfo[NSUnderlyingErrorKey], code, domain);
    }

    return NO;
}

这里传过去的codedomain两个参数分别为NSURLErrorCannotDecodeContentDataAFURLResponseSerializationErrorDomain,这两个参数是我们之前判断response可接受类型和code时候自己去生成错误的时候填写的。

探究函数二:AFJSONObjectByRemovingKeysWithNullValues

// 删除value为空的key值
static id AFJSONObjectByRemovingKeysWithNullValues(id JSONObject, NSJSONReadingOptions readingOptions) {
    // 是否为数组类型
    if ([JSONObject isKindOfClass:[NSArray class]]) {
       // 创建一个可变数组,只需要JSONObject.count个,感受到大神写代码的严谨态度了吗...
        NSMutableArray *mutableArray = [NSMutableArray arrayWithCapacity:[(NSArray *)JSONObject count]];
        for (id value in (NSArray *)JSONObject) {
            // 递归调用自己
            [mutableArray addObject:AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions)];
        }

        // 判断解析类型是mutable还是非muatable,对应返回mutableArray或者array
        return (readingOptions & NSJSONReadingMutableContainers) ? mutableArray : [NSArray arrayWithArray:mutableArray];
    }
    
    // 是否为字典类型
    else if ([JSONObject isKindOfClass:[NSDictionary class]]) {
        NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionaryWithDictionary:JSONObject];
        // 遍历所有key
        for (id <NSCopying> key in [(NSDictionary *)JSONObject allKeys]) {
            id value = (NSDictionary *)JSONObject[key];
            
            // value是空类型或者是空则移除
            if (!value || [value isEqual:[NSNull null]]) {
                [mutableDictionary removeObjectForKey:key];
            }
            
            // value是其他类型
            else if ([value isKindOfClass:[NSArray class]] || [value isKindOfClass:[NSDictionary class]]) {
                // 递归调用自己
                mutableDictionary[key] = AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions);
            }
        }

        return (readingOptions & NSJSONReadingMutableContainers) ? mutableDictionary : [NSDictionary dictionaryWithDictionary:mutableDictionary];
    }

    return JSONObject;
}

通过递归的方式删除value为空的key值。

探究函数三:AFErrorWithUnderlyingError

// 设置一个underlyingError为error的附属
static NSError * AFErrorWithUnderlyingError(NSError *error, NSError *underlyingError) {
   // 是否传入了error
    if (!error) {
        return underlyingError;
    }
    
    // 是否已经有附属了
    if (!underlyingError || error.userInfo[NSUnderlyingErrorKey]) {
        return error;
    }
    
    // 取出error的userinfo
    NSMutableDictionary *mutableUserInfo = [error.userInfo mutableCopy];
    mutableUserInfo[NSUnderlyingErrorKey] = underlyingError;

    return [[NSError alloc] initWithDomain:error.domain code:error.code userInfo:mutableUserInfo];
}

json解析的错误赋值给需要返回的error上。

最主要的解析方法的实现已经讲完了,ResponseSerialize还有一些其他的类型解析,大家可以自行去阅读。


5、AF整个流程和线程的关系

  1. 一开始初始化sessionManager的时候,一般都是在主线程。
  2. 然后调用get或者post等去请求数据,接着会进行request拼接,AF代理的字典映射,progressKVO添加等,一直到NSUrlSessionresume之前,这些准备工作仍旧是在主线程中进行的。
  3. 接着调用NSUrlSessionresume后,就跑到NSUrlSession内部去对网络进行数据请求了,在它内部是多线程并发的去请求数据的。
  4. 紧接着数据请求完成后,回调回来在一开始生成的并发数为1的NSOperationQueue中,这个时候会是多线程串行的回调回来的。
// AFURLSessionManager

- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
.......
    // 设置为delegate的操作队列并发的线程数量1,也就是串行队列
    self.operationQueue.maxConcurrentOperationCount = 1;
......
}

这里的并发数仅仅是回调代理的线程并发数,而不是请求网络的线程并发数。请求网络是由NSUrlSession来做的,它内部维护了一个线程池,用来做网络请求。它调度线程,基于底层的CFSocket去发送请求和接收数据,这些线程是并发的。

为什么回调Queue要设置并发数为1?

  • 虽然回调Queue的并发数为1,仍然会有不止一条线程,但是因为是串行回调,所以同一时间,只会有一条线程在操作AFUrlSessionManager的那些方法。还是会有多线程的操作的,因为设置刚开始调起请求的时候,是在主线程的,而回调则是串行子线程。
  • 因为跟代理相关的一些操作AF都使用了NSLock。所以就算Queue的并发数设置为n,因为多线程回调,锁的等待,导致所提升的程序速度也并不明显,反而多task回调导致的多线程并发,平白浪费了部分性能。设置Queue的并发数为1,则至少回调的事件,是不需要多线程并发的,回调没有了NSLock的等待时间,所以对时间并没有多大的影响。
  • AF2.x所有的回调是在一条线程,这条线程是AF的常驻线程,而这一条线程正是AF调度request的思想精髓所在,所以另一个目的就是为了和之前版本保持一致。
  1. 下一步自己又创建了并发的多线程,去对这些返回数据进行了各种类型的解析。
  2. 最后我们如果有自定义的completionQueue,则在自定义的queue中回调回来,也就是子线程回调回来,否则就是主队列,主线程中回调结束。

三、AF2.x版本的核心实现

AF2.x是基于NSURLConnection来封装的,而NSURLConnection的创建以及数据请求,就被封装在AFURLConnectionOperation这个类中。所以这个类基本上是AF2.x最底层也是最核心的类。AFHTTPRequestOperation是继承自AFURLConnectionOperation,对它父类一些方法做了些封装。AFHTTPRequestOperationManager则是一个管家,去管理这些这些operation

AF2.x整个网络请求的流程如下图:

AF2.x整个网络请求的流程
  1. 最上层的是AFHTTPRequestOperationManager,调用它进行getpost等各种类型的网络请求
  2. AFHTTPRequestOperationManager去调用AFURLRequestSerializationrequest参数拼装,通过拼装后的request生成了一个AFHTTPRequestOperation实例,接着把AFHTTPRequestOperation添加到一个NSOperationQueue中。
  3. AFHTTPRequestOperation拿到request后,会去调用它的父类AFURLConnectionOperation的初始化方法,并且把相关参数交给它,除此之外,当父类完成数据请求后,它调用了AFURLResponseSerialization把数据解析成我们需要的格式(jsonXML等)。
  4. 最后就是我们AF最底层的类AFURLConnectionOperation,它去进行数据请求,并且如果是https请求,会在请求的相关代理中,调用AFSecurityPolicyhttps认证,最后将请求到的数据返回。
1、Get请求

按照网络请求的流程去看看AF2.x的实现。

AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:url parameters:params
     success:^(AFHTTPRequestOperation *operation, id responseObject) {
    
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
    
}];
2、AFHTTPRequestOperationManager的初始化方法
+ (instancetype)manager {
    return [[self alloc] initWithBaseURL:nil];
}

- (instancetype)init {
    return [self initWithBaseURL:nil];    
}
- (instancetype)initWithBaseURL:(NSURL *)url {
    self = [super init];
    if (!self) {
        return nil;
    }
    if ([[url path] length] > 0 && ![[url absoluteString] hasSuffix:@"/"]) {
        url = [url URLByAppendingPathComponent:@""];
    }
    self.baseURL = url;
    self.requestSerializer = [AFHTTPRequestSerializer serializer];
    self.responseSerializer = [AFJSONResponseSerializer serializer];
    self.securityPolicy = [AFSecurityPolicy defaultPolicy];
    self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];

    //用来调度所有请求的queue
    self.operationQueue = [[NSOperationQueue alloc] init];
    //是否做证书验证
    self.shouldUseCredentialStorage = YES;

    return self;
}

基本和AF3.x类似,除了一下两点:

  • 设置了一个operationQueue。这个队列用来调度里面所有的operation,在AF2.x中,每一个operation就是一个网络请求。
  • 设置shouldUseCredentialStorage为YES,这个后面会传给operationoperation会根据这个值,去返回给代理,系统是否做https的证书验证。
3、Get方法的实现:
- (AFHTTPRequestOperation *)GET:(NSString *)URLString
                    parameters:(id)parameters
                       success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
                       failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
   //生成request
   NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:@"GET" URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:nil];
   
   // 生成operation
   AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithRequest:request success:success failure:failure];

   // 把生成的operation加到一开始创建的queue中
   [self.operationQueue addOperation:operation];

   return operation;
}

生成operation方法的实现:

- (AFHTTPRequestOperation *)HTTPRequestOperationWithRequest:(NSURLRequest *)request
                                                    success:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
                                                    failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
    //创建自定义的AFHTTPRequestOperation
    AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
    operation.responseSerializer = self.responseSerializer;
    operation.shouldUseCredentialStorage = self.shouldUseCredentialStorage;
    operation.credential = self.credential;

    //设置自定义的安全策略
    operation.securityPolicy = self.securityPolicy;

    [operation setCompletionBlockWithSuccess:success failure:failure];
    operation.completionQueue = self.completionQueue;
    operation.completionGroup = self.completionGroup;
    return operation;
}

其中initWithRequest方法的实现:

- (instancetype)initWithRequest:(NSURLRequest *)urlRequest {
    self = [super initWithRequest:urlRequest];
    if (!self) {
        return nil;
    }

    self.responseSerializer = [AFHTTPResponseSerializer serializer];
    return self;
}

实际上是调用了继承自NSOperation的父类,也就是最核心的类AFURLConnectionOperation的初始化方法:

//初始化
- (instancetype)initWithRequest:(NSURLRequest *)urlRequest {
    NSParameterAssert(urlRequest);

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

    //设置为ready
    _state = AFOperationReadyState;

    //递归锁,避免死锁
    //有些方法可能会存在递归调用的情况
    self.lock = [[NSRecursiveLock alloc] init];
    self.lock.name = kAFNetworkingLockName;

    self.runLoopModes = [NSSet setWithObject:NSRunLoopCommonModes];
    self.request = urlRequest;
    
    //是否应该咨询证书存储连接
    self.shouldUseCredentialStorage = YES;

    //https认证策略
    self.securityPolicy = [AFSecurityPolicy defaultPolicy];

    return self;
}

state标志着这个网络请求的状态,是个枚举:

typedef NS_ENUM(NSInteger, AFOperationState) {
    AFOperationPausedState      = -1,  //停止
    AFOperationReadyState       = 1,   //准备就绪
    AFOperationExecutingState   = 2,  //正在进行中
    AFOperationFinishedState    = 3,  //完成
};

这些状态其实对应着operation的状态:

//映射这个operation的各个状态
static inline NSString * AFKeyPathFromOperationState(AFOperationState state) {
    switch (state) {
        case AFOperationReadyState:
            return @"isReady";
        case AFOperationExecutingState:
            return @"isExecuting";
        case AFOperationFinishedState:
            return @"isFinished";
        case AFOperationPausedState:
            return @"isPaused";
        default: {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunreachable-code"
            return @"state";
#pragma clang diagnostic pop
        }
    }
}

并且还复写了这些属性的get方法,用来和自定义的state一一对应:

//复写这些方法,与自己的定义的state对应
 - (BOOL)isReady {
    return self.state == AFOperationReadyState && [super isReady];
}
 - (BOOL)isExecuting {
    return self.state == AFOperationExecutingState;
}
 - (BOOL)isFinished {
    return self.state == AFOperationFinishedState;
}

设置就绪状态AFOperationExecutingState后,在start方法中调用以下语句开始执行:

[self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];

在常驻线程中以不阻塞的方式,在self.runLoopModes的模式下调用operationDidStart方法:

- (void)operationDidStart {
    [self.lock lock];
    //如果没取消
    if (![self isCancelled]) {
        //startImmediately 默认为YES 请求发出,回调会加入到主线程的 Runloop 下,RunloopMode 会默认为 NSDefaultRunLoopMode
        //如果我们设置为NO,则需要调用scheduleInRunLoop:runLoop去注册一个runloop和mode,它会在我们指定的这个runloop所在的线程中回调结果
        self.connection = [[NSURLConnection alloc] initWithRequest:self.request delegate:self startImmediately:NO];
        
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        for (NSString *runLoopMode in self.runLoopModes) {
            //把connection和outputStream注册到当前线程runloop中去,只有这样,才能在这个线程中回调
            [self.connection scheduleInRunLoop:runLoop forMode:runLoopMode];
            [self.outputStream scheduleInRunLoop:runLoop forMode:runLoopMode];
        }
        //打开输出流
        [self.outputStream open];
        //开启请求
        [self.connection start];
    }
    [self.lock unlock];
    
    // 到主线程发送一个任务开始执行的通知
    dispatch_async(dispatch_get_main_queue(), ^{
        [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingOperationDidStartNotification object:self];
    });
}

这里数据请求和拼接并没有用NSMutableData,而是用了outputStream,它是一个写入到内存中的流,可以通过NSStreamDataWrittenToMemoryStreamKey拿到写入后的数据。这个outputStreamget方法中被初始化了:

 - (NSOutputStream *)outputStream {
    if (!_outputStream) {
        self.outputStream = [NSOutputStream outputStreamToMemory];
    }
    return _outputStream;
}

outputStream的优势在于下载大文件的时候,可以以流的形式,将文件直接保存到本地,这样可以为我们节省很多的内存,调用如下方法进行设置:

[NSOutputStream outputStreamToFileAtPath:@"filePath" append:YES];

但是这里是把流写入内存中,这样其实这个节省内存的意义已经不存在了。那为什么还要用呢?猜测是为了用它可以注册在某一个runloop的指定mode下。 虽然AF使用这个outputStream是肯定在这个常驻线程中的,不会有线程安全的问题,但是它是被声明在.h中的:@property (nonatomic, strong) NSOutputStream *outputStream;,难保外部不会在其他线程对这个数据做什么操作,所以它相对于NSMutableData作用就体现出来了,就算我们在外部其它线程中去操作它,也不会有线程安全的问题。

4、connection的代理方法

接下来网络请求开始执行了,就开始触发connection的代理方法了。

connection的代理方法

AF2.x一共实现了如上这么多代理方法,作用大部分和我们之前讲的NSURLSession的代理方法类似。这里只解释几个关键的代理方法。需要注意的是,之前是把connection注册在我们常驻线程的runloop中了,所以以下所有的代理方法,都是在这仅有的一条常驻线程中回调。

代理1:didReceiveResponse

//收到响应,把response赋给自己的属性
- (void)connection:(NSURLConnection __unused *)connection
didReceiveResponse:(NSURLResponse *)response
{
    self.response = response;
}

代理2:didReceiveData

//拼接获取到的数据
- (void)connection:(NSURLConnection __unused *)connection
    didReceiveData:(NSData *)data
{
    //1. 给outputStream拼接数据
    NSUInteger length = [data length];
    while (YES) {
        NSInteger totalNumberOfBytesWritten = 0;
        //如果outputStream 还有空余空间
        if ([self.outputStream hasSpaceAvailable]) {
           
            //创建一个buffer流缓冲区,大小为data的字节数
            const uint8_t *dataBuffer = (uint8_t *)[data bytes];

            NSInteger numberOfBytesWritten = 0;
           
            //当写的长度小于数据的长度,在循环里
            while (totalNumberOfBytesWritten < (NSInteger)length) {
                //往outputStream写数据,系统的方法,一次就写一部分,得循环写
                numberOfBytesWritten = [self.outputStream write:&dataBuffer[(NSUInteger)totalNumberOfBytesWritten] maxLength:(length - (NSUInteger)totalNumberOfBytesWritten)];
                //如果 numberOfBytesWritten写入失败了。跳出循环
                if (numberOfBytesWritten == -1) {
                    break;
                }
                //加上每次写的长度
                totalNumberOfBytesWritten += numberOfBytesWritten;
            }

            break;
        }
        
        //2. 如果出错则调用:connection:didFailWithError:也就是网络请求失败的代理
        if (self.outputStream.streamError) {
            //取消connection
            [self.connection cancel];
            //调用失败的方法
            [self performSelector:@selector(connection:didFailWithError:) withObject:self.connection withObject:self.outputStream.streamError];
            return;
        }
    }

    //3. 回到主线程回调下载数据大小
    dispatch_async(dispatch_get_main_queue(), ^{
        self.totalBytesRead += (long long)length;

        if (self.downloadProgress) {
            self.downloadProgress(length, self.totalBytesRead, self.response.expectedContentLength);
        }
    });
}

代理3:connectionDidFinishLoading

//任务完成之后调用
- (void)connectionDidFinishLoading:(NSURLConnection __unused *)connection {

    //从outputStream中拿到数据 NSStreamDataWrittenToMemoryStreamKey写入到内存中的流
    self.responseData = [self.outputStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey];

    //关闭outputStream
    [self.outputStream close];
    
    //如果响应数据已经有了,则outputStream置为nil
    if (self.responseData) {
       self.outputStream = nil;
    }
    //清空connection
    self.connection = nil;

    [self finish];
}

 - (void)finish {
    [self.lock lock];
    //把当前任务状态改为已完成
    self.state = AFOperationFinishedState;
    [self.lock unlock];

    //到主线程发送任务完成的通知
    dispatch_async(dispatch_get_main_queue(), ^{
        [[NSNotificationCenter defaultCenter] postNotificationName:AFNetworkingOperationDidFinishNotification object:self];
    });
}

设置状态为已完成,其实调用了本类复写的set的方法:

 - (void)setState:(AFOperationState)state {
    
    //判断从当前状态到另一个状态是不是合理,在加上现在是否取消。可见判断非常严谨。
    if (!AFStateTransitionIsValid(self.state, state, [self isCancelled])) {
        return;
    }
    
    [self.lock lock];
    
    //拿到对应的父类管理当前线程周期的key
    NSString *oldStateKey = AFKeyPathFromOperationState(self.state);
    NSString *newStateKey = AFKeyPathFromOperationState(state);
    
    //改变state的时候,发出KVO
    [self willChangeValueForKey:newStateKey];
    [self willChangeValueForKey:oldStateKey];
    _state = state;
    [self didChangeValueForKey:oldStateKey];
    [self didChangeValueForKey:newStateKey];
    [self.lock unlock];
}

类似NSOperationQueue,如果对应的operation的属性finnished被设置为YES,则代表当前operation结束了,会把operation从队列中移除,并且调用operationcompletionBlock,而请求到的数据就是从这个completionBlock中传递回去的。

代理4:didFailWithError

//请求失败的回调,在cancel connection的时候,自己也主动调用了
- (void)connection:(NSURLConnection __unused *)connection
  didFailWithError:(NSError *)error
{
    //给self.error赋值,之后完成Block会根据这个error去判断这次请求是成功还是失败
    self.error = error;

    //关闭outputStream
    [self.outputStream close];

    //如果响应数据已经有了,则outputStream置为nil
    if (self.responseData) {
        self.outputStream = nil;
    }
    self.connection = nil;
    [self finish];
}
5、通过setCompletionBlockWithSuccess方法接收responseData

此时AFURLConnectionOperation中的数据请求完了,但数据还在self.responseData中,那么它是怎么来到我们这的呢?在AFURLConnectionOperation子类AFHTTPRequestOperation中,有个setCompletionBlockWithSuccess方法:

[operation setCompletionBlockWithSuccess:success failure:failure];

- (void)setCompletionBlockWithSuccess:(void (^)(AFHTTPRequestOperation *operation, id responseObject))success
                              failure:(void (^)(AFHTTPRequestOperation *operation, NSError *error))failure
{
    // completionBlock is manually nilled out in AFURLConnectionOperation to break the retain cycle.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles" 忽略循环引用的警告
#pragma clang diagnostic ignored "-Wgnu" 忽略?:

    // 设置completionBlock,当数据请求完成,就会调用这个Block,然后在这个Block中调用传过来的成功或者失败的Block
    self.completionBlock = ^{
        // 类似AF3.x,可以自定义一个完成组和完成队列,数据在其中回调出去
        if (self.completionGroup) {
            dispatch_group_enter(self.completionGroup);
        }

        dispatch_async(http_request_operation_processing_queue(), ^{
            if (self.error) {// 否则为失败,把error信息传出
                if (failure) {
                    dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
                        failure(self, self.error);
                    });
                }
            } else {// 如果error为空,说明请求成功,把数据传出去
                id responseObject = self.responseObject;
                if (self.error) {
                    if (failure) {
                        dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
                            failure(self, self.error);
                        });
                    }
                } else {
                    if (success) {
                        dispatch_group_async(self.completionGroup ?: http_request_operation_completion_group(), self.completionQueue ?: dispatch_get_main_queue(), ^{
                            success(self, responseObject);
                        });
                    }
                }
            }

            if (self.completionGroup) {
                dispatch_group_leave(self.completionGroup);
            }
        });
    };
#pragma clang diagnostic pop
}

因为self持有了completionBlock,而completionBlock内部持有self,造成了循环引用。那么AF是如何解决这个循环引用的呢?AFURLConnectionOperation重写了setCompletionBlock方法:

//重写setCompletionBlock
- (void)setCompletionBlock:(void (^)(void))block {
    [self.lock lock];
    if (!block) {
        [super setCompletionBlock:nil];
    } else {
        __weak __typeof(self)weakSelf = self;
        [super setCompletionBlock:^ {
            __strong __typeof(weakSelf)strongSelf = weakSelf;

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
            //看有没有自定义的完成组,否则用AF的组
            dispatch_group_t group = strongSelf.completionGroup ?: url_request_operation_completion_group();
            //看有没有自定义的完成queue,否则用主队列
            dispatch_queue_t queue = strongSelf.completionQueue ?: dispatch_get_main_queue();
#pragma clang diagnostic pop
            
            //调用设置的Block,在这个组和队列中
            dispatch_group_async(group, queue, ^{
                block();
            });

            //结束时候置nil,防止循环引用
            //在我们设置的block调用结束的时候,主动的调用setCompletionBlock把Block置空,这样循环引用不复存在了
            dispatch_group_notify(group, url_request_operation_completion_queue(), ^{
                [strongSelf setCompletionBlock:nil];
            });
        }];
    }
    [self.lock unlock];
}
6、数据解析

AFHTTPRequestOperation重写了responseObjectget方法,把数据按照我们需要的类型(jsonxml等等)进行解析。

- (id)responseObject {
    [self.lock lock];
    if (!_responseObject && [self isFinished] && !self.error) {
        NSError *error = nil;

        //做数据解析
        self.responseObject = [self.responseSerializer responseObjectForResponse:self.response data:self.responseData error:&error];

        if (error) {
            self.responseSerializationError = error;
        }
    }
    [self.lock unlock];
    return _responseObject;
}

补充一点:AFSecurityPolicyAFURLConnectionOperationhttps认证的代理中被调用。

7、问题:为什么AF2.x需要一条常驻线程?

如果使用NSURLConnection,为了获取请求结果有以下三种选择:

  • 在主线程调用异步接口。
  • 每一个请求用一个线程,对应一个runloop,然后等待结果回调。
  • 只用一条线程,一个runloop,所有结果回调在这个线程上。

AF选择的是第3种方式,创建了一条常驻线程专门处理所有请求的回调事件。
那么可以使用排除法,解释不选择另外两种方式的原因。

方案一不可行的原因1:
如果所有的请求都在主线程中异步调用,会这样写

 [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES] 

NSURLConnection的回调会被放在主线程中NSDefaultRunLoopMode中,这样我们在其它类似UITrackingRunLoopMode模式下是得不到网络请求的结果的,这显然不是我们想要的。那么势必需要调用scheduleInRunLoop方法把它加入NSRunLoopCommonModes中。但试想如果有大量的网络请求,同时回调回来,就会影响到UI体验。

[connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; 

方案一不可行的原因2:如果请求数据返回,势必要进行数据解析,解析成需要的格式,而这些解析都在主线程中做,给主线程增加额外的负担。又或者回调回来开辟一个新的线程去做数据解析,那么有n个请求回来开辟n条线程带来的性能损耗,以及线程间切换带来的损耗,也是一笔更大的开销。

方案二不可行的原因:如果一开始就开辟n条线程去做请求,然后设置runloop保活住线程,等待结果回调。这样会造成为了等待不确定的请求结果,阻塞住线程,白白浪费n条线程的开销。


四、AFNetworking的作用总结

相对于AFNetworking2.xAFNetworking3.x确实没那么有用了。AFNetworking之前的核心作用就是帮我们去调度所有的请求,但是最核心地方却被苹果的NSURLSession给借鉴过去了,嗯...是借鉴。这些请求的调度,现在完全由NSURLSession给做了,导致AFNetworking3.x的作用被大大的削弱了。

除此之外,它的作用如下:

  • 帮我们做了各种请求方式request的拼接。想想如果我们使用NSURLSession去做请求,是不是还得自己去考虑各种请求方式下拼接参数的问题。

  • 帮我们做了一些公用参数(session级别的),和一些私用参数(task级别的)的分离。它用Block的形式,支持我们自定义一些代理方法,如果没有实现的话,AF还帮我们做了一些默认的处理。相比之下,如果我们用NSURLSession的话,还得参照AF这么一套代理转发的架构模式去封装。

  • 帮我们做了自定义的https认证处理。

  • 对于请求到的数据,AF帮我们做了各种格式的数据解析,并且支持我们设置自定义的code范围,自定义的数据解析方式。如果不在这些范围中,则直接调用失败block

  • 对于成功和失败的回调处理。AF帮我们在数据请求到回调给用户之间,做了各种错误的判断,保证了成功和失败的回调,界限清晰,AF也帮我们做了很多的容错处理。而NSURLSession呢?只给了一个完成的回调,我们得多做多少判断,才能拿到一个确定能正常显示的数据?

  • 帮我们绕开了很多的坑,比如系统内部并行创建task导致id不唯一等......

  • 如果我们需要一些UIKit的扩展,AF则提供了最稳定,而且最优化实现方式。比如AFImageDownloader对于组图片之间的下载协调,以及缓存使用的之间线程调度,对于线程,锁,以及性能各方面权衡,找出最优化的处理方式......扪心自问,平凡的我们使用NSURLSession去做,能做到几分。


五、AFSecurityPolicy实现https认证需求

  • AF的验证方式不是必须的,但是对有特殊验证需求的用户确是必要的。AF可以让你在系统验证证书之前,就去自主验证。如果自己验证不正确,直接取消网络请求,否则验证通过则继续进行系统验证。
  • 系统的验证,首先是去系统的根证书找,看是否有能匹配服务端的证书,如果匹配,则验证成功,返回https的安全数据。如果不匹配则去判断ATS是否关闭,如果关闭,则返回https不安全连接的数据。如果开启ATS,则拒绝这个请求,请求失败。

可以自己生成一个自签名的证书或者用百度地址等做请求,然后设置AFSecurityPolicy不同参数,打断点,一步步的看AF是如何去调用函数作证书验证的。

- (void)testSecurityPolicy
{
    NSString *urlstr = @"https://112.124.34.197:9000/users/bar";
    AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
    manager.securityPolicy        = [self securityPolicy];
    manager.responseSerializer = [AFHTTPResponseSerializer serializer];
    [manager GET:urlstr parameters:nil progress:^(NSProgress * _Nonnull downloadProgress) {
        
    } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
        
        NSLog(@"success--%@",responseObject);

    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
        NSLog(@"fail--%@",error);

    }];
}

- (AFSecurityPolicy *)securityPolicy
{
    // .crt --->.cer
    NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"https" ofType:@"cer"];
    NSData   *data    = [NSData dataWithContentsOfFile:cerPath];
    NSLog(@"%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
    NSSet    *cerSet  = [NSSet setWithObject:data];
    
    AFSecurityPolicy *security = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate withPinnedCertificates:cerSet];
    [AFSecurityPolicy defaultPolicy];
    
    security.allowInvalidCertificates = YES;
    security.validatesDomainName      = NO;
    return security;
}

1、NSURLSessionDelegate中的代理方法: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);
    }
}
  1. 首先指定了https为默认的认证方式。
  2. 判断有没有自定义Block:sessionDidReceiveAuthenticationChallenge,有的话使用自定义Block生成一个认证方式,并且给credential即需要接受认证的证书赋值,然后直接调用completionHandler,去根据这两个参数,执行系统的认证。
  3. 倘若没有自定义Block,判断如果服务端的认证方法要求是NSURLAuthenticationMethodServerTrust,则只需要验证服务端证书是否安全,即https的单向认证。这是AF默认处理的认证方式,其他的认证方式,只能由我们自定义Block的实现。
  4. 接着执行了AFSecurityPolicy相关的一个方法,做了一个AF内部的一个https认证:
[self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host])

AF默认的处理是,如果这行返回NO,则说明AF内部认证失败,则取消https认证,即取消请求。倘若返回YES则进入if代码块,用服务器返回的一个serverTrust去生成了一个认证证书。这个serverTrust是服务器传过来的,里面包含了服务器的证书信息,是用来本地客户端去验证该证书是否合法用的。

  1. 接着如果有证书,则用证书认证方式,否则还是用默认的验证方式。最后调用completionHandler传递认证方式和要认证的证书,去做系统根证书验证。

2、AFSecurityPolicy实现https认证

a、创建AFSecurityPolicy

创建一个securityPolicy

AFSecurityPolicy *policy = [AFSecurityPolicy defaultPolicy];

+ (instancetype)defaultPolicy {
    AFSecurityPolicy *securityPolicy = [[self alloc] init];
    //设置https验证模式为不验证
    securityPolicy.SSLPinningMode = AFSSLPinningModeNone;
    return securityPolicy;
}

对于AFSecurityPolicy,一共有4个重要的属性:

//https验证模式
@property (readonly, nonatomic, assign) AFSSLPinningMode SSLPinningMode;
//可以去匹配服务端证书验证的证书
@property (nonatomic, strong, nullable) NSSet <NSData *> *pinnedCertificates;
//是否支持非法的证书(例如自签名证书)
@property (nonatomic, assign) BOOL allowInvalidCertificates;
//是否去验证证书域名是否匹配
@property (nonatomic, assign) BOOL validatesDomainName;

AFSSLPinningMode提供了3种验证方式:

typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
    //不验证
    AFSSLPinningModeNone,
    //只验证公钥
    AFSSLPinningModePublicKey,
    //验证证书
    AFSSLPinningModeCertificate,
};

代理https认证的方法中的下面这行关键代码,传了两个参数进去,一个是SecTrustRef类型的serverTrust,它装了服务器端需要验证的证书的基本信息、公钥等。另一个是服务器的域名,用于域名验证。

// 4. 只需要验证服务端证书是否安全(即https的单向认证,这是AF默认处理的认证方式,其他的认证方式,只能由我们自定义Block的实现
if ([self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
b、evaluateServerTrust:方法的内部实现

掘金下evaluateServerTrust:方法的内部实现。这个方法是AFSecurityPolicy最核心的方法,完成了服务端的证书的信任评估。

// 根据severTrust和domain来检查服务器端发来的证书是否可信
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
                  forDomain:(NSString *)domain
{

    //后两者和allowInvalidCertificates为真的设置矛盾,说明这次验证是不安全的
    //如果有服务器域名、设置了允许信任无效或者过期证书(自签名证书)、需要验证域名、没有提供证书或者不验证证书,返回NO
    //因为要验证域名,所以必须不能是后者两种:AFSSLPinningModeNone或者添加到项目里的证书为0个
    if (domain && self.allowInvalidCertificates && self.validatesDomainName && (self.SSLPinningMode == AFSSLPinningModeNone || [self.pinnedCertificates count] == 0)) {
        
        //如果想要实现自签名的HTTPS访问成功,必须设置pinnedCertificates,且不能使用defaultPolicy
        NSLog(@"In order to val idate a domain name for self signed certificates, you MUST use pinning.");
        //不受信任,返回
        return NO;
    }
    
    //用来装验证策略
    NSMutableArray *policies = [NSMutableArray array];
    //生成验证策略。如果要验证域名,就以域名为参数创建一个策略,否则创建默认的basicX509策略
    if (self.validatesDomainName) {
        //如果需要验证domain,那么就使用SecPolicyCreateSSL函数创建验证策略
        //其中第一个参数为true表示为服务器证书验证创建一个策略,需要验证整个SSL证书链
        //第二个参数传入domain,用于判断整个证书链上叶子节点表示的那个domain是否和此处传入domain一致,即匹配主机名和证书上的主机名
        [policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
    } else {
        //如果不需要验证domain,就使用默认的BasicX509验证策略
        [policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
    }

    // 为serverTrust设置验证策略,用策略对serverTrust进行评估
    SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);

    //如果是AFSSLPinningModeNone(不做本地证书验证,从客户端系统中的受信任颁发机构 CA 列表中去验证服务端返回的证书)
    if (self.SSLPinningMode == AFSSLPinningModeNone) {
        //不使用ssl pinning 但允许自建证书,直接返回YES;否则进行第二个条件判断,去客户端系统根证书里找是否有匹配的证书,验证serverTrust是否可信,直接返回YES
        return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
    } else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
        //如果验证无效AFServerTrustIsValid,而且allowInvalidCertificates不允许自签,返回NO
        return NO;
    }

    //判断SSLPinningMode
    switch (self.SSLPinningMode) {
        //上一部分已经判断过了,如果执行到这里的话就返回NO
        case AFSSLPinningModeNone:
        default:
            return NO;
        
        //验证证书类型
        //这个模式表示用证书绑定(SSL Pinning)方式验证证书,需要客户端保存有服务端的证书拷贝
        //注意客户端保存的证书存放在self.pinnedCertificates中
        case AFSSLPinningModeCertificate: {
            
            // 全部校验(nsbundle .cer)
            NSMutableArray *pinnedCertificates = [NSMutableArray array];
            
            //把证书data,用系统api转成 SecCertificateRef 类型的数据
            //SecCertificateCreateWithData函数对原先的pinnedCertificates做一些处理,保证返回的证书都是DER编码的X.509证书
            for (NSData *certificateData in self.pinnedCertificates) {
                [pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
            }
            
            //将pinnedCertificates设置成需要参与验证的Anchor Certificate锚点证书
            //通过SecTrustSetAnchorCertificates设置了参与校验锚点证书之后
            //假如验证的数字证书是这个锚点证书的子节点,即验证的数字证书是由锚点证书对应CA或子CA签发的,或是该证书本身,则信任该证书
            //具体就是调用SecTrustEvaluate来验证
            //serverTrust是服务器来的验证,有需要被验证的证书
            //把本地证书设置为根证书
            SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates);
            
            //自签在之前是验证通过不了的,在这一步,把我们自己设置的证书加进去之后,就能验证成功了。
            //再去调用之前的serverTrust去验证该证书是否有效,有可能:经过这个方法过滤后,serverTrust里面的pinnedCertificates被筛选到只有信任的那一个证书
            //评估指定证书和策略的信任度(由系统默认可信或者由用户选择可信)
            if (!AFServerTrustIsValid(serverTrust)) {
                return NO;
            }

            //注意,这个方法和我们之前的锚点证书没关系了,是去从我们需要被验证的服务端证书,去拿证书链。
            //服务器端的证书链,注意此处返回的证书链顺序是从叶节点到根节点
            //所有服务器返回的证书信息
            NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust);
            
            //reverseObjectEnumerator逆序遍历
            //服务器端的证书链,注意此处返回的证书链顺序是从叶节点到根节点
            //证书链由两个环节组成—信任锚(CA 证书)环节和已签名证书环节,就是根证书和根据根证书签名派发得到的证书
            for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) {
                //如果我们的证书中,有一个和它证书链中的证书匹配的,就返回YES
                //是否本地包含相同的data
                if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
                    return YES;
                }
            }
            //没有匹配的
            return NO;
        }
            
        //公钥验证 AFSSLPinningModePublicKey模式同样是用证书绑定(SSL Pinning)方式验证,客户端要有服务端的证书拷贝
        //只是验证时只验证证书里的公钥,不验证证书的有效期等信息
        //只要公钥是正确的,就能保证通信不会被窃听,因为中间人没有私钥,无法解开通过公钥加密的数据
        case AFSSLPinningModePublicKey: {
            NSUInteger trustedPublicKeyCount = 0;
            
            // 从serverTrust中取出服务器端传过来的所有可用的证书,并依次得到相应的公钥
            NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust);
            //遍历服务端公钥
            for (id trustChainPublicKey in publicKeys) {
                //遍历本地公钥
                for (id pinnedPublicKey in self.pinnedPublicKeys) {
                    //判断如果相同 trustedPublicKeyCount+1
                    if (AFSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge SecKeyRef)pinnedPublicKey)) {
                        trustedPublicKeyCount += 1;
                    }
                }
            }
            return trustedPublicKeyCount > 0;
        }
    }
    
    return NO;
}

代码的注释很多,这一块确实比枯涩。

SecTrustRef:

  • CoreFoundation类型,是一个容器,装了服务器端需要验证的证书的基本信息、公钥等等,不仅如此,它还可以装一些评估策略,还有客户端的锚点证书,这个客户端的证书,可以用来和服务端的证书去匹配验证的。每一个SecTrustRef对象包含多个SecCertificateRefSecPolicyRef。其中 SecCertificateRef可以使用DER进行表示。

  • 用于对服务器端传来的X.509证书评估。数字证书的签发机构CA,在接收到申请者的资料后进行核对并确定信息的真实有效,然后就会制作一份符合X.509标准的文件。证书中的证书内容包含的持有者信息和公钥等都是由申请者提供的,而数字签名则是CA机构对证书内容进行hash加密后得到的,而这个数字签名就是我们验证证书是否是有可信CA签发的数据。

关于__bridge,有以下三种类型:

  • __bridge:CF和OC对象转化时只涉及对象类型不涉及对象所有权的转化。
  • __bridge_transfer:常用在将CF对象转换成OC对象时,将CF对象的所有权交给OC对象,此时ARC就能自动管理该内存。
  • __bridge_retained:(与__bridge_transfer相反)常用在将OC对象转换成CF对象时,将OC对象的所有权交给CF对象来管理。

self.pinnedPublicKeys的初始化:

// 此函数设置securityPolicy中的pinnedCertificates属性
// 注意还将对应的self.pinnedPublicKeys属性也设置了,该属性表示的是对应证书的公钥(与pinnedCertificates中的证书是一一对应的)
- (void)setPinnedCertificates:(NSSet *)pinnedCertificates {
    _pinnedCertificates = pinnedCertificates;
    
    //获取对应公钥集合
    if (self.pinnedCertificates) {
        //创建公钥集合
        NSMutableSet *mutablePinnedPublicKeys = [NSMutableSet setWithCapacity:[self.pinnedCertificates count]];
        
        //从证书中拿到公钥。
        for (NSData *certificate in self.pinnedCertificates) {
            id publicKey = AFPublicKeyForCertificate(certificate);
            if (!publicKey) {
                continue;
            }
            [mutablePinnedPublicKeys addObject:publicKey];
        }
        self.pinnedPublicKeys = [NSSet setWithSet:mutablePinnedPublicKeys];
    } else {
        self.pinnedPublicKeys = nil;
    }
}

AF重写了设置证书的set方法,并同时把证书中每个公钥放在了self.pinnedPublicKeys中。

这个方法中关联了一系列的函数,下面按照调用顺序一一列出来:
函数一:AFServerTrustIsValid

// 判断serverTrust是否有效,即返回的服务器是否是可信任的
static BOOL AFServerTrustIsValid(SecTrustRef serverTrust) {
    //默认无效
    BOOL isValid = NO;
    //用来装验证结果,枚举
    SecTrustResultType result;
    
    //__Require_noErr_Quiet 用来判断前者是0还是非0,如果0则表示没错,就跳到后面的表达式所在位置去执行,否则表示有错就继续往下执行
    //SecTrustEvaluate系统用于评估证书是否可信的函数,去系统根目录找,然后把结果赋值给result。评估结果匹配,返回0,否则出错返回非0
    __Require_noErr_Quiet(SecTrustEvaluate(serverTrust, &result), _out);

    /* 比如有些弹窗,用户点击了信任
     1.评估得到了用户认可,显示地决定信任该证书,result 成功是 kSecTrustResultProceed 失败是kSecTrustResultDeny
     2.系统隐式地信任这个证书,result 成功是 kSecTrustResultUnspecified 失败是kSecTrustResultRecoverableTrustFailure
     */
    // 只有两种结果能设置为有效,isValid = 1
    isValid = (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);
    
    //out函数块,如果为SecTrustEvaluate,返回非0,则评估出错,则isValid为NO
_out:
    return isValid;
}

函数二、三类似:获取serverTrust证书链证书 |获取serverTrust证书链公钥。

//获取证书链
//获取服务器返回的所有证书
static NSArray * AFCertificateTrustChainForServerTrust(SecTrustRef serverTrust) {
    //使用SecTrustGetCertificateCount函数获取到serverTrust中需要评估的证书链中的证书数目,并保存到certificateCount中
    CFIndex certificateCount = SecTrustGetCertificateCount(serverTrust);
    
    //创建数组
    //根据SecTrustRef中的信息得到count
    NSMutableArray *trustChain = [NSMutableArray arrayWithCapacity:(NSUInteger)certificateCount];
    
    //使用SecTrustGetCertificateAtIndex函数获取到证书链中的每个证书,并添加到trustChain中,最后返回trustChain
    for (CFIndex i = 0; i < certificateCount; i++) {
        //取到对应的SecCertificateRef
        SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, i);
        //转为data
        [trustChain addObject:(__bridge_transfer NSData *)SecCertificateCopyData(certificate)];
    }

    return [NSArray arrayWithArray:trustChain];
}

// 取出所有证书中的公钥
// 从serverTrust中取出服务器端传过来的所有可用的证书,并依次得到相应的公钥
static NSArray * AFPublicKeyTrustChainForServerTrust(SecTrustRef serverTrust) {
    //接下来的一小段代码和上面AFCertificateTrustChainForServerTrust函数的作用基本一致,都是为了获取到serverTrust中证书链上的所有证书,并依次遍历,取出公钥
    
    //安全策略
    SecPolicyRef policy = SecPolicyCreateBasicX509();
    //取到count
    CFIndex certificateCount = SecTrustGetCertificateCount(serverTrust);
    
    NSMutableArray *trustChain = [NSMutableArray arrayWithCapacity:(NSUInteger)certificateCount];
    //遍历serverTrust里证书的证书链
    for (CFIndex i = 0; i < certificateCount; i++) {
        //从证书链取证书
        SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, i);
        //数组
        SecCertificateRef someCertificates[] = {certificate};
        //CF数组
        CFArrayRef certificates = CFArrayCreate(NULL, (const void **)someCertificates, 1, NULL);
        
        //到了取出公钥的步骤了
        
        //根据给定的certificates和policy来生成一个trust对象
        //不成功就跳到_out
        SecTrustRef trust;
        __Require_noErr_Quiet(SecTrustCreateWithCertificates(certificates, policy, &trust), _out);

        //使用SecTrustEvaluate来评估上面构建的trust
        //评估失败跳到 _out
        SecTrustResultType result;
        __Require_noErr_Quiet(SecTrustEvaluate(trust, &result), _out);
        
        //如果该trust符合X.509证书格式,那么先使用SecTrustCopyPublicKey获取到trust的公钥,再将此公钥添加到trustChain中
        [trustChain addObject:(__bridge_transfer id)SecTrustCopyPublicKey(trust)];

    _out:
        //释放资源
        if (trust) {
            CFRelease(trust);
        }

        if (certificates) {
            CFRelease(certificates);
        }

        continue;
    }
    CFRelease(policy);
    
    // 返回对应的一组公钥
    return [NSArray arrayWithArray:trustChain];
}

这个获取的证书排序,是从证书链的叶节点,到根节点的。

函数四:判断公钥是否相同。

//这个方法是比较两个key是否相等,如果是ios/watch/tv直接使用isEqual方法判断二者地址就可进行比较
//判断两个公钥是否相同
static BOOL AFSecKeyIsEqualToKey(SecKeyRef key1, SecKeyRef key2) {
#if TARGET_OS_IOS || TARGET_OS_WATCH || TARGET_OS_TV
    //iOS 判断二者地址
    return [(__bridge id)key1 isEqual:(__bridge id)key2];
#else
    return [AFSecKeyGetData(key1) isEqual:AFSecKeyGetData(key2)];
#endif
}

方法适配了各种运行环境,做了匹配的判断。

最后列出验证过程中调用过的系统原生函数。

//1 创建一个验证SSL的策略,第一个参数true则表示验证整个证书链,第二个参数传入domain,用于判断整个证书链上叶子节点表示的那个domain是否和此处传入domain一致
SecPolicyCreateSSL(<#Boolean server#>, <#CFStringRef  _Nullable hostname#>)

//2 默认的BasicX509验证策略,不验证域名
SecPolicyCreateBasicX509();

//3 为serverTrust设置验证策略,即告诉客户端如何验证serverTrust
SecTrustSetPolicies(<#SecTrustRef  _Nonnull trust#>, <#CFTypeRef  _Nonnull policies#>)

//4 验证serverTrust,并且把验证结果返回给第二参数 result
SecTrustEvaluate(<#SecTrustRef  _Nonnull trust#>, <#SecTrustResultType * _Nullable result#>)

//5 判断前者errorCode是否为0,为0则跳到exceptionLabel处执行代码
__Require_noErr(<#errorCode#>, <#exceptionLabel#>)

//6 根据证书data,去创建SecCertificateRef类型的数据。
SecCertificateCreateWithData(<#CFAllocatorRef  _Nullable allocator#>, <#CFDataRef  _Nonnull data#>)

//7 给serverTrust设置锚点证书,即如果以后再次去验证serverTrust,会从锚点证书去找是否匹配。
SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates);

//8 拿到证书链中的证书个数
CFIndex certificateCount = SecTrustGetCertificateCount(serverTrust);

//9 去取得证书链中对应下标的证书
SecTrustGetCertificateAtIndex(serverTrust, i)

//10 根据证书获取公钥
SecTrustCopyPublicKey(trust)

3、自签名的证书

如果你用的是付费的公信机构颁发的证书,标准的https,那么无论你用的是AF还是NSUrlSession,什么都不用做,代理方法也不用实现,你的网络请求就能正常完成。

如果你用的是自签名的证书:

  • 首先你需要在plist文件中,设置可以返回不安全的请求(关闭该域名的ATS)。
  • 其次,如果是NSUrlSesion,那么需要在代理方法实现如下:
- (void)URLSession:(NSURLSession *)session
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
    __block NSURLCredential *credential = nil;
    
    credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
    // 确定挑战的方式
    if (credential) {
        //证书挑战 则跑到这里
        disposition = NSURLSessionAuthChallengeUseCredential;
    }
    //完成挑战
    if (completionHandler) {
        completionHandler(disposition, credential);
    }
}

上述就是AF的相对于自签证书的实现的简化版。

如果是AF,你则需要设置policy

//允许自签名证书,必须的
policy.allowInvalidCertificates = YES;

//是否验证域名的CN字段
//不是必须的,但是如果写YES,则必须导入证书。
policy.validatesDomainName = NO;

还可以根据需求去验证证书或者公钥,前提是把自签的服务端证书,或者自签的CA根证书导入到项目中,并且如下设置证书:

NSString *certFilePath = [[NSBundle mainBundle] pathForResource:@"AFUse_server.cer" ofType:nil];
NSData *certData = [NSData dataWithContentsOfFile:certFilePath];
NSSet *certSet = [NSSet setWithObjects:certData,certData, nil]; 
policy.pinnedCertificates = certSet;

这样你就可以使用AF的不同AFSSLPinningMode去验证了。


六、UIKit扩展与缓存实现

见下篇 IOS框架:AFNetworking(下)


Demo

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

参考文献

AFNetworking到底做了什么?
AFNetworking之于https认证

相关文章

  • IOS框架:AFNetworking(中)

    原创:知识点总结性文章创作不易,请珍惜,之后会持续更新,不断完善个人比较喜欢做笔记和写总结,毕竟好记性不如烂笔头哈...

  • 2018-08-06

    #AFNetworking源码阅读系列 一 前言: AFNetWorking一款轻量级网络请求开源框架,基于iOS...

  • AFNetworking多线程分析

    AFNetworking多线程分析 AFNetworking是目前最常用的iOS的网络开发框架。它是对Apple系...

  • AFNetworking刨根问底

    AFNetworking网络框架在iOS开发中的霸主地位已经根深蒂固,本篇将基于3.2.1版本对框架的几个核心模块...

  • AFNetworking(3.10)源码解析(一):基本架构

    基本架构 AFNetworking是iOS著名的网络通信框架。 导入AFNetworking到工程里后,打开AFN...

  • AFNetworking3.0源码浅析

    AFNetworking是iOS开发中基本都会用到的一个网络框架;阅读其源码对理解iOS的网络开发很有帮助。 概述...

  • iOS AFNetworking 源码阅读一

    大名鼎鼎的AFNetWorking,做iOS开发的人都知道吧。AFNetWorking一款轻量级网络请求开源框架,...

  • 网络编程二

    网络编程二 一、AFNetworking第三方网络框架 AFNetworking2.0使用简单,对最新的iOS特性...

  • AFNetWorking使用(一)

    AFNetworking可以说是iOS中最常用的开源框架了,在GitHub中的地址https://github.c...

  • 解读AF之AFHTTPSessionManager

    介绍 AFNetworking作为iOS的网络请求框架,其内部是对iOS提供的NSURLSession进行的封装,...

网友评论

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

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