Objective-C三种定时器CADisplayLink / NSTimer / GCD的使用
OC中的三种定时器:CADisplayLink、NSTimer、GCD
我们先来看看CADiskplayLink, 点进头文件里面看看, 用注释来说明下
@interface CADisplayLink : NSObject
{
@private
void *_impl; //指针
} + (CADisplayLink *)displayLinkWithTarget:(id)target selector:(SEL)sel;
//唯一一个初始化方法 - (void)addToRunLoop:(NSRunLoop *)runloop forMode:(NSRunLoopMode)mode;
//将创建好点实例添加到RunLoop中 - (void)removeFromRunLoop:(NSRunLoop *)runloop forMode:(NSRunLoopMode)mode;
//从RunLoop中移除
- (void)invalidate;
//销毁实例 @property(readonly, nonatomic) CFTimeInterval timestamp; //上一次Selector被调用到时间, 只读
@property(readonly, nonatomic) CFTimeInterval duration; //屏幕刷新时间间隔, 目前iOS刷新频率是60HZ, 所以刷新时间间隔是16.7ms @property(readonly, nonatomic) CFTimeInterval targetTimestamp CA_AVAILABLE_IOS_STARTING(10.0, 10.0, 3.0);
//下一次被调用到时间
@property(getter=isPaused, nonatomic) BOOL paused; //设置为YES的时候会暂停事件的触发 @property(nonatomic) NSInteger frameInterval
CA_AVAILABLE_BUT_DEPRECATED_IOS (3.1, 10.0, 9.0, 10.0, 2.0, 3.0, "use preferredFramesPerSecond"); //事件触发间隔。是指两次selector触发之间间隔几次屏幕刷新,默认值为1,也就是说屏幕每刷新一次,执行一次selector,这个也可以间接用来控制动画速度
@property(nonatomic) NSInteger preferredFramesPerSecond CA_AVAILABLE_IOS_STARTING(10.0, 10.0, 3.0);
//每秒现实多少帧 @end
从头文件来看CADisplayLink的使用还是挺简单的, 下面上代码:
- (void)viewDidLoad {
[super viewDidLoad];
self.displayLink = [CADisplayLink displayLinkWithTarget:self
selector:@selector(logCount)];
self.displayLink.frameInterval = ; //屏幕刷新2次调用一次Selector
[self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
}
- (void)logCount {
self.count ++;
NSLog(@"Count = %ld", self.count);
if (self.count > ) {
self.count = ;
[self.displayLink invalidate]; //直接销毁
}
}
代码很简单就不做说明了
需要注意的是CADisplayLink必须要添加到可以执行的RunLoop中才会执行, 当添加到某一个RunLoop后如果该RunLoop暂停或者该RunLoop的Model改变了, 计时器也会暂停
比如我们给TableView添加计时器到当前RunLoop的NSDefaultRunLoopMode model中, 当屏幕一半显示时计时器可以正常调用, 但当我们用手滑动TableView时, 计时器就会暂停。
因为当滑动时, RunLoop会进入到UITrackingRunLoopMode
所以当我们发现计时器没有运行时, 可以检查下是否有加入到正确的mode中
那我们来说一下runloop的几种mode:
- Default模式
定义:NSDefaultRunLoopMode (Cocoa) kCFRunLoopDefaultMode (Core Foundation)
描述:默认模式中几乎包含了所有输入源(NSConnection除外),一般情况下应使用此模式。
- Connection模式
定义:NSConnectionReplyMode(Cocoa)
描述:处理NSConnection对象相关事件,系统内部使用,用户基本不会使用。
- Modal模式
定义:NSModalPanelRunLoopMode(Cocoa)
描述:处理modal panels事件。
- Event tracking模式
定义:UITrackingRunLoopMode(iOS)
NSEventTrackingRunLoopMode(cocoa)
描述:在拖动loop或其他user interface tracking loops时处于此种模式下,在此模式下会限制输入事件的处理。例如,当手指按住UITableView拖动时就会处于此模式。
- Common模式
定义:NSRunLoopCommonModes (Cocoa) kCFRunLoopCommonModes (Core Foundation)
描述:这是一个伪模式,其为一组run loop mode的集合,将输入源加入此模式意味着在Common Modes中包含的所有模式下都可以处理。在Cocoa应用程序中,默认情况下Common Modes包含default modes,modal modes,event Tracking modes.可使用CFRunLoopAddCommonMode方法向Common Modes中添加自定义modes。
注:iOS中仅NSDefaultRunLoopMode,UITrackingRunLoopMode,NSRunLoopCommonModes三种可用mode。
CADisplayLink
基本用法刚刚介绍过。
优势:依托于设备屏幕刷新频率触发事件,所以其触发时间上是最准确的。也是最适合做UI不断刷新的事件,过渡相对流畅,无卡顿感。
缺点:
- 由于依托于屏幕刷新频率,若果CPU不堪重负而影响了屏幕刷新,那么我们的触发事件也会受到相应影响。
- selector触发的时间间隔只能是duration的整倍数。
- selector事件如果大于其触发间隔就会造成掉帧现象。
- CADisplayLink不能被继承。
-------------------我是分割线---------------------
下面说说NSTImer, 一样我们直接看头文件并用注释说明
@interface NSTimer : NSObject + (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
//实例化方法, 响应事件用的NSInvocation, 需要手动添加到RunLoop中才会生效
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
//实例化方法, 响应事件用的NSIvocation, 系统为自动帮你将timer添加到currentRunLoop中,defaultMode
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
//实例化方法, 响应事件用的PerformanceSelector, userInfo中可以用来传参数,需要手动添加到RunLoop中
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
//实例化方法,响应事件用的PerformanceSelector, userInfo可以用来传递参数, 系统会自动帮你将timer添加到currentRunLoop中, defaultMode + (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
//实例化方法, 以block的方式传入要执行的内容, 需要手动添加到RunLoop中 + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
//实例化方法, 以block的方式传入要执行的内容,系统会自动帮你将timer添加到currentRunLoop中,defaultMode - (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
//跟上面类似, 只是多指定了一个开始时间
- (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)ti target:(id)t selector:(SEL)s userInfo:(nullable id)ui repeats:(BOOL)rep NS_DESIGNATED_INITIALIZER;
//跟上面类似, 只是多指定了一个开始时间
- (void)fire; //立即执行一次定时器方法, 注意不是立即开启定时器 @property (copy) NSDate *fireDate; //当前事件的触发事件, 一般用来做暂停和恢复
@property (readonly) NSTimeInterval timeInterval; //只读属性, 获取当前timer的触发间隔 @property NSTimeInterval tolerance NS_AVAILABLE(10_9, 7_0); //允许的误差值 - (void)invalidate; //立即销毁timer
@property (readonly, getter=isValid) BOOL valid; //只读属性, 获取当前timer是否有效 @property (nullable, readonly, retain) id userInfo; //只读属性, 初始化时传入的用户参数 @end
NSTimer的内容相对多一些但也更加灵活, 有一个地方需要注意的是timer开头的实例化方法需要手动添加到RunLoop, Schedule开头的会由系统帮你添加到RunLoop
fireDate,设置当前timer的事件的触发时间。通常我们使用这个属性来做计时器的暂停与恢复。
///暂停计时器
self.timer.fireDate = [NSDate distantFuture];
///恢复计时器
self.timer.fireDate = [NSDate distantPast];
tolerance,
允许误差时间。我们知道NSTimer事件的触发事件是不准确的,完全取决于当前runloop处理的时间。如果当前runloop在处理复杂运算,则timer执行时间将会被推迟,直到复杂运算结束后立即执行触发事件,之后再按照初始设置的节奏去执行。当设置tolerance之后在允许范围内的延迟可以触发事件,超过的则不触发。默认是时间间隔的1/10
网上很多人对fire方法的解释其实并不正确。fire并不是立即激活定时器,而是立即执行一次定时器方法。
当加入到runloop中timer不需要激活即可按照设定的时间触发事件。fire只是相当于手动让timer触发一次事件。
如果timer设置的repeat为NO,则fire之后timer立即销毁。
如果timer的repeat为YES,则到了之前设置的时间他依旧会按部就班的触发事件。
fire只是单独触发了一次事件,并不影响原timer的节奏。
- 关于invalid方法
我们知道NSTimer使用的时候如果不注意的话,是会造成内存泄漏的。原因是我们生成实例的时候,会对控制器retain一下。如果不对其进行管理则VC的永远不会引用计数为零,进而造成内存泄漏。
所以,当我们不需要的timer的时候,请如下操作:
[self.timer invalid];
self.timer = nil;
这样Timer会对VC进行一次release。所以一定不要忘记调用invalid方法。
顺便提一句,如果生成timer实例的时候repeat为NO,那当触发事件结束后,系统也会自动调用invalid一次。
NSTimer的优势:使用相对灵活,应用广泛
劣势:受runloop影响严重,同时易造成内存泄漏(调用invalid方法解决)
-------------------我是分割线---------------------
下面说说GCD计时器:dispatch_source_t
其实dispatch_source_t说为计时器不完全正确, 它实际上是GCD给我们用的一个源对象
还是先直接上代码:
#import "ViewController.h" @interface ViewController () @property (nonatomic, assign) NSInteger count;
@property (nonatomic, strong) dispatch_source_t tTimer; //GCD计时器一定要设置为成员变量, 否则会立即释放 @end @implementation ViewController @synthesize tTimer; - (void)viewDidLoad { [super viewDidLoad]; //创建GCD timer资源, 第一个参数为源类型, 第二个参数是资源要加入的队列
self.tTimer = \
dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, , , dispatch_get_main_queue()); //设置timer信息, 第一个参数是我们的timer对象, 第二个是timer首次触发延迟时间, 第三个参数是触发时间间隔, 最后一个是是timer触发允许的延迟值, 建议值是十分之一
dispatch_source_set_timer(self.tTimer,
dispatch_walltime(NULL, * NSEC_PER_SEC),
0.32 * NSEC_PER_SEC,
); //设置timer的触发事件
dispatch_source_set_event_handler(self.tTimer, ^{ [self logCount];
}); //激活timer对象
dispatch_resume(self.tTimer);
} - (void)logCount { self.count ++;
NSLog(@"Count = %ld", self.count); if (self.count > ) { self.count = ;
//暂停timer对象
dispatch_suspend(self.tTimer); //销毁timer, 注意暂停的timer资源不能直接销毁, 需要先resume再cancel, 否则会造成内存泄漏
//dispatch_source_cancel(self.tTimer);
}
}
注释已经很清楚了, 就不再逐条解释(上面代码会呦循环引用的问题, 大家自己改下)
需要注意的是, GCD timer资源必须设定为成员变量, 否则会在创建完毕后立即释放
suspend挂起或暂停后的timer要先resume才能cancel, 挂起的timer直接cancel会造成内存泄漏
GCDTimer的优势:不受当前runloopMode的影响。
劣势:虽然说不受runloopMode的影响,但是其计时效应仍不是百分之百准确的。
另外,他的触发事件也有可能被阻塞,当GCD内部管理的所有线程都被占用时,其触发事件将被延迟。
好吧GCD我也没用玩转, 只说这些。 后面会找时间专门研究下
Objective-C三种定时器CADisplayLink / NSTimer / GCD的使用的更多相关文章
- cocos2dx三种定时器使用
cocos2dx三种定时器的使用以及停止schedule.scheduleUpdate.scheduleOnce 今天白白跟大家分享一下cocos2dx中定时器的用法. 首先,什么是定时 ...
- C#中三种定时器对象的比较 【转】
https://www.cnblogs.com/zxtceq/p/5667281.html C#中三种定时器对象的比较 ·关于C#中timer类 在C#里关于定时器类就有3个1.定义在System.W ...
- iOS三种定时器的用法NSTimer、CADisplayLink、GCD
一,NSTimer //创建方式1 NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector: ...
- NSTimer、CADisplayLink、GCD 三种定时器的用法 —— 昉
在软件开发过程中,我们常常需要在某个时间后执行某个方法,或者是按照某个周期一直执行某个方法.在这个时候,我们就需要用到定时器. 在iOS中有很多方法完成定时器的任务,例如 NSTimer.CADisp ...
- C#中三种定时器对象的比较
·关于C#中timer类 在C#里关于定时器类就有3个1.定义在System.Windows.Forms里2.定义在System.Threading.Timer类里3.定义在System.Timers ...
- C#三种定时器
三个定时器分别是 实现按用户定义的时间间隔引发事件的计时器.此计时器最宜用于 Windows 窗体应用程序中,并且必须在窗口中使用. System.Windows.Forms.Timer 提供以指定的 ...
- C#三种定时器的实现
http://www.coridc.com/archives/2253.html c#中提供了三种类型的计时器: 1.基于 Windows 的标准计时器(System.Windows.Forms.Ti ...
- iOS 计时器三种定时器的用法NSTimer、CADisplayLink、GCD
原文:http://www.cocoachina.com/ios/20160919/17595.html DEMO链接
- android 三种定时器的写法
//两秒后执行new Handler().postDelayed(new Runnable() { @Override public void run() { --todo }}, 2000); -- ...
随机推荐
- 【Machine Learning】KNN算法虹膜图片识别
K-近邻算法虹膜图片识别实战 作者:白宁超 2017年1月3日18:26:33 摘要:随着机器学习和深度学习的热潮,各种图书层出不穷.然而多数是基础理论知识介绍,缺乏实现的深入理解.本系列文章是作者结 ...
- PHP实现RTX发送消息提醒
RTX是腾讯公司推出的企业级即时通信平台,大多数公司都在使用它,但是我们很多时候需要将自己系统或者产品的一些通知实时推送给RTX,这就需要用到RTX的服务端SDK,建议先去看看RTX的SDK开发文档( ...
- console的高级使用
1.console.table()用来表格化展示数据. var people = { zqz: { name: 'zhaoqize', age: 'guess?' }, wdx: { name: 'w ...
- ASP.NET Core的路由[3]:Router的创建者——RouteBuilder
在<注册URL模式与HttpHandler的映射关系>演示的实例中,我们总是利用一个RouteBuilder对象来为RouterMiddleware中间件创建所需的Router对象,接下来 ...
- CRL快速开发框架系列教程六(分布式缓存解决方案)
本系列目录 CRL快速开发框架系列教程一(Code First数据表不需再关心) CRL快速开发框架系列教程二(基于Lambda表达式查询) CRL快速开发框架系列教程三(更新数据) CRL快速开发框 ...
- 开源一款简单清爽的日历组件,JavaScript版的
源码会在最后给出地址,需要的朋友自己去下载.最近项目需要做一个日程安排的功能,就是点击日历的某一天弹出一个录入页面,填完信息后保存当天的日程安排.有日程的日期会有不同的标记(比如加一个背景色啥的).网 ...
- 强强联合,Testin云测&云层天咨众测学院开课了!
Testin&云层天咨众测学院开课了! 共享经济时代,测试如何赶上大潮,利用碎片时间给女票或者自己赚点化妆品钱? 2016年12月13日,Testin联手云层天咨带领大家一起推开众测的大门 ...
- css3圆形百分比进度条的实现原理
原文地址:css3圆形百分比进度条的实现原理 今天早上起来在查看jquery插件机制的时候,一不小心点进了css3圆形百分比进度条的相关文章,于是一发不可收拾,开始折腾了... 关于圆形圈的实现,想必 ...
- 利用PowerShell复制SQLServer账户的所有权限
问题 对于DBA或者其他运维人员来说授权一个账户的相同权限给另一个账户是一个很普通的任务.但是随着服务器.数据库.应用.使用人员地增加就变得很枯燥乏味又耗时费力的工作.那么有什么容易的办法来实现这个任 ...
- CentOS:设置系统级代理(转)
原文地址:http://www.cnblogs.com/cocowool/archive/2012/07/05/2578487.html YUM代理设置 编辑/etc/yum.conf,在最后加入 # ...