【AFNetworking】AFNetworking源码阅读(一)

时间:2023-12-10 12:08:38
  • 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代码结构


【AFNetworking】AFNetworking源码阅读(一)

上面这个图只是简单地罗列了一下该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是什么,直接上图:

【AFNetworking】AFNetworking源码阅读(一)

当你有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函数

【AFNetworking】AFNetworking源码阅读(一)
- (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;
【AFNetworking】AFNetworking源码阅读(一)

具体细节后面讨论,此处我们知道是根据一个url获取到服务器端的数据即可。注意获取到的数据是JSON格式的,这里作者在Post类,即Model中定义了一个JSON---->Model函数-initWithAttributes,,也就是说模型数据转化部分也放在了model中。

另外,调用GET方法不是直接用AFHTTPSessionManager的manager,而是又定义了一个AFAppDotNetAPIClient,继承自AFHTTPSessionManager。并在其定义的单例模式中简单地封装了一些AFHTTPSessionManager的设置。

【AFNetworking】AFNetworking源码阅读(一)
+ (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;
}
【AFNetworking】AFNetworking源码阅读(一)

知识点: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. 1.如何在status bar上显示那个小菊花。
  2. 2.如何判断什么时候显示这个小菊花,也就是怎么判断session task的开始和结束。

3.1 问题一:如何显示小菊花?

我搜寻了一下代码,发现显示方式很简单,是系统自带的。就一行代码:

[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:networkActivityIndicatorVisible];

但关键作为一个这么牛逼的库,肯定不能就这么简单就把菊花漏出来了。对了!它还允许用户自定义处理(用户需要自己定义networkActivityActionBlock)。见代码(AFNetworkActivityIndicatorManager.m下的setNetworkActivityIndicatorVisible:函数):

【AFNetworking】AFNetworking源码阅读(一)
if (self.networkActivityActionBlock) { // 如果自己实现networkActivityActionBlock,
self.networkActivityActionBlock(networkActivityIndicatorVisible);
} else {
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:networkActivityIndicatorVisible];
}
【AFNetworking】AFNetworking源码阅读(一)

3.2 问题二:什么时候显示与隐藏小菊花?

这个算是比较困难的问题。首先你得涉及到状态的处理和转移(处理就是指遇到这个状态我应该做什么,转移表示的是如何进行状态转移的)。纵观全局,发现获取和维护都是使用了currentState这个属性。这个currentState是一个AFNetworkActivityManagerState类型的属性,何为AFNetworkActivityManagerState:

【AFNetworking】AFNetworking源码阅读(一)
typedef NS_ENUM(NSInteger, AFNetworkActivityManagerState) {
AFNetworkActivityManagerStateNotActive,
AFNetworkActivityManagerStateDelayingStart,
AFNetworkActivityManagerStateActive,
AFNetworkActivityManagerStateDelayingEnd
};
【AFNetworking】AFNetworking源码阅读(一)

我们从中大概也可以看出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是做什么的即可。我们发现这两个函数都是定义了一个计时器。具体看代码:

【AFNetworking】AFNetworking源码阅读(一)
- (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];
}
【AFNetworking】AFNetworking源码阅读(一)

至于startCompletionDelayTimer类似,此处直接放出源码,具体几个细节后面详解:

【AFNetworking】AFNetworking源码阅读(一)
- (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];
}
【AFNetworking】AFNetworking源码阅读(一)

注意这里添加计时器的时候,使用的Mode是NSRunLoopCommonModes,表示不管RunLoop出于什么状态,都执行这个计时器任务(因为如果不指定这个mode的话,UI操作会阻塞计时器任务)。

不过现在关键是完全不知道这两个delay是干啥的?找了一会,终于在activationDelay和completionDelay的注释中找到了答案,恍然大悟,整个小菊花存在的时间是这样的:

【AFNetworking】AFNetworking源码阅读(一)

不禁要问,既然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,我大致看了下,觉得所有状态变化应该就写在这了:

【AFNetworking】AFNetworking源码阅读(一)
- (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;
}
}
}
【AFNetworking】AFNetworking源码阅读(一)

结合上面那个图,大概转移关系也是可以理解的。

不过在状态转移过程中,有一个属性很重要,叫做isNetworkActivityOccurring。这个其实是最真实的记录session task起始的状态。不过这个属性是根据activityCount来决定的:

- (BOOL)isNetworkActivityOccurring {
@synchronized(self) {
return self.activityCount > 0;
}
}

那什么是activityCount?我们发现activityCount的增减是通过incrementActivityCountdecrementActivityCount两个函数进行的。这两个函数也是使用了手动KVO的形式,具体实现很简单,此处就不赘述了。我们再看在networkRequestDidStart函数中调用了incrementActivityCount,在networkRequestDidFinish调用了decrementActivityCount。而这两个networkRequestDid*函数也是使用了KVO。具体这两个函数什么时候执行,已经超出了第一篇文章要研究的范围了。我们大概从他们的名字可以猜出networkRequest开始的时候activityCount++,networkRequest结束的时候activityCount—。