原创:知识点总结性文章
创作不易,请珍惜,之后会持续更新,不断完善
个人比较喜欢做笔记和写总结,毕竟好记性不如烂笔头哈哈,这些文章记录了我的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、总结请求图片、缓存、设置图片的流程
- 1、AFNetworkActivityIndicatorManager :网络请求时状态栏的小菊花
- Demo
- 参考文献
b、NSURLSessionDelegate转发到AF自定义的deleagate
代理1:didCompleteWithError
AF实现的代理!被从urlsession
那转发到这。
1、生成了一个存储这个task
相关信息的字典:userInfo
。将responseSerializer
和downloadFileURL
或data
存到userInfo
里面,这个字典是用来作为发送任务完成的通知的参数。 userInfo
中的key
值如下:
-
AFNetworkingTaskDidCompleteResponseDataKey
:session
存储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
具体如何解析数据,之后遇见了再看吧。
如果没有实现自定义的completionGroup
和completionQueue
,那么就使用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
可以设置各自下载路径。
AFHttpManager
的download
方法:
[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) {
}];
其中return
的path
就是对应的这个代理方法里的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
就是Domain
为AFURLResponseSerializationErrorDomain
,code
为NSURLErrorCannotDecodeContentData
。 - 根据初始化的
acceptableStatusCodes
判断状态码是否有效。如果MIME type
不满足,,那么产生的validationError
就是Domain
为AFURLResponseSerializationErrorDomain
,code
为NSURLErrorBadServerResponse
。 - 在
self.acceptableContentTypes
和self.acceptableStatusCodes
这两个判断中,如果都出现错误怎么办呢?这就用到了NSUnderlyingErrorKey
这个字段,它表示一个优先的错误,value
为NSError
对象。
// 判断是不是可接受类型和可接受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;
}
这里传过去的code
和domain
两个参数分别为NSURLErrorCannotDecodeContentData
、AFURLResponseSerializationErrorDomain
,这两个参数是我们之前判断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整个流程和线程的关系
- 一开始初始化
sessionManager
的时候,一般都是在主线程。 - 然后调用
get
或者post
等去请求数据,接着会进行request
拼接,AF代理的字典映射,progress
的KVO
添加等,一直到NSUrlSession
的resume
之前,这些准备工作仍旧是在主线程中进行的。 - 接着调用
NSUrlSession
的resume
后,就跑到NSUrlSession
内部去对网络进行数据请求了,在它内部是多线程并发的去请求数据的。 - 紧接着数据请求完成后,回调回来在一开始生成的并发数为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
的思想精髓所在,所以另一个目的就是为了和之前版本保持一致。
- 下一步自己又创建了并发的多线程,去对这些返回数据进行了各种类型的解析。
- 最后我们如果有自定义的
completionQueue
,则在自定义的queue
中回调回来,也就是子线程回调回来,否则就是主队列,主线程中回调结束。
三、AF2.x版本的核心实现
AF2.x是基于NSURLConnection
来封装的,而NSURLConnection
的创建以及数据请求,就被封装在AFURLConnectionOperation
这个类中。所以这个类基本上是AF2.x最底层也是最核心的类。AFHTTPRequestOperation
是继承自AFURLConnectionOperation
,对它父类一些方法做了些封装。AFHTTPRequestOperationManager
则是一个管家,去管理这些这些operation
。
AF2.x整个网络请求的流程如下图:
AF2.x整个网络请求的流程- 最上层的是
AFHTTPRequestOperationManager
,调用它进行get
、post
等各种类型的网络请求 -
AFHTTPRequestOperationManager
去调用AFURLRequestSerialization
做request
参数拼装,通过拼装后的request
生成了一个AFHTTPRequestOperation
实例,接着把AFHTTPRequestOperation
添加到一个NSOperationQueue
中。 -
AFHTTPRequestOperation
拿到request
后,会去调用它的父类AFURLConnectionOperation
的初始化方法,并且把相关参数交给它,除此之外,当父类完成数据请求后,它调用了AFURLResponseSerialization
把数据解析成我们需要的格式(json
、XML
等)。 - 最后就是我们AF最底层的类
AFURLConnectionOperation
,它去进行数据请求,并且如果是https
请求,会在请求的相关代理中,调用AFSecurityPolicy
做https
认证,最后将请求到的数据返回。
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,这个后面会传给operation
,operation
会根据这个值,去返回给代理,系统是否做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
拿到写入后的数据。这个outputStream
在get
方法中被初始化了:
- (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
的代理方法了。
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
从队列中移除,并且调用operation
的completionBlock
,而请求到的数据就是从这个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
重写了responseObject
的get
方法,把数据按照我们需要的类型(json
、xml
等等)进行解析。
- (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;
}
补充一点:AFSecurityPolicy
在AFURLConnectionOperation
中https
认证的代理中被调用。
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.x
,AFNetworking3.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 disposition
和NSURLCredential *credential
(disposition
指定应对这个挑战的方法,而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);
}
}
- 首先指定了
https
为默认的认证方式。 - 判断有没有自定义
Block:sessionDidReceiveAuthenticationChallenge
,有的话使用自定义Block
生成一个认证方式,并且给credential
即需要接受认证的证书赋值,然后直接调用completionHandler
,去根据这两个参数,执行系统的认证。 - 倘若没有自定义
Block
,判断如果服务端的认证方法要求是NSURLAuthenticationMethodServerTrust
,则只需要验证服务端证书是否安全,即https
的单向认证。这是AF默认处理的认证方式,其他的认证方式,只能由我们自定义Block
的实现。 - 接着执行了
AFSecurityPolicy
相关的一个方法,做了一个AF内部的一个https
认证:
[self.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host])
AF默认的处理是,如果这行返回NO,则说明AF内部认证失败,则取消https
认证,即取消请求。倘若返回YES
则进入if
代码块,用服务器返回的一个serverTrust
去生成了一个认证证书。这个serverTrust
是服务器传过来的,里面包含了服务器的证书信息,是用来本地客户端去验证该证书是否合法用的。
- 接着如果有证书,则用证书认证方式,否则还是用默认的验证方式。最后调用
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
对象包含多个SecCertificateRef
和SecPolicyRef
。其中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扩展与缓存实现
Demo
Demo在我的Github上,欢迎下载。
SourceCodeAnalysisDemo
网友评论