来源:Yuzeyang
链接:http://zeeyang.com/2016/02/21/AFNetWorking-one/
首先来介绍下AFNetWorking,官方介绍如下:
AFNetworking is a delightful networking library for iOS and Mac OS X. It’s built on top of theFoundation URL Loading System, extending the powerful high-level networking abstractions built into Cocoa. It has a modular architecture with well-designed, feature-rich APIs that are a joy to use.
Perhaps the most important feature of all, however, is the amazing community of developers who use and contribute to AFNetworking every day. AFNetworking powers some of the most popular and critically-acclaimed apps on the iPhone, iPad, and Mac.
Choose AFNetworking for your next project, or migrate over your existing projects—you’ll be happy you did!
翻译过来简单来说就是
AFNetworking是一个适用于iOS和Mac OS X两个平台的网络库,它是基于Foundation URL Loading System上进行了一套封装,并且提供了丰富且优美的API接口给使用者使用
相信从star数和fork数来看,大家都能明白这个库是多么的受欢迎了,所以了解这个库对于一个iOS开发来说是极为重要的!
这个是AFNetworking的github地址:GitHub – https://github.com/AFNetworking/AFNetworking
在使用前阅读README是非常重要的,里面往往包括了这个库的介绍、安装和使用等等,对于快速了解一个库来说,这是非常有帮助的
首先我们在AFNetWorking源码地址里download下来,打开工程文件,可以看到里面内容分为两个部分,一个是AFNetworking,另一个是UIKit+AFNetworking
很明显,第一个是用来做网络请求相关的,第二个则是和UI使用相关的,我们先看第一个
在看完头文件和README之后,你会发现AFURLSessionManager和AFHTTPSessionManager是里面比较重要的两个类
这里我先讲AFURLSessionManager这个类
首先浏览完这个类从API,发现其主要提供了数据的请求、上传和下载功能
在属性方面:
@property(readonly,nonatomic,strong)NSArray *tasks;
@property(readonly,nonatomic,strong)NSArray *dataTasks;
@property(readonly,nonatomic,strong)NSArray *uploadTasks;
@property(readonly,nonatomic,strong)NSArray *downloadTasks;
通过这四个属性,我们分别可以拿到总的任务集合、数据任务集合、上传任务集合和下载任务集合
@property(nonatomic,assign)BOOL attemptsToRecreateUploadTasksForBackgroundSessions;
这个属性非常重要,注释里面写到,在iOS7中存在一个bug,在创建后台上传任务时,有时候会返回nil,所以为了解决这个问题,AFNetworking遵照了苹果的建议,在创建失败的时候,会重新尝试创建,次数默认为3次,所以你的应用如果有场景会有在后台上传的情况的话,记得将该值设为YES,避免出现上传失败的问题
FOUNDATION_EXPORT NSString * const AFNetworkingTaskDidResumeNotification;
在对外提供的notification key里面,使用了FOUNDATION_EXPORT来定义常量,使用FOUNDATION_EXPORT和extern或者define有什么区别呢?
FOUNDATION_EXPORT在c文件编译下是和extern等同,在c++文件编译下是和extern “C”等同,在32位机的环境下又是另外编译情况,在兼容性方面,FOUNDATION_EXPORT做的会更好。
这里还提到了效率方面的问题:iOS开发的一些奇巧淫技3
http://www.jianshu.com/p/f547eb0368c4
进入到实现文件里面,我们可以看到在外部API调用dataTask、uploadTask、downloadTask方法实际上都是completionHanlder block返回出来的,但是我们知道网络请求是delegate返回结果的,AF内部做了巧妙的操作,他对每个task都增加代理设置
- (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;
url_session_manager_create_task_safely(^{
dataTask = [self.session dataTaskWithRequest:request];
});
// 每个task里面都会调用addDelegate方法
[self addDelegateForDataTask:dataTask uploadProgress:uploadProgressBlock downloadProgress:downloadProgressBlock completionHandler:completionHandler];
return dataTask;
}
在设置里面,每个task会在内部创建AFURLSessionManagerTaskDelegate对象,并设置completionHandler、uploadProgressBlock、downloadProgressBlock回调
- (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
{
// 初始化delegate对象
AFURLSessionManagerTaskDelegate *delegate = [[AFURLSessionManagerTaskDelegate alloc] init];
delegate.manager = self;
// 将task的completionHandler赋给delegate,系统网络请求delegate 调用该block,返回结果
delegate.completionHandler = completionHandler;
dataTask.taskDescription = self.taskDescriptionForSessionTasks;
// 对task进行delegate
[self setDelegate:delegate forTask:dataTask];
// 设置上传和下载进度回调
delegate.uploadProgressBlock = uploadProgressBlock;
delegate.downloadProgressBlock = downloadProgressBlock;
}
然后delegate对象利用kvo将task对一些方法进行监听,并且监听到变化时,通过block返回,将delegate转成block出去
- (void)setDelegate:(AFURLSessionManagerTaskDelegate *)delegate
forTask:(NSURLSessionTask *)task
{
// 断言
NSParameterAssert(task);
NSParameterAssert(delegate);
[self.lock lock];
self.mutableTaskDelegatesKeyedByTaskIdentifier[@(task.taskIdentifier)] = delegate;
// task使用kvo对一些方法监听,返回上传或者下载的进度
[delegate setupProgressForTask:task];
// sessionManager对暂停task和恢复task进行注册通知
[self addNotificationObserverForTask:task];
[self.lock unlock];
}
在原先IM的设计时,因为接口的数量并不多,所以在AsyncSocket的delegate回调后,我们依旧是采用delegate回调给业务层,但是随着接口数量的增加,业务层对于回调的处理更加困难和不可控,在重构IM的时候,我们也参考学习了AF的做法,我们通过对唯一标识和每个请求做一一绑定,将请求的上下文关联起来,这样让socket长连接的请求的也想http请求一样,都由block回去,对于业务层的处理也方便更多
setupProgressForTask方法主要是对task和progress设置监听
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([object isKindOfClass:[NSURLSessionTask class]] || [object isKindOfClass:[NSURLSessionDownloadTask class]]) {
// 设置上传和下载的新值
}
else if ([object isEqual:self.downloadProgress]) {
if (self.downloadProgressBlock) {
self.downloadProgressBlock(object);
}
}
else if ([object isEqual:self.uploadProgress]) {
if (self.uploadProgressBlock) {
self.uploadProgressBlock(object);
}
}
}
在第一个if判断里面,object判断是否是NSURLSessionTask类或者是否是NSURLSessionDownloadTask类,但是进到NSURLSessionDownloadTask的时候,我们可以看到NSURLSessionDownloadTask是NSURLSessionTask的子类,那为什么还要判断这个呢?
NSURLSessionTask实际上是Class cluster,通过NSURLSession生成的task返回的并不一定是指定的task类型。因此kindOfClass并不总会生效,具体可以参见AFURLSessionManager.m在load方法中的说明。
特定于当前问题,是由于iOS 7上NSCFURLSessionDownloadTask的基类并不是NSCFURLSessionTask,因此isKindOfClass会出错。查看对应的commit就可以知道了。
在NSURLSessionTaskDelegate的代理里面,只是做了两件事情,第一个是获取数据,将responseSerializer和downloadFileURL或data存到userInfo里面,第二个是根据error是否为空值,做下一步处理
#pragma mark - NSURLSessionTaskDelegate
- (void)URLSession:(__unused NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
// 获取数据,将responseSerializer和downloadFileURL或data存到userInfo里面
__strong AFURLSessionManager *manager = self.manager;
__block id responseObject = nil;
__block NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
userInfo[AFNetworkingTaskDidCompleteResponseSerializerKey] = manager.responseSerializer;
//Performance Improvement from #2672
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;
}
if (self.downloadFileURL) {
userInfo[AFNetworkingTaskDidCompleteAssetPathKey] = self.downloadFileURL;
} else if (data) {
userInfo[AFNetworkingTaskDidCompleteResponseDataKey] = data;
}
if (error) {
// 有error时处理
} else {
// 无error时正常处理
}
#pragma clang diagnostic pop
}
在有error时,userInfo先存储error,然后检查manager是否有completionGroup和completionQueue,没有的话,就创建一个dispatch_group_t和在主线程上做completionHandler的操作,并在主线程中发送一个AFNetworkingTaskDidCompleteNotification通知,这个通知在UIKit+AFNetworking里UIRefreshControl +AFNetworking里也会接收到,用来停止刷新,如果你不使用AF的UI部分,你可以通过接收这个通知来做操作
userInfo[AFNetworkingTaskDidCompleteErrorKey] = error;
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];
});
});
在没有error时,会先对数据进行一次序列化操作,然后下面的处理就和有error的那部分一样了
dispatch_async(url_session_manager_processing_queue(), ^{
NSError *serializationError = nil;
responseObject = [manager.responseSerializer responseObjectForResponse:task.response data:data error:&serializationError];
if (self.downloadFileURL) {
responseObject = self.downloadFileURL;
}
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];
});
});
});
一开始我们就看到了clang命令,这个的作用是用来消除特定区域的clang的编译警告,-Wgnu则是消除?:警告,这个是clang的警告message列表Which Clang Warning Is Generating This Message?
- (void)URLSession:(__unused NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
// some codes
#pragma clang diagnostic pop
}
再下面两个则是收到数据和下载文件的回调处理
#pragma mark - NSURLSessionDataTaskDelegate
- (void)URLSession:(__unused NSURLSession *)session
dataTask:(__unused NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data
{
[self.mutableData appendData:data];
}
#pragma mark - NSURLSessionDownloadTaskDelegate
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
NSError *fileManagerError = nil;
self.downloadFileURL = nil;
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];
}
}
}
}
在刚才说到的load方法里面,对系统的resume和suspend方法进行了替换
+ (void)swizzleResumeAndSuspendMethodForClass:(Class)theClass {
Method afResumeMethod = class_getInstanceMethod(self, @selector(af_resume));
Method afSuspendMethod = class_getInstanceMethod(self, @selector(af_suspend));
if (af_addMethod(theClass, @selector(af_resume), afResumeMethod)) {
af_swizzleSelector(theClass, @selector(resume), @selector(af_resume));
}
if (af_addMethod(theClass, @selector(af_suspend), afSuspendMethod)) {
af_swizzleSelector(theClass, @selector(suspend), @selector(af_suspend));
}
}
替换之后,只是增加了通知处理而已
- (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];
}
}
在调用替换和增加方法时候,用到了关键字inline,inline是为了防止反汇编之后,在符号表里面看不到你所调用的该方法,否则别人可以通过篡改你的返回值来造成攻击,iOS安全–使用static inline方式编译函数,防止静态分析,特别是在使用swizzling的时候,那除了使用swizzling动态替换函数方法之外,还有别的方法么?有,修改IMP指针指向的方法,轻松学习之 IMP指针的作用 – CocoaChina_让移动开发更简单
static inline void af_swizzleSelector(Class theClass, SEL originalSelector, SEL swizzledSelector) {
Method originalMethod = class_getInstanceMethod(theClass, originalSelector);
Method swizzledMethod = class_getInstanceMethod(theClass, swizzledSelector);
method_exchangeImplementations(originalMethod, swizzledMethod);
}
static inline BOOL af_addMethod(Class theClass, SEL selector, Method method) {
return class_addMethod(theClass, selector, method_getImplementation(method), method_getTypeEncoding(method));
}
在+ load方法中,我们又看到了GCC命令,那clang和GCC在使用的时机有没有什么区别?通常情况下,在GCC特有的处理或者是在GCC,clang和其他兼容GCC的编译器时,尽量使用#pragma GCC,clang特有的处理时,使用#pragma clang,这个是GCC的message表
+ (void)load {
// ...
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wnonnull"
NSURLSessionDataTask *localDataTask = [session dataTaskWithURL:nil];
#pragma clang diagnostic pop
// ...
}
看完之后,有个疑问,查了资料也没有找到:
在NSURLSessionDelegate的URLSession:didReceiveChallenge:completionHandler:方法里面disposition会对credential对象做非空判断然后再赋值校验类型,但是NSURLSessionTaskDelegate的– [URLSession:task:didReceiveChallenge:completionHandler:]方法里面disposition并不对credential对象做判断,而是直接就赋值校验类型,有知道的,欢迎留言交流