iOS AFNetWorking源码详解(一)

来源: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对象做判断,而是直接就赋值校验类型,有知道的,欢迎留言交流

时间: 2024-09-30 16:10:55

iOS AFNetWorking源码详解(一)的相关文章

AFNetWorking源码详解(二)

来源:Yuzeyang 链接:http://zeeyang.com/2016/03/15/AFNetWorking-two/ AFHTTPSessionManager继承于AFURLSessionManager,提供了更方便的HTTP请求方法,包括了GET.POST.PUT.PATCH.DELETE这五种方式,并且AF鼓励我们在AFHTTPSessionManager再进行一次封装来满足我们自己的业务需求 在开始的地方,AF一直提醒到一个属性baseURL,这个变量你可以在进一步封装的时候,将b

Android编程之Fragment动画加载方法源码详解

上次谈到了Fragment动画加载的异常问题,今天再聊聊它的动画加载loadAnimation的实现源代码: Animation loadAnimation(Fragment fragment, int transit, boolean enter, int transitionStyle) { 接下来具体看一下里面的源码部分,我将一部分一部分的讲解,首先是: Animation animObj = fragment.onCreateAnimation(transit, enter, fragm

Java concurrent AQS 源码详解

一.引言 AQS(同步阻塞队列)是concurrent包下锁机制实现的基础,相信大家在读完本篇博客后会对AQS框架有一个较为清晰的认识 这篇博客主要针对AbstractQueuedSynchronizer的源码进行分析,大致分为三个部分: 静态内部类Node的解析 重要常量以及字段的解析 重要方法的源码详解. 所有的分析仅基于个人的理解,若有不正之处,请谅解和批评指正,不胜感激!!! 二.Node解析 AQS在内部维护了一个同步阻塞队列,下面简称sync queue,该队列的元素即静态内部类No

深入Java基础(四)--哈希表(1)HashMap应用及源码详解

继续深入Java基础系列.今天是研究下哈希表,毕竟我们很多应用层的查找存储框架都是哈希作为它的根数据结构进行封装的嘛. 本系列: (1)深入Java基础(一)--基本数据类型及其包装类 (2)深入Java基础(二)--字符串家族 (3)深入Java基础(三)–集合(1)集合父类以及父接口源码及理解 (4)深入Java基础(三)–集合(2)ArrayList和其继承树源码解析以及其注意事项 文章结构:(1)哈希概述及HashMap应用:(2)HashMap源码分析:(3)再次总结关键点 一.哈希概

Spring IOC源码详解之容器依赖注入

Spring IOC源码详解之容器依赖注入 上一篇博客中介绍了IOC容器的初始化,通过源码分析大致了解了IOC容器初始化的一些知识,先简单回顾下上篇的内容 载入bean定义文件的过程,这个过程是通过BeanDefinitionReader来完成的,其中通过 loadBeanDefinition()来对定义文件进行解析和根据Spring定义的bean规则进行处理 - 事实上和Spring定义的bean规则相关的处理是在BeanDefinitionParserDelegate中完成的,完成这个处理需

Spring IOC源码详解之容器初始化

Spring IOC源码详解之容器初始化 上篇介绍了Spring IOC的大致体系类图,先来看一段简短的代码,使用IOC比较典型的代码 ClassPathResource res = new ClassPathResource("beans.xml"); DefaultListableBeanFactory factory = new DefaultListableBeanFactory(); XmlBeanDefinitionReader reader = new XmlBeanDe

IntentService源码详解

IntentService可以做什么: 如果你有一个任务,分成n个子任务,需要它们按照顺序完成.如果需要放到一个服务中完成,那么IntentService就会使最好的选择. IntentService是什么: IntentService是一个Service(看起来像废话,但是我第一眼看到这个名字,首先注意的是Intent啊.),所以如果自定义一个IntentService的话,一定要在AndroidManifest.xml里面声明. 从上面的"可以做什么"我们大概可以猜测一下Inten

Android View 事件分发机制源码详解(View篇)

前言 在Android View 事件分发机制源码详解(ViewGroup篇)一文中,主要对ViewGroup#dispatchTouchEvent的源码做了相应的解析,其中说到在ViewGroup把事件传递给子View的时候,会调用子View的dispatchTouchEvent,这时分两种情况,如果子View也是一个ViewGroup那么再执行同样的流程继续把事件分发下去,即调用ViewGroup#dispatchTouchEvent:如果子View只是单纯的一个View,那么调用的是Vie

butterknife源码详解

butterknife源码详解 作为Android开发者,大家肯定都知道大名鼎鼎的butterknife.它大大的提高了开发效率,虽然在很早之前就开始使用它了,但是只知道是通过注解的方式实现的,却一直没有仔细的学习下大牛的代码.最近在学习运行时注解,决定今天来系统的分析下butterknife的实现原理. 如果你之前不了解Annotation,那强烈建议你先看注解使用. 废多看图: 从图中可以很直观的看出它的module结构,以及使用示例代码. 它的目录和我们在注解使用这篇文章中介绍的一样,大体