AFNetworking 3.0 源码解读(九)之 AFNetworkActivityIndicatorManager
让我们的APP像艺术品一样优雅,开发工程师更像是一名匠人,不仅需要精湛的技艺,而且要有一颗匠心。
前言
AFNetworkActivityIndicatorManager 是对状态栏中网络激活那个小控件的管理。在平时的开发中,我们很可能忽略了它的存在。然而,实现对它的管理,让我们的APP更符合人机交互,不也是件大快人心的事儿吗。看下边这张图片就明白了:
AFNetworkActivityIndicatorManager 接口
// 这个宏的意思指下边的类不能被扩展
NS_EXTENSION_UNAVAILABLE_IOS("Use view controller based solutions where appropriate instead.")
我们还是先看看,暴露出来的接口中,我们能够做哪些事情。不得不说的是,AFNetworkActivityIndicatorManager 大部分功能是通过重写setter方法实现的。
BOOL enabled
是否开启? 默认是不开启的。如果你的APP中使用了AFNetworking这个框架的话,只需要在AppDelegate
的application:didFinishLaunchingWithOptions:
方法中加入下边这行代码就行了:[[AFNetworkActivityIndicatorManager sharedManager] setEnabled:YES];
BOOL networkActivityIndicatorVisible
这个属性用来获取和设置激活状态。这个属性支持kvo。如果是设置,首先回调用自己实现的控制转态的block,如果没有实现这个block,就直接通过UIApplication来设置激活状态了。NSTimeInterval activationDelay
激活延时,指的是当网络开始到显示激活的一个时间间隔。默认的是1秒,为什么要设置这个呢?根据人机交互指南,有些网络很快,这个情况就不需要显示激活的那个状态了。NSTimeInterval completionDelay
状态消失的延时,默认为0.17秒。sharedManager
全局的单例对象。(void)incrementActivityCount
增加激活的请求的数量,当数量大于0,就处于激活状态。(void)decrementActivityCount
减少数量。setNetworkingActivityActionWithBlock:
根据状态来自定义事件。
AFNetworkActivityManagerState
激活一共分为四种状态:
typedef NS_ENUM(NSInteger, AFNetworkActivityManagerState) {
AFNetworkActivityManagerStateNotActive, // 未激活
AFNetworkActivityManagerStateDelayingStart, //激活前的延时阶段
AFNetworkActivityManagerStateActive, // 激活
AFNetworkActivityManagerStateDelayingEnd // 取消阶段
};
私有方法
static NSTimeInterval const kDefaultAFNetworkActivityManagerActivationDelay = 1.0;
static NSTimeInterval const kDefaultAFNetworkActivityManagerCompletionDelay = 0.17;
// 获取通知中的请求
static NSURLRequest * AFNetworkRequestFromNotification(NSNotification *notification) {
if ([[notification object] respondsToSelector:@selector(originalRequest)]) {
return [(NSURLSessionTask *)[notification object] originalRequest];
} else {
return nil;
}
}
typedef void (^AFNetworkActivityActionBlock)(BOOL networkActivityIndicatorVisible);
AFNetworkActivityIndicatorManager实现部分
由于内部的实现比较简单,没有特别难以理解的地方,在此就直接贴出代码了:
@interface AFNetworkActivityIndicatorManager ()
//激活数
@property (readwrite, nonatomic, assign) NSInteger activityCount;
//激活前延时的定时器
@property (readwrite, nonatomic, strong) NSTimer *activationDelayTimer;
//失效后延时的定时器
@property (readwrite, nonatomic, strong) NSTimer *completionDelayTimer;
//是否是激活中
@property (readonly, nonatomic, getter = isNetworkActivityOccurring) BOOL networkActivityOccurring;
//激活事件的自定义属性
@property (nonatomic, copy) AFNetworkActivityActionBlock networkActivityActionBlock;
//当前的状态
@property (nonatomic, assign) AFNetworkActivityManagerState currentState;
@property (nonatomic, assign, getter=isNetworkActivityIndicatorVisible) BOOL networkActivityIndicatorVisible;
// 当激活状态改变后更新当前的状态
- (void)updateCurrentStateForNetworkActivityChange;
@end
--
+ (instancetype)sharedManager {
static AFNetworkActivityIndicatorManager *_sharedManager = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_sharedManager = [[self alloc] init];
});
return _sharedManager;
}
不过,这里要说明一点,激活与否的依据来源于AFNetworking中下边的3个通知:
- AFNetworkingTaskDidResumeNotification
- AFNetworkingTaskDidSuspendNotification
- AFNetworkingTaskDidCompleteNotification
--
- (instancetype)init {
self = [super init];
if (!self) {
return nil;
}
self.currentState = AFNetworkActivityManagerStateNotActive;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidStart:) name:AFNetworkingTaskDidResumeNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidFinish:) name:AFNetworkingTaskDidSuspendNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkRequestDidFinish:) name:AFNetworkingTaskDidCompleteNotification object:nil];
self.activationDelay = kDefaultAFNetworkActivityManagerActivationDelay;
self.completionDelay = kDefaultAFNetworkActivityManagerCompletionDelay;
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
[_activationDelayTimer invalidate];
[_completionDelayTimer invalidate];
}
@synchronized()锁的补充
synchronized是一种锁,这种锁不管是在oc中还是java中用的都挺多的,而且这种锁锁得是对象。具体原理,可以看这篇文章后边的 参考 那一部分。
总结一下,锁一般用于多线程环境下对数据的操作中。在 AFNetworking 中我们见到了3种不同的锁,分别是:
NSLock
dispatch_semaphore_wait
@synchronized
// enabled setter方法
- (void)setEnabled:(BOOL)enabled {
_enabled = enabled;
if (enabled == NO) {
//设置当前状态为not
[self setCurrentState:AFNetworkActivityManagerStateNotActive];
}
}
--
// 自定义block的setter
- (void)setNetworkingActivityActionWithBlock:(void (^)(BOOL networkActivityIndicatorVisible))block {
self.networkActivityActionBlock = block;
}
--
// isNetworkActivityOccurring的getter
- (BOOL)isNetworkActivityOccurring {
@synchronized(self) {
return self.activityCount > 0;
}
}
--
// networkActivityIndicatorVisible的setter
- (void)setNetworkActivityIndicatorVisible:(BOOL)networkActivityIndicatorVisible {
if (_networkActivityIndicatorVisible != networkActivityIndicatorVisible) {
// 激活kvo
[self willChangeValueForKey:@"networkActivityIndicatorVisible"];
// 这个方法可能会在多线程被调用多次,所以要加锁
@synchronized(self) {
_networkActivityIndicatorVisible = networkActivityIndicatorVisible;
}
[self didChangeValueForKey:@"networkActivityIndicatorVisible"];
if (self.networkActivityActionBlock) {
self.networkActivityActionBlock(networkActivityIndicatorVisible);
} else {
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:networkActivityIndicatorVisible];
}
}
}
--
// activityCount的setter
- (void)setActivityCount:(NSInteger)activityCount {
@synchronized(self) {
_activityCount = activityCount;
}
// 这个方法会涉及到界面的更新,因此要在主线程
dispatch_async(dispatch_get_main_queue(), ^{
[self updateCurrentStateForNetworkActivityChange];
});
}
--
- (void)incrementActivityCount {
[self willChangeValueForKey:@"activityCount"];
@synchronized(self) {
_activityCount++;
}
[self didChangeValueForKey:@"activityCount"];
dispatch_async(dispatch_get_main_queue(), ^{
[self updateCurrentStateForNetworkActivityChange];
});
}
--
- (void)decrementActivityCount {
[self willChangeValueForKey:@"activityCount"];
@synchronized(self) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu"
_activityCount = MAX(_activityCount - 1, 0);
#pragma clang diagnostic pop
}
[self didChangeValueForKey:@"activityCount"];
dispatch_async(dispatch_get_main_queue(), ^{
[self updateCurrentStateForNetworkActivityChange];
});
}
--
//通知方法
- (void)networkRequestDidStart:(NSNotification *)notification {
if ([AFNetworkRequestFromNotification(notification) URL]) {
[self incrementActivityCount];
}
}
//通知方法
- (void)networkRequestDidFinish:(NSNotification *)notification {
if ([AFNetworkRequestFromNotification(notification) URL]) {
[self decrementActivityCount];
}
}
--
- (void)setCurrentState:(AFNetworkActivityManagerState)currentState {
@synchronized(self) {
if (_currentState != currentState) {
[self willChangeValueForKey:@"currentState"];
_currentState = currentState;
switch (currentState) {
case AFNetworkActivityManagerStateNotActive:
[self cancelActivationDelayTimer];
[self cancelCompletionDelayTimer];
[self setNetworkActivityIndicatorVisible:NO];
break;
case AFNetworkActivityManagerStateDelayingStart:
[self startActivationDelayTimer];
break;
case AFNetworkActivityManagerStateActive:
[self cancelCompletionDelayTimer];
[self setNetworkActivityIndicatorVisible:YES];
break;
case AFNetworkActivityManagerStateDelayingEnd:
[self startCompletionDelayTimer];
break;
}
}
[self didChangeValueForKey:@"currentState"];
}
}
--
- (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;
}
}
}
--
- (void)startActivationDelayTimer {
self.activationDelayTimer = [NSTimer
timerWithTimeInterval:self.activationDelay target:self selector:@selector(activationDelayTimerFired) userInfo:nil repeats:NO];
[[NSRunLoop mainRunLoop] addTimer:self.activationDelayTimer forMode:NSRunLoopCommonModes];
}
- (void)activationDelayTimerFired {
if (self.networkActivityOccurring) {
[self setCurrentState:AFNetworkActivityManagerStateActive];
} else {
[self setCurrentState:AFNetworkActivityManagerStateNotActive];
}
}
- (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];
}
- (void)completionDelayTimerFired {
[self setCurrentState:AFNetworkActivityManagerStateNotActive];
}
- (void)cancelActivationDelayTimer {
[self.activationDelayTimer invalidate];
}
- (void)cancelCompletionDelayTimer {
[self.completionDelayTimer invalidate];
}
总结
说一下整个流程吧:
- 当收到 AFNetworking 的AFNetworkingTaskDidResumeNotification通知后,调用
incrementActivityCount
方法。 - 在
incrementActivityCount
方法中把激活数+1,然后调用updateCurrentStateForNetworkActivityChange
方法更新当前的状态。 - 在
updateCurrentStateForNetworkActivityChange
方法中会设置当前的状态,也就是调用setCurrentState:
方法。 - 在
setCurrentState:
方法中通过当前的状态,来开启或者关闭定时器,然后调用setNetworkActivityIndicatorVisible:
方法。 - 在
setNetworkActivityIndicatorVisible:
方法中设置激活状态。
我在想,如果写一个网络框架,应该是架构在 AFNetworking 上,应该在调用的时候,隐藏所有它的行迹,包括本片文章的这个功能。
参考
synchronized原理和锁优化
objective-c @synchronized 锁用法
推荐阅读
AFNetworking 3.0 源码解读(一)之 AFNetworkReachabilityManager
AFNetworking 3.0 源码解读(二)之 AFSecurityPolicy
AFNetworking 3.0 源码解读(三)之 AFURLRequestSerialization
AFNetworking 3.0 源码解读(四)之 AFURLResponseSerialization
AFNetworking 3.0 源码解读(五)之 AFURLSessionManager
AFNetworking 3.0 源码解读(六)之 AFHTTPSessionManager
AFNetworking 3.0 源码解读(七)之 AFAutoPurgingImageCache
AFNetworking 3.0 源码解读(八)之 AFImageDownloader
AFNetworking 3.0 源码解读(九)之 AFNetworkActivityIndicatorManager的更多相关文章
- AFNetworking 3.0 源码解读(十一)之 UIButton/UIProgressView/UIWebView + AFNetworking
AFNetworking的源码解读马上就结束了,这一篇应该算是倒数第二篇,下一篇会是对AFNetworking中的技术点进行总结. 前言 上一篇我们总结了 UIActivityIndicatorVie ...
- AFNetworking 3.0 源码解读(十)之 UIActivityIndicatorView/UIRefreshControl/UIImageView + AFNetworking
我们应该看到过很多类似这样的例子:某个控件拥有加载网络图片的能力.但这究竟是怎么做到的呢?看完这篇文章就明白了. 前言 这篇我们会介绍 AFNetworking 中的3个UIKit中的分类.UIAct ...
- AFNetworking 3.0 源码解读 总结(干货)(下)
承接上一篇AFNetworking 3.0 源码解读 总结(干货)(上) 21.网络服务类型NSURLRequestNetworkServiceType 示例代码: typedef NS_ENUM(N ...
- AFNetworking 3.0 源码解读(八)之 AFImageDownloader
AFImageDownloader 这个类对写DownloadManager有很大的借鉴意义.在平时的开发中,当我们使用UIImageView加载一个网络上的图片时,其原理就是把图片下载下来,然后再赋 ...
- AFNetworking 3.0 源码解读(七)之 AFAutoPurgingImageCache
这篇我们就要介绍AFAutoPurgingImageCache这个类了.这个类给了我们临时管理图片内存的能力. 前言 假如说我们要写一个通用的网络框架,除了必备的请求数据的方法外,必须提供一个下载器来 ...
- AFNetworking 3.0 源码解读(六)之 AFHTTPSessionManager
AFHTTPSessionManager相对来说比较好理解,代码也比较短.但却是我们平时可能使用最多的类. AFNetworking 3.0 源码解读(一)之 AFNetworkReachabilit ...
- AFNetworking 3.0 源码解读(三)之 AFURLRequestSerialization
这篇就讲到了跟请求相关的类了 关于AFNetworking 3.0 源码解读 的文章篇幅都会很长,因为不仅仅要把代码进行详细的的解释,还会大概讲解和代码相关的知识点. 上半篇: URI编码的知识 关于 ...
- AFNetworking 3.0 源码解读(四)之 AFURLResponseSerialization
本篇是AFNetworking 3.0 源码解读的第四篇了. AFNetworking 3.0 源码解读(一)之 AFNetworkReachabilityManager AFNetworking 3 ...
- AFNetworking 3.0 源码解读(五)之 AFURLSessionManager
本篇是AFNetworking 3.0 源码解读的第五篇了. AFNetworking 3.0 源码解读(一)之 AFNetworkReachabilityManager AFNetworking 3 ...
随机推荐
- [Spring]IoC容器之进击的注解
先啰嗦两句: 第一次在博客园使用markdown编辑,感觉渲染样式差强人意,还是github的样式比较顺眼. 概述 Spring2.5 引入了注解. 于是,一个问题产生了:使用注解方式注入 JavaB ...
- HTML中上传与读取图片或文件(input file)----在路上(25)
input file相关知识简例 在此介绍的input file相关知识为: 上传照片及文件,其中包括单次上传.批量上传.删除照片.增加照片.读取图片.对上传的图片或文件的判断,比如限制图片的张数.限 ...
- Partition2:对表分区
在SQL Server中,普通表可以转化为分区表,而分区表不能转化为普通表,普通表转化成分区表的过程是不可逆的,将普通表转化为分区表的方法是: 在分区架构(Partition Scheme)上创建聚集 ...
- .Net多线程编程—System.Threading.Tasks.Parallel
System.Threading.Tasks.Parallel类提供了Parallel.Invoke,Parallel.For,Parallel.ForEach这三个静态方法. 1 Parallel. ...
- 谈谈一些有趣的CSS题目(九)-- 巧妙的实现 CSS 斜线
开本系列,谈谈一些有趣的 CSS 题目,题目类型天马行空,想到什么说什么,不仅为了拓宽一下解决问题的思路,更涉及一些容易忽视的 CSS 细节. 解题不考虑兼容性,题目天马行空,想到什么说什么,如果解题 ...
- pt-online-schema-change中update触发器的bug
pt-online-schema-change在对表进行表结构变更时,会创建三个触发器. 如下文测试案例中的t2表,表结构如下: mysql> show create table t2\G . ...
- jsp页面无法识别el表达式的解决方案
今天在写一个springmvc的小demo时,碰到一个问题,在jsp页面中书写为${user.username}的表达式语言,在浏览器页面中仍然显示为${user.username},说明jsp根本不 ...
- 在知乎上看到 Web Socket这篇文章讲得确实挺好,从头看到尾都非常形象生动,一口气看完,没有半点模糊,非常不错
在知乎上看到这篇文章讲得确实挺好,从头看到尾都非常形象生动,一口气看完,没有半点模糊,非常不错,所以推荐给大家,非常值得一读. 作者:Ovear链接:https://www.zhihu.com/que ...
- Conversion to Dalvik format failed: Unable to execute dex: Multiple dex files define ...
Conversion to Dalvik format failed: Unable to execute dex: Multiple dex files define ... 这个错误是因为有两个相 ...
- SQL 约束
先用设计器创建约束.再用代码创建约束.数据库约束是为了保证数据的完整性(正确性)而实现的一套机制见文件Employee.sql非空约束(选择复选框)主键约束(PK) primary key const ...