【AFNetworking】AFNetworking源码阅读(一)
- 1. 前言
- 2. iOS Example代码结构
- 3.AFNetworkActivityIndicatorManager
- 4. UIRefreshControl+AFNetworking
- 5. AFNetworkActivityManagerTests+AFUIRefreshControlTests
- 6. 参考文章
回到顶部
1. 前言
AFNetworking版本:3.0.4
静下心来阅读一下AFNetworking源代码,我想回到最原点,从AFNetworking提供的iOS Example开始阅读。
新增:准备给自己加点难度,把AFNetworking对应的Tests部分也看了!
iOS Example的代码其实很规范,值得学习。这里是我的感悟:我觉得熟悉业务,再看代码才是正确的姿势。不管什么源码,我一般都会先了解代码是用来做什么的,怎么用的,也就是它的业务逻辑。当然,这是一个互通的过程,因为源码量越多,所掌握的业务逻辑其实也会一样的。
回到顶部
2. iOS Example代码结构
上面这个图只是简单地罗列了一下该example的架构。还没有深入研究具体的逻辑。我们还是按照代码顺序一步一步往下看。
2.1 AppDelegate
此文件主要就是实现函数didFinishLaunchingWithOptions。将windows的rootViewController设置为rootViewController为GlobaltimelineViewController的NavigationController。此处有两点需要注意一下:
- 第一处
NSURLCache *URLCache = [[NSURLCache alloc] initWithMemoryCapacity:4 * 1024 * 1024 diskCapacity:20 * 1024 * 1024 diskPath:nil]; [NSURLCache setSharedURLCache:URLCache];
NSURLCache
为您的应用的 URL 请求提供了内存中(对应memoryCapacity)以及磁盘上(对应diskCapacity)的综合缓存机制。所以你想使用NSURLCache带来的好处,就需要在此处设置一个sharedURLCache。
- 第二处
[[AFNetworkActivityIndicatorManager sharedManager] setEnabled:YES];
为了说明AFNetworkingActivityIndicator是什么,直接上图:
当你有session task正在运行时,这个小菊花就会转啊转。这个是自动检测的,只需要你设置AFNetworkingActivityIndicatorManager的sharedManager中的enabled设为YES即可。
这里我简单看了下AFNetworkingActivityIndicatorManager,发现它对外接口不多,比较容易理解它的业务流程。所以我准备在第三部分就将AFNetworkingActivityIndicatorManager的源码拿下。
设置完了cache和AFNetworkingActivityIndicator,接着就是进入GlobalTimelineViewController(UITableViewController)了。这里我学到一个,就是UITableViewController可以使用initWithStyle进行初始化。
2.2 GlobalTimelineViewController
主要是围绕UITableView的delegate和dataSource来说。
2.2.1 UITableViewDelegate
主要是计算heightForRowAtIndexPath这个函数比较麻烦,这里的Cell比较简单,可以直接使用posts中存储的text值来计算高度,核心代码就下面这句:
CGRect rectToFit = [text boundingRectWithSize:CGSizeMake(240.0f, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName: [UIFont systemFontOfSize:12.0f]} context:nil];
对于boundingRectWithSize的使用又增进了一步。
2.2.2 UITableViewDataSource
主要是用posts作为数据源,而posts的获取在此处尤为关键,是通过Post本身(model)的globalTimelinePostsWithBlock函数获取数据的,这里作者将网络端的请求放在了model里面。
接着调用了refreshControl控件的setRefreshingWithStateOfTask:。setRefreshingWithStateOfTask:其实是UIRefreshControl+AFNetworking的一个category中定义的。UIRefreshControl+AFNetworking的源码很简单,放在第四部分讲。
注意setRefreshingWithStateOfTask:有一个参数就是NSURLSessionTask*。而这个NSURLSessionTask的获取是调用了Post类中的globalTimelinePostsWithBlock:函数。
在globalTimelinePostsWithBlock:函数中其实封装了一层AFHTTPSessionManager的GET函数
- (nullable NSURLSessionDataTask *)GET:(NSString *)URLString parameters:(nullable id)parameters progress:(nullable void (^)(NSProgress *downloadProgress)) downloadProgress success:(nullable void (^)(NSURLSessionDataTask *task, id _Nullable responseObject))success failure:(nullable void (^)(NSURLSessionDataTask * _Nullable task, NSError *error))failure;
具体细节后面讨论,此处我们知道是根据一个url获取到服务器端的数据即可。注意获取到的数据是JSON格式的,这里作者在Post类,即Model中定义了一个JSON---->Model函数-initWithAttributes,,也就是说模型数据转化部分也放在了model中。
另外,调用GET方法不是直接用AFHTTPSessionManager的manager,而是又定义了一个AFAppDotNetAPIClient,继承自AFHTTPSessionManager。并在其定义的单例模式中简单地封装了一些AFHTTPSessionManager的设置。
+ (instancetype)sharedClient { static AFAppDotNetAPIClient *_sharedClient = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ // 初始化HTTP Client的base url,此处为@"https://api.app.net/" _sharedClient = [[AFAppDotNetAPIClient alloc] initWithBaseURL:[NSURL URLWithString:AFAppDotNetAPIBaseURLString]]; // 设置HTTP Client的安全策略为AFSSLPinningModeNone _sharedClient.securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone]; }); return _sharedClient; }
知识点:SSL Pinning
Https对比Http已经很安全,但在建立安全链接的过程中,可能遭受中间人攻击。防御这种类型攻击的最直接方式是Client使用者能正确鉴定Server发的证书【目前很多浏览器在这方面做的足够好,用户只要不在遇到警告时还继续其中的危险操作】,而对于Client的开发者而言,一种方式保持一个可信的根证书颁发机构列表,确认可信的证书,警告或阻止不是可信根证书颁发机构颁发的证书。
SSL Pinning其实就是证书绑定,一般浏览器的做法是信任可信根证书颁发机构颁发的证书,但在移动端【非浏览器的桌面应用亦如此】,应用只和少数的几个Server有交互,所以可以做得更极致点,直接就在应用内保留需要使用的具体Server的证书。对于iOS开发者而言,如果使用AFNetwoking作为网络库,那么要做到这点就很方便,直接证书作为资源打包进去就好,AFNetworking会自动加载,具体代码就不贴了,nsscreencast已经有很好的tutorial。
至于model根据网络层获取的数据赋值,除了user的头像那块比较难,因为涉及到UIImageView+AFNetworking等文件,其他部分很简单。而AFNetworking的UIImageView+AFNetworking的部分其实很类似SDWebImage的思路。后面会单独拿出一部分再学习一遍,也是为了复习SDWebImage。
3.AFNetworkActivityIndicatorManager
上面简单地说了下这个类的作用。如果要我去实现这个类,面临的两个问题就是:
- 1.如何在status bar上显示那个小菊花。
- 2.如何判断什么时候显示这个小菊花,也就是怎么判断session task的开始和结束。
3.1 问题一:如何显示小菊花?
我搜寻了一下代码,发现显示方式很简单,是系统自带的。就一行代码:
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:networkActivityIndicatorVisible];
但关键作为一个这么牛逼的库,肯定不能就这么简单就把菊花漏出来了。对了!它还允许用户自定义处理(用户需要自己定义networkActivityActionBlock)。见代码(AFNetworkActivityIndicatorManager.m下的setNetworkActivityIndicatorVisible:函数):
if (self.networkActivityActionBlock) { // 如果自己实现networkActivityActionBlock, self.networkActivityActionBlock(networkActivityIndicatorVisible); } else { [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:networkActivityIndicatorVisible]; }
3.2 问题二:什么时候显示与隐藏小菊花?
这个算是比较困难的问题。首先你得涉及到状态的处理和转移(处理就是指遇到这个状态我应该做什么,转移表示的是如何进行状态转移的)。纵观全局,发现获取和维护都是使用了currentState这个属性。这个currentState是一个AFNetworkActivityManagerState类型的属性,何为AFNetworkActivityManagerState:
typedef NS_ENUM(NSInteger, AFNetworkActivityManagerState) { AFNetworkActivityManagerStateNotActive, AFNetworkActivityManagerStateDelayingStart, AFNetworkActivityManagerStateActive, AFNetworkActivityManagerStateDelayingEnd };
我们从中大概也可以看出AFNetworkActivityIndicatorManager所需要处理的状态就这四种。NotActive和Active我清楚,就是判断当前有没有session task,但是DelayingStart和DelayingEnd是什么?不着急,先看看这些状态用来干啥的?
3.2.1 状态的处理
我们先搜索currentState。发现setCurrentState:函数集中了状态的处理过程:
整个函数是包含在@synchronized中,使用self作为锁的唯一标识。主要是担心多个网络线程同时修改currentState。接着就是判断currentState是否有变化,如果变了,就执行if语句中的函数。这里有一个貌似配对的函数willChangeValueForKey:和didChangeValueForKey:。
知识点:手动通知
KVO中有两种通知Observer的方式,自动通知和手动通知。自动通知顾名思义就是只要值变化了,就自动通知观察者。
- ①但是有时候我们有些地方的值变化了,并不想通知观察者亦或不想立即通知观察者,或者
- ②此处虽然值还没变,但是我也想通知观察者,那么就可以使用手动通知,在你想发送给观察者消息的地方,加上willChangeValueForKey和didChangeValueForKey。
说白了只要加上这两句话,就会通知观察者,不管是不是值变化了(亲测值没变化也有效)。不过,在此之前最好是把自动通知关掉,可以利用automaticallyNotifiesObserversForKey:来返回NO,达到关闭自动通知的功能(当然,开着也行,那么自动通知和手动通知会揉在一起,执行起来很乱)。
跟着就是判断currentState,并作出相应处理了:
- AFNetworkActivityManagerStateNotActive
一上来就出现了cancelActivationDelayTimer和cancelActivationDelayTimer两个函数。看懂这两个函数不难,直接查找startActiviationDelayTimer和startCompletionDelayTimer两个函数,看看我们的activationDelayTimer和completionDelayTimer是做什么的即可。我们发现这两个函数都是定义了一个计时器。具体看代码:
- (void)startActivationDelayTimer { // 定义了一个名为activationDelayTimer的定时器,定时器的时间为self.activationDelay。 // 执行完定时器后,执行activationDelayTimerFired函数 self.activationDelayTimer = [NSTimer timerWithTimeInterval:self.activationDelay target:self selector:@selector(activationDelayTimerFired) userInfo:nil repeats:NO]; // 将该定时器添加到RunLoop里面执行 [[NSRunLoop mainRunLoop] addTimer:self.activationDelayTimer forMode:NSRunLoopCommonModes]; }
至于startCompletionDelayTimer类似,此处直接放出源码,具体几个细节后面详解:
- (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]; }
注意这里添加计时器的时候,使用的Mode是NSRunLoopCommonModes,表示不管RunLoop出于什么状态,都执行这个计时器任务(因为如果不指定这个mode的话,UI操作会阻塞计时器任务)。
不过现在关键是完全不知道这两个delay是干啥的?找了一会,终于在activationDelay和completionDelay的注释中找到了答案,恍然大悟,整个小菊花存在的时间是这样的:
不禁要问,既然session task已经开始了,为什么不直接使用Active作为状态,还要搞出一个activationDelay,这是因为Apple的HIG(Human Interface Guidelines)说有些session task时间太短了,有可能用户还没意识到session task的进行,就已经结束了,就没必要搞个菊花在上面转啊转的(这个用户的意识盲区在此处默认设定为1秒,即activationDelay)。至于completionDelay,是因为如果有多个session task正在进行,前一个task结束之后,不一会(这个不一会的时间,默认是0.17秒,可能利用了大数据分析出来的(鬼知道怎么测出来了),也就是completionDelay)另一个task就开始,此处认为这个间隙没必要停止菊花转。
至于下面三个state感觉就没必要讲了。
- AFNetworkActivityManagerStateDelayingStart
- AFNetworkActivityManagerStateActive
- AFNetworkActivityManagerStateDelayingEnd
3.2.2 状态的转移
牛逼的代码就是不一样,状态都这么多…没办法,只好全局搜索,发现了这个函数----updateCurrentStateForNetworkActivityChange,我大致看了下,觉得所有状态变化应该就写在这了:
- (void)updateCurrentStateForNetworkActivityChange { if (self.enabled) { switch (self.currentState) { case AFNetworkActivityManagerStateNotActive: 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; } } }
结合上面那个图,大概转移关系也是可以理解的。
不过在状态转移过程中,有一个属性很重要,叫做isNetworkActivityOccurring。这个其实是最真实的记录session task起始的状态。不过这个属性是根据activityCount来决定的:
- (BOOL)isNetworkActivityOccurring { @synchronized(self) { return self.activityCount > 0; } }
那什么是activityCount?我们发现activityCount的增减是通过incrementActivityCount和decrementActivityCount两个函数进行的。这两个函数也是使用了手动KVO的形式,具体实现很简单,此处就不赘述了。我们再看在networkRequestDidStart函数中调用了incrementActivityCount,在networkRequestDidFinish调用了decrementActivityCount。而这两个networkRequestDid*函数也是使用了KVO。具体这两个函数什么时候执行,已经超出了第一篇文章要研究的范围了。我们大概从他们的名字可以猜出networkRequest开始的时候activityCount++,networkRequest结束的时候activityCount—。