【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—。
【AFNetworking】AFNetworking源码阅读(一)的更多相关文章
- 【原】AFNetworking源码阅读(六)
[原]AFNetworking源码阅读(六) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 这一篇的想讲的,一个就是分析一下AFSecurityPolicy文件,看看AF ...
- 【原】AFNetworking源码阅读(五)
[原]AFNetworking源码阅读(五) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇中提及到了Multipart Request的构建方法- [AFHTTP ...
- 【原】AFNetworking源码阅读(四)
[原]AFNetworking源码阅读(四) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇还遗留了很多问题,包括AFURLSessionManagerTaskDe ...
- 【原】AFNetworking源码阅读(三)
[原]AFNetworking源码阅读(三) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇的话,主要是讲了如何通过构建一个request来生成一个data tas ...
- 【原】AFNetworking源码阅读(二)
[原]AFNetworking源码阅读(二) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 上一篇中我们在iOS Example代码中提到了AFHTTPSessionMa ...
- 【原】AFNetworking源码阅读(一)
[原]AFNetworking源码阅读(一) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 AFNetworking版本:3.0.4 由于我平常并没有经常使用AFNetw ...
- AFNetworking源码阅读
get方法: - (NSURLSessionDataTask *)GET:(NSString *)URLString parameters:(id)parameters progress:(void ...
- 【原】FMDB源码阅读(一)
[原]FMDB源码阅读(一) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 说实话,之前的SDWebImage和AFNetworking这两个组件我还是使用过的,但是对于 ...
- 【原】FMDB源码阅读(三)
[原]FMDB源码阅读(三) 本文转载请注明出处 —— polobymulberry-博客园 1. 前言 FMDB比较优秀的地方就在于对多线程的处理.所以这一篇主要是研究FMDB的多线程处理的实现.而 ...
随机推荐
- spring整合freemarker 自定义标签
1.自定义标签实现 TemplateDirectiveModel 接口 2.spring 配置,注意标红的两行 <bean id="freemarkerConfig" cla ...
- Git:代码冲突常见解决方法
摘自: http://blog.csdn.net/iefreer/article/details/7679631 如果系统中有一些配置文件在服务器上做了配置修改,然后后续开发又新添加一些配置项的时候, ...
- PE查看器
主要界面如下: 主要代码如下: BOOL CPEParseDlg::OnInitDialog() { CDialog::OnInitDialog(); // 设置此对话框的图标.当应用程序主窗口不是对 ...
- HLS入门收集(1)
使用HLS各种问题 关于求指数函数 exp(x) 在HLS中使用exp(x),也就是指数函数.不能导出RTL到EDK也就是Pcore 只能导出为VIVADO IP:相关解释:见官方论坛 http:/ ...
- ASP.NET MVC5 入门
参考资料<ASP.NET MVC5 高级编程>第5版 第1章 入门 1.1 ASP.NET MVC 简介 ASP.NET MVC是一种构建Web 应用程序的框架,它将一般的MVC(Mode ...
- 那么如何添加网站favicon.ico图标
1. 获得一个favicon.ico的图标,大小为16px×16px最为合适 2. 将制作好的图标文件Favicon.ico上传到网站的根目录: 3. 在首页文件的html代码的头部中加入如下代码: ...
- android学习视频分享
最近整理了大量的安卓开发学习资料,有书籍有视频有代码,老罗的第一季有点老了, 这里就给大家分享下老罗的第二季的视频教程吧,还有源码,初级到高级程序猿都有用. 下载地址:http://51pansou. ...
- asp 301跳转代码
<% Response.Status="301 Moved Permanently" Response.AddHeader "Location&quo ...
- PHP计算某个目录大小的方法
用PHP来计算某个目录大小的方法. PHP CURL session COOKIE 可以调用系统命令,还可以这样: <?php function dirsize($dir) { @$dh ...
- 一款兼容IE6并带有多图横向滚动的jquery特效
一款兼容IE6并带有多图横向滚动的jquery特效,自动切换多个图片的jquery特效效果, 为大家分享这个的原因是,这款特效在兼容IE6上面很完美,实用性就广很多了. 适用浏览器:IE6.IE7.I ...