一、AFN 的基本构成和作用
AFNetworking由五个模块组成:
网络通信模块(AFURLSessionManager、AFHTTPSessionManger) 网络状态监听模块(Reachability) 网络通信安全策略模块(Security) 网络通信信息序列化/反序列化模块(Serialization) 对于iOS UIKit库的扩展(UIKit)
AFN 的作用
- 各种请求方式request的拼接。想想如果我们用NSURLSession,我们去做请求,是不是还得自己去考虑各种请求方式下,拼接参数的问题。
- 帮我们做了一些公用参数(session级别的),和一些私用参数(task级别的)的分离。它用Block的形式,支持我们自定义一些代理方法,如果没有实现的话,AF还帮我们做了一些默认的处理。而如果我们用NSURLSession的话,还得参照AF这么一套代理转发的架构模式去封装。
- 自定义的https认证处理。
- 对请求到的数据各种格式的数据解析,并且支持我们设置自定义的code范围,自定义的数据方式。
- 对于成功和失败的回调处理。AF帮我们在数据请求到,到回调给用户之间,做了各种错误的判断,保证了成功和失败的回调,界限清晰。在这过程中,AF帮我们做了很多的容错处理。
AFN 核心
核心是网络通信模块 AFURLSessionManager,几乎所有的类都是围绕着这个类在处理业务逻辑。AFURLSessionManager 对 NSURLSession 做了一系列的封装。而其余的四个模块,均是为了配合网络通信或对已有UIKit的一个扩展工具包。
二、AFHTTPSessionManager
AFHTTPSessionManager 初始化
初始化代码
+ (instancetype)manager {
return [[[self class] alloc] initWithBaseURL:nil];
}
- (instancetype)init {
return [self initWithBaseURL:nil];
}
- (instancetype)initWithBaseURL:(NSURL *)url {
return [self initWithBaseURL:url sessionConfiguration:nil];
}
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
return [self initWithBaseURL:nil sessionConfiguration:configuration];
}
- (instancetype)initWithBaseURL:(NSURL *)url
sessionConfiguration:(NSURLSessionConfiguration *)configuration
{
self = [super initWithSessionConfiguration:configuration];
if (!self) {
return nil;
}
// Ensure terminal slash for baseURL path, so that NSURL +URLWithString:relativeToURL: works as expected
if ([[url path] length] > 0 && ![[url absoluteString] hasSuffix:@"/"]) {
url = [url URLByAppendingPathComponent:@""];
}
self.baseURL = url;
self.requestSerializer = [AFHTTPRequestSerializer serializer];
self.responseSerializer = [AFJSONResponseSerializer serializer];
return self;
}
复制代码
初始化最终都调用到 - (instancetype)initWithBaseURL:(NSURL *)url sessionConfiguration:(NSURLSessionConfiguration *)configuration
方法,该方法中完成:
- 其实初始化方法都调用父类(AFURLSessionManager)的初始化方法
- baseURL存了起来
- 生成了一个请求序列对象和一个响应序列对象
AFHTTPSessionManager 发起网络请求
- (NSURLSessionDataTask *)dataTaskWithHTTPMethod:(NSString *)method
URLString:(NSString *)URLString
parameters:(id)parameters
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgress
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress
success:(void (^)(NSURLSessionDataTask *, id))success
failure:(void (^)(NSURLSessionDataTask *, NSError *))failure
{
NSError *serializationError = nil;
//把参数,还有各种东西转化为一个request
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
if (serializationError) {
if (failure) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
//如果解析错误,直接返回
dispatch_async(self.completionQueue ?: dispatch_get_main_queue(), ^{
failure(nil, serializationError);
});
#pragma clang diagnostic pop
}
return nil;
}
__block NSURLSessionDataTask *dataTask = nil;
dataTask = [self dataTaskWithRequest:request
uploadProgress:uploadProgress
downloadProgress:downloadProgress
completionHandler:^(NSURLResponse * __unused response, id responseObject, NSError *error) {
if (error) {
if (failure) {
failure(dataTask, error);
}
} else {
if (success) {
success(dataTask, responseObject);
}
}
}];
return dataTask;
}
复制代码
NSMutableURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:URLString relativeToURL:self.baseURL] absoluteString] parameters:parameters error:&serializationError];
:
生成过程详见AFURLRequestSerialization
-> request 生成步骤。self.completionQueue
: 这是一个GCD的Queue,假如我们自定义了,那么从这个Queue中回调结果,否则从主队列回调。dataTask
的生成详见 AFURLSessionManager -> NSURLSessionDataTask 的生成
三、AFURLSessionManager
AFURLSessionManager的初始化方法
初始化代码
- (instancetype)init {
return [self initWithSessionConfiguration:nil];
}
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
self = [super init];
if (!self) {
return nil;
}
if (!configuration) {
configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
}
self.sessionConfiguration = configuration;
self.operationQueue = [[NSOperationQueue alloc] init];
//queue并发线程数设置为1
self.operationQueue.maxConcurrentOperationCount = 1;
//注意代理,代理的继承,实际上NSURLSession去判断了,你实现了哪个方法会去调用,包括子代理的方法!
self.session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];
//各种响应转码
self.responseSerializer = [AFJSONResponseSerializer serializer];
//设置默认安全策略
self.securityPolicy = [AFSecurityPolicy defaultPolicy];
#if !TARGET_OS_WATCH
self.reachabilityManager = [AFNetworkReachabilityManager sharedManager];
#endif
// 设置存储NSURL task与AFURLSessionManagerTaskDelegate的词典(重点,在AFNet中,每一个task都会被匹配一个AFURLSessionManagerTaskDelegate 来做task的delegate事件处理) ===============
self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];
// 设置AFURLSessionManagerTaskDelegate 词典的锁,确保词典在多线程访问时的线程安全
self.lock = [[NSLock alloc] init];
self.lock.name = AFURLSessionManagerLockName;
// 置空task关联的代理
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {
for (NSURLSessionDataTask *task in dataTasks) {
[self addDelegateForDataTask:task uploadProgress:nil downloadProgress:nil completionHandler:nil];
}
for (NSURLSessionUploadTask *uploadTask in uploadTasks) {
[self addDelegateForUploadTask:uploadTask progress:nil completionHandler:nil];
}
for (NSURLSessionDownloadTask *downloadTask in downloadTasks) {
[self addDelegateForDownloadTask:downloadTask progress:nil destination:nil completionHandler:nil];
}
}];
return self;
}
复制代码
-
self.operationQueue.maxConcurrentOperationCount = 1;
: 这个operationQueue就是我们代理回调的queue。这里把代理回调的线程并发数设置为1了。 -
[self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) { }];
:用来异步的获取当前session的所有未完成的task。其实讲道理来说在初始化中调用这个方法应该里面一个task都不会有。打断点去看里面的数组都是空的,那代码为什么放在这里呢?是为了防止后台回来,重新初始化这个session,一些之前的后台请求任务,导致程序的crash。 -
self.mutableTaskDelegatesKeyedByTaskIdentifier = [[NSMutableDictionary alloc] init];
:用来让每一个请求task和我们自定义的AF代理来建立映射用的。设置存储NSURL task与AFURLSessionManagerTaskDelegate的词典,在AFNet中,每一个task都会被匹配一个AFURLSessionManagerTaskDelegate 来做task的delegate事件处理。
NSURLSessionDataTask 的生成
- (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
completionHandler:(nullable void (^)(NSURLResponse *response, id _Nullable responseObject, NSError * _Nullable error))completionHandler {
__block NSURLSessionDataTask *dataTask = nil;
//第一件事,创建NSURLSessionDataTask,里面适配了Ios8以下taskIdentifiers,函数创建task对象。
//其实现应该是因为iOS 8.0以下版本中会并发地创建多个task对象,而同步有没有做好,导致taskIdentifiers 不唯一…这边做了一个串行处理
url_session_manager_create_task_safely(^{
dataTask = [self.session dataTaskWithRequest:request];
});
[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];
return dataTask;
}
复制代码
-
[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];
: 用于AF自定义代理初始化,并将 AF 代理和 task 建立映射,详见下面。 -
url_session_manager_create_task_safely
函数:static void url_session_manager_create_task_safely(dispatch_block_t block) { if (NSFoundationVersionNumber < NSFoundationVersionNumber_With_Fixed_5871104061079552_bug) { // Fix of bug // Open Radar:http://openradar.appspot.com/radar?id=5871104061079552 (status: Fixed in iOS8) // Issue about:https://github.com/AFNetworking/AFNetworking/issues/2093 //理解下,第一为什么用sync,因为是想要主线程等在这,等执行完,在返回,因为必须执行完dataTask才有数据,传值才有意义。 //第二,为什么要用串行队列,因为这块是为了防止ios8以下内部的dataTaskWithRequest是并发创建的, //这样会导致taskIdentifiers这个属性值不唯一,因为后续要用taskIdentifiers来作为Key对应delegate。 dispatch_sync(url_session_manager_creation_queue(), block); } else { block(); } } static dispatch_queue_t url_session_manager_creation_queue() { static dispatch_queue_t af_url_session_manager_creation_queue; static dispatch_once_t onceToken; //保证了即使是在多线程的环境下,也不会创建其他队列 dispatch_once(&onceToken, ^{ af_url_session_manager_creation_queue = dispatch_queue_create("com.alamofire.networking.session.manager.creation", DISPATCH_QUEUE_SERIAL); }); return af_url_session_manager_creation_queue; } 复制代码
-
为什么将
dataTask = [self.session dataTaskWithRequest:request];
放在url_session_manager_create_task_safely(^{ });
里面?
原来这是为了适配iOS8的以下,创建session的时候,偶发的情况会出现session的属性taskIdentifier这个值不唯一,而这个taskIdentifier是我们后面来映射delegate的key,所以它必须是唯一的。具体原因应该是NSURLSession内部去生成task的时候是用多线程并发去执行的。我们只需要在iOS8以下同步串行的去生成task就可以防止这一问题发生。(这就是sync的应用,有用到的地方了吧,很多东西不是没用,而只是你想不到怎么用)
AF的自定义代理初始化、代理和task建立映射
- (void)addDelegateForDataTask:(NSURLSessionDataTask *)dataTask
uploadProgress:(nullable void (^)(NSProgress *uploadProgress)) uploadProgressBlock
downloadProgress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgressBlock
completionHandler:(void (^)(NSURLResponse *response, id responseObject, NSError *error))completionHandler
{
AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] init];
// AFURLSessionManagerTaskDelegate与AFURLSessionManager建立相互关系
delegate.manager = self;
delegate.completionHandler = completionHandler;
//这个taskDescriptionForSessionTasks用来发送开始和挂起通知的时候会用到,就是用这个值来Post通知,来两者对应
dataTask.taskDescription = self.taskDescriptionForSessionTasks;
// ***** 将AF delegate对象与 dataTask建立关系
[self setDelegate:delegate forTask:dataTask];
// 设置AF delegate的上传进度,下载进度块。
delegate.uploadProgressBlock = uploadProgressBlock;
delegate.downloadProgressBlock = downloadProgressBlock;
}
复制代码
delegate.manager = self;
:
代理把AFURLSessionManager这个类作为属性了。我们可以看到@property (nonatomic, weak) AFURLSessionManager *manager;
这个属性是弱引用的,所以不会存在循环引用的问题。[self setDelegate:delegate forTask:dataTask];
:
把AF代理和task建立映射,存在一个事先声明好的字典里。
加锁的原因是因为这个字典属性是mutable的,是线程不安全的。而我们对这些方法的调用,确实是会在复杂的多线程环境中。- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate forTask:(NSURLSessionTask *)task { //断言,如果没有这个参数,debug下crash在这 NSParameterAssert(task); NSParameterAssert(delegate); //加锁保证字典线程安全 [self.lock lock]; // 将AF delegate放入以taskIdentifier标记的词典中(同一个NSURLSession中的taskIdentifier是唯一的) self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate; // 为AF delegate 设置task 的progress监听,让进度实时的传递 [delegate setupProgressForTask:task]; //添加task开始和暂停的通知 [self addNotificationObserverForTask:task]; [self.lock unlock]; } 复制代码
AFUrlSessionManager对代理的处理
AFUrlSessionManager
对NSUrlSession
代理做了一些公共的处理,而转发到AF自定义代理的3条,则负责把每个task对应的数据回调出去。
-
AF 实现并转发的代理
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error;
: task完成之后的回调,成功和失败都会回调这里。下面详细介绍。- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data;
: 当我们获取到数据就会调用,会被反复调用,请求到的数据就在这被拼装完整。- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
: 下载完成的时候调用。
第一个
didCompleteWithError
和第二个didReceiveData
是NSUrlSession的代理中最重要的两个方法 -
AF 实现但未转发的代理
- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(NSError *)error
: 当前这个session已经失效时,该代理方法被调用。- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session
: 当session中所有已经入队的消息被发送出去后,会调用该代理方法。- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest *))completionHandler
: 被服务器重定向的时候调用。- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task needNewBodyStream:(void (^)(NSInputStream *bodyStream))completionHandler
: 当一个session task需要发送一个新的request body stream到服务器端的时候,调用该代理方法。- 如果task是由uploadTaskWithStreamedRequest:创建的,那么提供初始的request body stream时候会调用该代理方法。
- 因为认证挑战或者其他可恢复的服务器错误,而导致需要客户端重新发送一个含有body stream的request,这时候会调用该代理。
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
: 周期性地通知代理发送到服务器端数据的进度。- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response
: 收到服务器响应后调用。- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask
: 上面的代理如果设置为NSURLSessionResponseBecomeDownload,则会调用这个方法。- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask willCacheResponse:(NSCachedURLResponse *)proposedResponse completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler
: *当task接收到所有期望的数据后,session会调用此代理方法;- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
: 周期性地通知下载进度调用。- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes
: 当下载被取消或者失败后重新恢复下载时调用。- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
: https认证,session-level级别。web服务器接收到客户端请求时,有时候需要先验证客户端是否为正常用户,再决定是够返回真实数据。这种情况称之为服务端要求客户端接收挑战。- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
: https认证。non-session-level级别的认证,而之前的是session-level级别的。
-
AF 未实现的代理
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didBecomeStreamTask:(NSURLSessionStreamTask *)streamTask
task完成之后的回调:
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error;
/*
task完成之后的回调,成功和失败都会回调这里
函数讨论:
注意这里的error不会报告服务期端的error,他表示的是客户端这边的eroor,比如无法解析hostname或者连不上host主机。
*/
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
//根据task去取我们一开始创建绑定的delegate
AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task];
// delegate may be nil when completing a task in the background
if (delegate) {
//把代理转发给我们绑定的delegate
[delegate URLSession:session task:task didCompleteWithError:error];
//转发完移除delegate
[self removeDelegateForTask:task];
}
//自定义Block回调
if (self.taskDidComplete) {
self.taskDidComplete(session, task, error);
}
}
复制代码
- 拿到之前和这个task对应绑定的AF的delegate;
- 转发了调用了AF代理的方法;
- 把这个AF的代理和task的绑定解除了,并且移除了相关的progress和通知;
- 调用了自定义的Blcok;
- 拿到之前和这个task对应绑定的AF的delegate代码
- (AFURLSessionManagerTaskDelegate *)delegateForTask:(NSURLSessionTask *)task { NSParameterAssert(task); AFURLSessionManagerTaskDelegate *delegate = nil; [self.lock lock]; delegate = self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)]; [self.lock unlock]; return delegate; } 复制代码
- 把这个AF的代理和task的绑定解除了,并且移除了相关的progress和通知。
- (void)removeDelegateForTask:(NSURLSessionTask *)task { NSParameterAssert(task); //移除跟AF代理相关的东西 AFURLSessionManagerTaskDelegate *delegate = [self delegateForTask:task]; [self.lock lock]; [delegate cleanUpProgressForTask:task]; [self removeNotificationObserverForTask:task]; [self.mutableTaskDelegatesKeyedByTaskIdentifier removeObjectForKey:@(task.taskIdentifier)]; [self.lock unlock]; } 复制代码
对应的AF的实现:
//AF实现的代理!被从urlsession那转发到这
- (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;
//用来存储一些相关信息,来发送通知用的
__block NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
//存储responseSerializer响应解析对象
userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer;
//Performance Improvement from #2672
//注意这行代码的用法,感觉写的很Nice...把请求到的数据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填数据
if (self.downloadFileURL) {
userInfo[AFNetworkingTaskDidCompleteAssetPathKey] = self.downloadFileURL;
} else if (data) {
userInfo[AFNetworkingTaskDidCompleteResponseDataKey] = data;
}
//错误处理
if (error) {
userInfo[AFNetworkingTaskDidCompleteErrorKey] = error;
//可以自己自定义完成组 和自定义完成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 {
//url_session_manager_processing_queue AF的并行队列
dispatch_async(url_session_manager_processing_queue(), ^{
NSError *serializationError = nil;
//解析数据
responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];
//如果是下载文件,那么responseObject为下载的路径
if (self.downloadFileURL) {
responseObject = self.downloadFileURL;
}
//写入userInfo
if (responseObject) {
userInfo[AFNetworkingTaskDidCompleteSerializedResponseKey] = responseObject;
}
//如果解析错误
if (serializationError) {
userInfo[AFNetworkingTaskDidCompleteErrorKey] = serializationError;
}
//回调结果
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
}
复制代码
主要做了件事:
- 生成了一个存储这个task相关信息的字典:userInfo,这个字典是用来作为发送任务完成的通知的参数。
- 判断了参数error的值,来区分请求成功还是失败。
- 如果成功则在一个AF的并行queue中,去做数据解析等后续操作:(注意AF的优化的点,虽然代理回调是串行的(不明白可以见本文最后)。但是数据解析这种费时操作,确是用并行线程来做的)
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; } 复制代码
- 然后根据我们一开始设置的responseSerializer来解析data。如果解析成功,调用成功的回调,否则调用失败的回调。
- 如果自定义了GCD完成组
completionGroup
和完成队列completionQueue
的话,会在自定义的回调,否则在 AF自己创建的组url_session_manager_completion_group
和主队列回调。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; } 复制代码
- (void)URLSession:(__unused NSURLSession *)session dataTask:(__unused NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data;
- (void)URLSession:(__unused NSURLSession *)session
dataTask:(__unused NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data {
//拼接数据
[self.mutableData appendData:data];
}
复制代码
被NSUrlSession代理转发到这里,拼接了需要回调的数据。
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location;
- (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];
}
}
}
}
复制代码
下载成功了被NSUrlSession代理转发到这里。 NSUrlSession代理的下载路径是所有request都用的公用的下载路径。如果自定义了下载路径,所有的request都会下载到自定义路径。
其他
用来做断点续传的方法:
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes:
当下载被取消或者失败后重新恢复下载时调用;
- 其实这个就是用来做断点续传的代理方法。可以在下载失败的时候,拿到我们失败的拼接的部分resumeData,然后用去调用downloadTaskWithResumeData:就会调用到这个代理方法来了。
- 其中注意:fileOffset这个参数,如果文件缓存策略或者最后文件更新日期阻止重用已经存在的文件内容,那么该值为0。否则,该值表示当前已经下载data的偏移量。
AFUrlSessionManager 的一些自定义Block
@property (readwrite, nonatomic, copy) AFURLSessionDidBecomeInvalidBlock sessionDidBecomeInvalid;
@property (readwrite, nonatomic, copy) AFURLSessionDidReceiveAuthenticationChallengeBlock sessionDidReceiveAuthenticationChallenge;
@property (readwrite, nonatomic, copy) AFURLSessionDidFinishEventsForBackgroundURLSessionBlock didFinishEventsForBackgroundURLSession;
@property (readwrite, nonatomic, copy) AFURLSessionTaskWillPerformHTTPRedirectionBlock taskWillPerformHTTPRedirection;
...
复制代码
各自对应的还有一堆这样的set方法:
- (void)setSessionDidBecomeInvalidBlock:(void (^)(NSURLSession *session, NSError *error))block {
self.sessionDidBecomeInvalid = block;
}
复制代码
这个设计思路:
- 作者用@property把这个些Block属性在.m文件中声明,然后复写了set方法。
- 然后在.h中去声明这些set方法
- (void)setSessionDidBecomeInvalidBlock:(nullable void (^)(NSURLSession *session, NSError *error))block;
为什么要绕这么一大圈呢?原来这是为了我们这些用户使用起来方便,调用set方法去设置这些Block,能很清晰的看到Block的各个参数与返回值。
四、AFURLRequestSerialization
初始化方法
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
self.stringEncoding = NSUTF8StringEncoding;
self.mutableHTTPRequestHeaders = [NSMutableDictionary dictionary];
self.requestHeaderModificationQueue = dispatch_queue_create("requestHeaderModificationQueue", DISPATCH_QUEUE_CONCURRENT);
// 忽略部分代码...
self.HTTPMethodsEncodingParametersInURI = [NSSet setWithObjects:@"GET", @"HEAD", @"DELETE", nil];
self.mutableObservedChangedKeyPaths = [NSMutableSet set];
for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) {
if ([self respondsToSelector:NSSelectorFromString(keyPath)]) {
[self addObserver:self forKeyPath:keyPath options:NSKeyValueObservingOptionNew context:AFHTTPRequestSerializerObserverContext];
}
}
return self;
}
复制代码
AFHTTPRequestSerializerObservedKeyPaths
封装了一些属性的名字,这些都是NSUrlRequest的属性。self.mutableObservedChangedKeyPaths
:AFN设置的request属性值的集合。AFURLRequestSerialization
的-init
方法对这个集合进行了初始化,并且对当前类的和NSUrlRequest相关的那些属性添加了KVO监听。
request 生成步骤
-
第一步:网络请求
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method URLString:(NSString *)URLString parameters:(id)parameters error:(NSError *__autoreleasing *)error { //断言,debug模式下,如果缺少改参数,crash NSParameterAssert(method); NSParameterAssert(URLString); NSURL *url = [NSURL URLWithString:URLString]; NSParameterAssert(url); NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url]; mutableRequest.HTTPMethod = method; //将request的各种属性循环遍历 for (NSString *keyPath in AFHTTPRequestSerializerObservedKeyPaths()) { //如果自己观察到的发生变化的属性,在这些方法里 if ([self.mutableObservedChangedKeyPaths containsObject:keyPath]) { //把给自己设置的属性给request设置 [mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath]; } } //将传入的parameters进行编码,并添加到request中 mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy]; return mutableRequest; } 复制代码
[mutableRequest setValue:[self valueForKeyPath:keyPath] forKey:keyPath];
:用KVC的方式,把属性值都设置到我们请求的request中去。 -
第二步:把需要传递的参数进行编码,并且设置到request中去
//将传入的parameters进行编码,并添加到request中 mutableRequest = [[self requestBySerializingRequest:mutableRequest withParameters:parameters error:error] mutableCopy]; 复制代码
- (NSURLRequest *)requestBySerializingRequest:(NSURLRequest *)request withParameters:(id)parameters error:(NSError *__autoreleasing *)error { NSParameterAssert(request); NSMutableURLRequest *mutableRequest = [request mutableCopy]; //从自己的head里去遍历,如果有值则设置给request的head [self.HTTPRequestHeaders enumerateKeysAndObjectsUsingBlock:^(id field, id value, BOOL * __unused stop) { if (![request valueForHTTPHeaderField:field]) { [mutableRequest setValue:value forHTTPHeaderField:field]; } }]; //来把各种类型的参数,array dic set转化成字符串,给request NSString *query = nil; if (parameters) { //自定义的解析方式 if (self.queryStringSerialization) { NSError *serializationError; query = self.queryStringSerialization(request, parameters, &serializationError); if (serializationError) { if (error) { *error = serializationError; } return nil; } } else { //默认解析方式 switch (self.queryStringSerializationStyle) { case AFHTTPRequestQueryStringDefaultStyle: query = AFQueryStringFromParameters(parameters); break; } } } //最后判断该request中是否包含了GET、HEAD、DELETE(都包含在HTTPMethodsEncodingParametersInURI)。因为这几个method的quey是拼接到url后面的。而POST、PUT是把query拼接到http body中的。 if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) { if (query && query.length > 0) { mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]]; } } else { //post put请求 // #2864: an empty string is a valid x-www-form-urlencoded payload if (!query) { query = @""; } if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) { [mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"]; } //设置请求体 [mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]]; } return mutableRequest; } 复制代码
-
第三步:还根据该request中请求类型,来判断参数字符串应该如何设置到request中去。如果是GET、HEAD、DELETE,则把参数quey是拼接到url后面的。而POST、PUT是把query拼接到http body中的:
if ([self.HTTPMethodsEncodingParametersInURI containsObject:[[request HTTPMethod] uppercaseString]]) { if (query && query.length > 0) { mutableRequest.URL = [NSURL URLWithString:[[mutableRequest.URL absoluteString] stringByAppendingFormat:mutableRequest.URL.query ? @"&%@" : @"?%@", query]]; } } else { //post put请求 // #2864: an empty string is a valid x-www-form-urlencoded payload if (!query) { query = @""; } if (![mutableRequest valueForHTTPHeaderField:@"Content-Type"]) { [mutableRequest setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"]; } //设置请求体 [mutableRequest setHTTPBody:[query dataUsingEncoding:self.stringEncoding]]; } 复制代码
至此,我们生成了一个request。
五、AFURLResponseSerialization
协议方法
@protocol AFURLResponseSerialization <NSObject, NSSecureCoding, NSCopying>
- (nullable id)responseObjectForResponse:(nullable NSURLResponse *)response
data:(nullable NSData *)data
error:(NSError * _Nullable __autoreleasing *)error;
@end
复制代码
这个是协议方法,AFHTTPResponseSerializer
、AFJSONResponseSerializer
、AFXMLParserResponseSerializer
、AFXMLDocumentResponseSerializer
、AFPropertyListResponseSerializer
、AFImageResponseSerializer
、AFCompoundResponseSerializer
都是遵守这个方法去做数据解析。
AFHTTPResponseSerializer
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error {
[self validateResponse:(NSHTTPURLResponse *)response data:data error:error];
return data;
}
复制代码
下面这个方法就是来判断返回数据与咱们使用的解析器是否匹配,需要解析的状态码是否匹配。如果错误,则填充错误信息,并且返回NO,否则返回YES,错误信息为nil。
// 判断是不是可接受类型和可接受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的数据类型是否匹配,
//如果有接受数据类型,如果不匹配response,而且响应类型不为空,数据长度不为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]) {
//错误信息字典,填充一些错误信息
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;
}
//生成错误
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]) {
//填写错误信息字典
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;
}
//生成错误
validationError = AFErrorWithUnderlyingError([NSError errorWithDomain:AFURLResponseSerializationErrorDomain code:NSURLErrorBadServerResponse userInfo:mutableUserInfo], validationError);
//返回标识
responseIsValid = NO;
}
}
//给我们传过来的错误指针赋值
if (error && !responseIsValid) {
*error = validationError;
}
//返回是否错误标识
return responseIsValid;
}
复制代码
AFJSONResponseSerializer:
主要函数:
- (id)responseObjectForResponse:(NSURLResponse *)response
data:(NSData *)data
error:(NSError *__autoreleasing *)error
{
//先判断是不是可接受类型和可接受code
if (![self validateResponse:(NSHTTPURLResponse *)response data:data error:error]) {
//error为空,或者有错误,去函数里判断。
if (!error || AFErrorOrUnderlyingErrorHasCodeInDomain(*error, NSURLErrorCannotDecodeContentData, AFURLResponseSerializationErrorDomain)) {
//返回空
return nil;
}
}
id responseObject = nil;
NSError *serializationError = nil;
// Workaround for behavior of Rails to return a single space for `head :ok` (a workaround for a bug in Safari), which is not interpreted as valid input by NSJSONSerialization.
// See https://github.com/rails/rails/issues/1742
//如果数据为空
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;
}
//判断是否需要移除Null值
if (self.removesKeysWithNullValues && responseObject) {
responseObject = AFJSONObjectByRemovingKeysWithNullValues(responseObject, self.readingOptions);
}
//拿着json解析的error去填充错误信息
if (error) {
*error = AFErrorWithUnderlyingError(serializationError, *error);
}
//返回解析结果
return responseObject;
}
复制代码
AFJSONObjectByRemovingKeysWithNullValues,递归的形式实现:
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];
for (id <NSCopying> key in [(NSDictionary *)JSONObject allKeys]) {
id value = (NSDictionary *)JSONObject[key];
//value空则移除
if (!value || [value isEqual:[NSNull null]]) {
[mutableDictionary removeObjectForKey:key];
} else if ([value isKindOfClass:[NSArray class]] || [value isKindOfClass:[NSDictionary class]]) {
//如果数组还是去调用自己
mutableDictionary[key] = AFJSONObjectByRemovingKeysWithNullValues(value, readingOptions);
}
}
return (readingOptions & NSJSONReadingMutableContainers) ? mutableDictionary : [NSDictionary dictionaryWithDictionary:mutableDictionary];
}
return JSONObject;
}
复制代码
主要是把json解析的错误,赋值给我们需要返回给用户的error上:
static NSError * AFErrorWithUnderlyingError(NSError *error, NSError *underlyingError) {
if (!error) {
return underlyingError;
}
if (!underlyingError || error.userInfo[NSUnderlyingErrorKey]) {
return error;
}
NSMutableDictionary *mutableUserInfo = [error.userInfo mutableCopy];
mutableUserInfo[NSUnderlyingErrorKey] = underlyingError;
return [[NSError alloc] initWithDomain:error.domain code:error.code userInfo:mutableUserInfo];
}
复制代码
六、AFNetworking之UIKit扩展
AFNetworkActivityIndicatorManager
AFNetworkActivityIndicatorManager 对菊花显示做的一个优化处理。如果一个请求时间很短,那么菊花很可能闪一下就结束了。如果很多请求过来,那么菊花会不停的闪啊闪,这显然并不是我们想要的效果。于是: 1)在一个请求开始的时候,我延迟一会在去转菊花,如果在这延迟时间内,请求结束了,那么我就不需要去转菊花了。 2)但是一旦转菊花开始,哪怕很短请求就结束了,我们还是会去转一个时间再去结束,这时间就是延迟结束的时间。
默认时间:
static NSTimeInterval const kDefaultAFNetworkActivityManagerActivationDelay = 1.0;
static NSTimeInterval const kDefaultAFNetworkActivityManagerCompletionDelay = 0.17;
复制代码
开始触发旋转
//请求开始
- (void)networkRequestDidStart:(NSNotification *)notification {
if ([AFNetworkRequestFromNotification(notification) URL]) {
//增加请求活跃数
[self incrementActivityCount];
}
}
//请求结束
- (void)networkRequestDidFinish:(NSNotification *)notification {
//AFNetworkRequestFromNotification(notification)返回这个通知的request,用来判断request是否是有效的
if ([AFNetworkRequestFromNotification(notification) URL]) {
//减少请求活跃数
[self decrementActivityCount];
}
}
//增加请求活跃数
- (void)incrementActivityCount {
//活跃的网络数+1,并手动发送KVO
[self willChangeValueForKey:@"activityCount"];
@synchronized(self) {
_activityCount++;
}
[self didChangeValueForKey:@"activityCount"];
//主线程去做
dispatch_async(dispatch_get_main_queue(), ^{
[self updateCurrentStateForNetworkActivityChange];
});
}
//减少请求活跃数
- (void)decrementActivityCount {
[self willChangeValueForKey:@"activityCount"];
@synchronized(self) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
_activityCount = MAX(_activityCount - 1, 0);
#pragma clang diagnostic pop
}
[self didChangeValueForKey:@"activityCount"];
dispatch_async(dispatch_get_main_queue(), ^{
[self updateCurrentStateForNetworkActivityChange];
});
}
复制代码
更新状态的方法
- (void)updateCurrentStateForNetworkActivityChange {
//如果是允许小菊花
if (self.enabled) {
switch (self.currentState) {
//不活跃
case AFNetworkActivityManagerStateNotActive:
//判断活跃数,大于0为YES
if (self.isNetworkActivityOccurring) {
//设置状态为延迟开始
[self setCurrentState:AFNetworkActivityManagerStateDelayingStart];
}
break;
case AFNetworkActivityManagerStateDelayingStart:
//No op. Let the delay timer finish out.
break;
case AFNetworkActivityManagerStateActive:
if (!self.isNetworkActivityOccurring) {
[self setCurrentState:AFNetworkActivityManagerStateDelayingEnd];
}
break;
case AFNetworkActivityManagerStateDelayingEnd:
if (self.isNetworkActivityOccurring) {
[self setCurrentState:AFNetworkActivityManagerStateActive];
}
break;
}
}
}
复制代码
self.enabled
: 是否需要显示菊花self.isNetworkActivityOccurring
判断是否活跃//判断是否活跃 - (BOOL)isNetworkActivityOccurring { @synchronized(self) { return self.activityCount > 0; } } 复制代码
设置当前小菊花状态 set 方法
//设置当前小菊花状态
- (void)setCurrentState:(AFNetworkActivityManagerState)currentState {
@synchronized(self) {
if (_currentState != currentState) {
//KVO
[self willChangeValueForKey:@"currentState"];
_currentState = currentState;
switch (currentState) {
//如果为不活跃
case AFNetworkActivityManagerStateNotActive:
//取消两个延迟用的timer
[self cancelActivationDelayTimer];
[self cancelCompletionDelayTimer];
//设置小菊花不可见
[self setNetworkActivityIndicatorVisible:NO];
break;
case AFNetworkActivityManagerStateDelayingStart:
//开启一个定时器延迟去转菊花
[self startActivationDelayTimer];
break;
//如果是活跃状态
case AFNetworkActivityManagerStateActive:
//取消延迟完成的timer
[self cancelCompletionDelayTimer];
//开始转菊花
[self setNetworkActivityIndicatorVisible:YES];
break;
//延迟完成状态
case AFNetworkActivityManagerStateDelayingEnd:
//开启延迟完成timer
[self startCompletionDelayTimer];
break;
}
}
[self didChangeValueForKey:@"currentState"];
}
}
复制代码
- 这里根据当前状态,是否需要开始执行一个延迟开始或者延迟完成,又或者是否需要取消这两个延迟。
- 还判断了是否需要去转状态栏的菊花,调用了
setNetworkActivityIndicatorVisible:
方法。
具体旋转
- (void)setNetworkActivityIndicatorVisible:(BOOL)networkActivityIndicatorVisible {
if (_networkActivityIndicatorVisible != networkActivityIndicatorVisible) {
[self willChangeValueForKey:@"networkActivityIndicatorVisible"];
@synchronized(self) {
_networkActivityIndicatorVisible = networkActivityIndicatorVisible;
}
[self didChangeValueForKey:@"networkActivityIndicatorVisible"];
//支持自定义的Block,去自己控制小菊花
if (self.networkActivityActionBlock) {
self.networkActivityActionBlock(networkActivityIndicatorVisible);
} else {
//否则默认AF根据该Bool,去控制状态栏小菊花是否显示
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:networkActivityIndicatorVisible];
}
}
}
复制代码
这个方法就是用来控制菊花是否转。并且支持一个自定义的Block,我们可以自己去拿到这个菊花是否应该转的状态值,去做一些自定义的处理。
利用定时器实现优化
开始延迟执行一个方法,和结束的时候延迟执行一个方法,和对应这两个方法的取消:
//开始任务到结束的时间,默认为1秒,如果1秒就结束,那么不转菊花,延迟去开始转
- (void)startActivationDelayTimer {
//只执行一次
self.activationDelayTimer = [NSTimer
timerWithTimeInterval:self.activationDelay target:self selector:@selector(activationDelayTimerFired) userInfo:nil repeats:NO];
//添加到主线程runloop去触发
[[NSRunLoop mainRunLoop] addTimer:self.activationDelayTimer forMode:NSRunLoopCommonModes];
}
//完成任务到下一个任务开始,默认为0.17秒,如果0.17秒就开始下一个,那么不停 延迟去结束菊花转
- (void)startCompletionDelayTimer {
//先取消之前的
[self.completionDelayTimer invalidate];
//延迟执行让菊花不在转
self.completionDelayTimer = [NSTimer timerWithTimeInterval:self.completionDelay target:self selector:@selector(completionDelayTimerFired) userInfo:nil repeats:NO];
[[NSRunLoop mainRunLoop] addTimer:self.completionDelayTimer forMode:NSRunLoopCommonModes];
}
- (void)cancelActivationDelayTimer {
[self.activationDelayTimer invalidate];
}
- (void)cancelCompletionDelayTimer {
[self.completionDelayTimer invalidate];
}
复制代码
两个延迟调用具体实现,回到之前state的set方法中:
- (void)activationDelayTimerFired {
//活跃状态,即活跃数大于1才转
if (self.networkActivityOccurring) {
[self setCurrentState:AFNetworkActivityManagerStateActive];
} else {
[self setCurrentState:AFNetworkActivityManagerStateNotActive];
}
}
- (void)completionDelayTimerFired {
[self setCurrentState:AFNetworkActivityManagerStateNotActive];
}
复制代码
UIImageView+AFNetworking、
七、其他
_AFURLSessionTaskSwizzling类利用 runtime 替换掉NSUrlSession中的resume和suspend方法
+ (void)load {
if (NSClassFromString(@"NSURLSessionTask")) {
// 1) 首先构建一个NSURLSession对象session,再通过session构建出一个_NSCFLocalDataTask变量
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
NSURLSession * session = [NSURLSession sessionWithConfiguration:configuration];
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnonnull"
NSURLSessionDataTask *localDataTask = [session dataTaskWithURL:nil];
#pragma clang diagnostic pop
// 2) 获取到af_resume实现的指针
IMP originalAFResumeIMP = method_getImplementation(class_getInstanceMethod([self class], @selector(af_resume)));
Class currentClass = [localDataTask class];
// 3) 检查当前class是否实现了resume。如果实现了,继续第4步。
while (class_getInstanceMethod(currentClass, @selector(resume))) {
// 4) 获取到当前class的父类(superClass)
Class superClass = [currentClass superclass];
// 5) 获取到当前class对于resume实现的指针
IMP classResumeIMP = method_getImplementation(class_getInstanceMethod(currentClass, @selector(resume)));
// 6) 获取到父类对于resume实现的指针
IMP superclassResumeIMP = method_getImplementation(class_getInstanceMethod(superClass, @selector(resume)));
// 7) 如果当前class对于resume的实现和父类不一样(类似iOS7上的情况),并且当前class的resume实现和af_resume不一样,才进行method swizzling。
if (classResumeIMP != superclassResumeIMP &&
originalAFResumeIMP != classResumeIMP) {
//执行交换的函数
[self swizzleResumeAndSuspendMethodForClass:currentClass];
}
// 8) 设置当前操作的class为其父类class,重复步骤3~8
currentClass = [currentClass superclass];
}
[localDataTask cancel];
[session finishTasksAndInvalidate];
}
}
//被替换掉的方法,只要有TASK开启或者暂停,都会执行
- (void)af_resume {
NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state");
NSURLSessionTaskState state = [self state];
[self af_resume];
if (state != NSURLSessionTaskStateRunning) {
[[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidResumeNotification object:self];
}
}
- (void)af_suspend {
NSAssert([self respondsToSelector:@selector(state)], @"Does not respond to state");
NSURLSessionTaskState state = [self state];
[self af_suspend];
if (state != NSURLSessionTaskStateSuspended) {
[[NSNotificationCenter defaultCenter] postNotificationName:AFNSURLSessionTaskDidSuspendNotification object:self];
}
}
复制代码
- 注意:
首先可以注意class_getInstanceMethod这个方法,它会获取到当前类继承链逐级往上,第一个实现的该方法。所以说它获取到的方法不能确定是当前类还是父类的。而且这里也没有用dispatch_once_t来保证一个方法只交换一次,那万一这是父类的方法,当前类换一次,父类又换一次,不是等于没交换么?...请注意这行判断,这个条件就杜绝了这种情况的发生,只有当前类实现了这个方法,才可能进入这个if块。
// 7) 如果当前class对于resume的实现和父类不一样(类似iOS7上的情况),并且当前class的resume实现和af_resume不一样,才进行method swizzling。 if (classResumeIMP != superclassResumeIMP && originalAFResumeIMP != classResumeIMP) { //执行交换的函数 [self swizzleResumeAndSuspendMethodForClass:currentClass]; } 复制代码
AF3.x的整个流程和线程的关系
- 我们一开始初始化sessionManager的时候,一般都是在主线程,(当然不排除有些人喜欢在分线程初始化...)
- 然后我们调用get或者post等去请求数据,接着会进行request拼接,AF代理的字典映射,progress的KVO添加等等,到NSUrlSession的resume之前这些准备工作,仍旧是在主线程中的。
- 然后我们调用NSUrlSession的resume,接着就跑到NSUrlSession内部去对网络进行数据请求了,在它内部是多线程并发的去请求数据的。
- 紧接着数据请求完成后,回调回来在我们一开始生成的并发数为1的NSOperationQueue中,这个时候会是多线程串行的回调回来的。
- 然后我们到返回数据解析那一块,我们自己又创建了并发的多线程,去对这些数据进行了各种类型的解析。
- 最后我们如果有自定义的completionQueue,则在自定义的queue中回调回来,也就是分线程回调回来,否则就是主队列,主线程中回调结束。
为什么AF2.x需要一条常驻线程?
首先如果我们用NSURLConnection,我们为了获取请求结果有以下三种选择:
- 在主线程调异步接口
- 每一个请求用一个线程,对应一个runloop,然后等待结果回调。
- 只用一条线程,一个runloop,所有结果回调在这个线程上。
很显然AF选择的是第3种方式,创建了一条常驻线程专门处理所有请求的回调事件,这个模型跟nodejs有点类似,我们来讨论讨论不选择另外两种方式的原因:
-
不选择主线程原因:
- 如果我们放到主线程去做,势必要这么写:
[[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES]
,这样NSURLConnection的回调会被放在主线程中NSDefaultRunLoopMode中,这样我们在其它类似UITrackingRunLoopMode模式下,我们是得不到网络请求的结果的,这显然不是我们想要的,那么我们势必需要调用:[connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
把它加入NSRunLoopCommonModes
中,试想如果有大量的网络请求,同时回调回来,就会影响我们的UI体验了。 - 如果我们请求数据返回,势必要进行数据解析,解析成我们需要的格式,那么这些解析都在主线程中做,给主线程增加额外的负担。 又或者我们回调回来开辟一个新的线程去做数据解析,那么我们有n个请求回来开辟n条线程带来的性能损耗,以及线程间切换带来的损耗,是不是一笔更大的开销。
- 如果我们放到主线程去做,势必要这么写:
-
我们一开始就开辟n条线程去做请求,然后设置runloop保活住线程,等待结果回调。其实看到这,大家想想都觉得这个方法很傻,为了等待不确定的请求结果,阻塞住线程,白白浪费n条线程的开销。
综上所述,这就是AF2.x需要一条常驻线程的原因了。
参考文章
AFNetworking到底做了什么?
AFNetworking到底做了什么?(二)
文章推荐
AFNetworking AFHttpSessionManager源码解析 AFNetworking AFURLSessionManager源码解析