页面实现多个定时器(计时器)时选用NSTimer还是GCD?(干货不湿)
定时器在我们每个人做的iOS项目里面必不可少,如登录页面倒计时、支付期限倒计时等等,一般来说使用NSTimer创建定时器:
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
But 使用NSTimer需要注意一下几点:
1、必须保证有一个活跃的RunLoop。
系统框架提供了几种创建NSTimer的方法,其中以scheduled开头的方法会自动把timer加入当前RunLoop,到了设定时间就会触发selector方法,而没有scheduled开头的方法则需要手动添加timer到一个RunLoop中才会有效。程序启动时,会默认启动主线程的RunLoop并在程序运行期内有效,所以把timer放入主线程时不需要启动RunLoop,但现实开发中主线程更多的是处理UI事物,把耗时且耗能的操作放在子线程中,这就需要将子线程的RunLoop激活。
我们不难知道RunLoop在运行时一般有两个:NSDefaultRunLoopMode、NSEventTrackingRunLoopMode,scheduled生成的timer会默认添加到NSDefaultRunLoopMode,当某些UI事件发生时,如页面滑动RunLoop切换到NSEventTrackingRunLoopMode运行,我们会发现定时器失效,为了解决timer失效的问题,我们需要在scheduled一个定时器的时候,设置它的运行模式为:
[[NSRunLoop currentRunLoop] addTimer:self.progressTimer forMode:NSRunLoopCommonModes];
注意:NSRunLoopCommonModes并不是一种正在存在的运行状态,这个模式等效于NSDefaultRunLoopMode和NSEventTrackingRunLoopMode的结合,相当于它标记了timer可以在这两种模式下都有效。
2.NSTimer的创建与撤销必须在同一个线程操作,不能跨越线程操作。
3.存在内存泄漏的风险(这个问题需要引起重视)
scheduledTimerWithTimeInterval方法将target设为A对象时,A对象会被这个timer所持有,也就是会被retain一次,timer又会被当前的runloop所持有。使用NSTimer时,timer会保持对target和userInfo参数的强引用。只有当调取了NSTimer的invalidate方法时,NSTimer才会释放target和userInfo。生成timer的方法中如果repeats参数为NO,则定时器触发后会自动调取invalidate方法。如果repeats参数为YES,则需要手动调取invalidate方法才能释放timer对target和userIfo的强引用。
- (void)cancel{
[_timer invalidate];
_timer = nil;
}
这里要特别注意的一点是,按照各种资料显示,我们在销毁或者释放对象时,大部分都是在dealloc方法中,然后我们高高兴兴的在dealloc里写上
- (void)dealloc{
[self cancel];
}
以为这样就可以释放timer了,不幸的是,dealloc方法永远不会被调用。因为timer的引用,对象A的引用计数永远不会降到0,这时如果不调用cancel,对象X将永远无法释放,造成内存泄露。所以我建议在使用定时器的事件完成后立即将timer进行cancel,如果是比较长时间的定时器,可以在页面消失事件中调用,如:
- (void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
[self cancel];
}
看到这里,你会不会发现使用NSTimer实现定时器这么麻烦,又是RunLoop,又是线程的,一会儿还得考虑内存泄露,So , 如果在一个页面需要同时显示多个计时器的时候,NSTimer简直就是灾难了。那么有没有高逼格的办法实现呢?答案就是GCD! 以下5点是使用dispatch_source_t创建timer的主要知识点:
1.获取全局子线程队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
2.创建timer添加到队列中
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
3.设置首次执行事件、执行间隔和精确度
dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, interval * NSEC_PER_SEC), interval * NSEC_PER_SEC, 0.1 * NSEC_PER_SEC);
4.处理事件block
dispatch_source_set_event_handler(timer, ^{
// doSomething()
});
5.激活timer / 取消timer
dispatch_resume(timer); / dispatch_source_cancel(timer);
写到这里,自然要问如果我只是想执行一次,不需要循环实现定时器那怎么办呢?那也没问题,参考NSTimer,我们可以集成repeats选项,当repeats = No时,在激活timer并回调block事件后dispatch_source_cancel掉当前dispatch_source_t timer即可,如下所示:


上面的代码就创建了一个timer,如果repeats = NO,在一个周期完成后,系统会自动cancel掉这个timer;如果repeats=YES,那么timer会一个周期接一个周期的执行,直到你手动cancel掉这个timer,你可以在dealloc方法里面做cancel,这样timer恰好运行于整个对象的生命周期中。这里不必要担心NSTimer因dealloc始终无法调而产生的内存泄漏问题,你也可以通过queue参数控制这个timer所添加到的线程,也就是action最终执行的线程。传入nil则会默认放到子线程中执行。UI相关的操作需要传入dispatch_get_main_queue()以放到主线程中执行。
写到这里,基本上可以满足开发要求,然而我们可以更加变态,假设这样的场景,每次开始新一次的计时前,需要取消掉上一次的计时任务 或者 将上一次计时的任务,合并到新的一次计时中,最终一并执行!针对这两种场景,也已经集成到上面的接口scheduleGCDTimerWithName中。具体代码请看demo!
github地址:https://github.com/BeckWang0912/ZTGCDTimer
页面实现多个定时器(计时器)时选用NSTimer还是GCD?(干货不湿)的更多相关文章
- 设置一个div网页滚动时,使其固定在头部,当页面滚动到距离头部300px时,隐藏该div,另一个div在底部,此时显示;当页面滚动到起始位置时,头部div出现,底部div隐藏
设置一个div网页滚动时,使其固定在头部,当页面滚动到距离头部300px时,隐藏该div,另一个div在底部,此时显示: 当页面滚动到起始位置时,头部div出现,底部div隐藏 前端代码: <! ...
- 时间操作(JavaScript版)—页面显示格式:年月日 上午下午 时分秒 星期
版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/wangshuxuncom/article/details/35222531 <!DOCTYPE ...
- 在子页面使用layer弹出层时只显示遮罩层,不显示弹出框问题
最近子页面使用layer弹出层时只显示遮罩层,不显示弹出框,这个问题搞了很久,最后才发现,在子页面上使用弹出框时,如果只使用layer.alert()或者layer.open()时,会默认在当前页面弹 ...
- JS在即将离开当前页面(刷新或关闭)时触发事件
// onbeforeunload 事件在即将离开当前页面(刷新或关闭)时触发 window.onbeforeunload = function () { return /^\#\/ipinfo/.t ...
- flexpaper上传带中文名字的文档,在页面显示若出现404错误时,请在server.xml文件中进行编码utf-8
flexpaper上传带中文名字的文档,在页面显示若出现404错误时,请在server.xml文件中进行编码utf-8
- android 两种实现计时器时分秒的实现,把时间放在你的手中~
可能我们在开发中会时常用到计时器这玩意儿,比如在录像的时候,我们可能需要在右上角显示一个计时器.这个东西其实实现起来非常简单. 只需要用一个控件Chronometer,是的,就这么简单,我都不好意思讲 ...
- H5 App页面 绝对定位 软键盘弹出时顶起底部按钮
做H5 App页面时,有时候,按钮可能会放到页面的最底下,这个时候可能会用到绝对定位(position: absolute),但是,当input 输入框被点击时,弹出的软键盘会顶起底部的按钮,就像这样 ...
- 2016/3/30 租房子 ①建立租房子的增、删、改php页面 ②多条件查询 ③全选时 各部分全选中 任意checkbox不选中 全选checkbox不选中
字符串的另一种写法:<<<AAAA; 后两个AA回车要求顶格 不然报错 例子: <!DOCTYPE html> <html lang="en" ...
- javaweb 与jsp页面的交互流程 (初次接触时写)
javaweb 与jsp页面的交互流程 javaweb项目目录 1. javaweb项目的一般目录: 2. jsp 页面一般情况下放在 top(前台页面) back(后台页面) 3. 后台代码 放在s ...
随机推荐
- web安全色
web安全色产生的原因 不同的平台(Mac.PC等)有不同的调色板,不同的浏览器也有自己的调色板.这就意味着对于一幅图,显示在Mac上的Web浏览器中的图像,与它在PC上相同浏览器中显示的效果可能差别 ...
- OSI七层模型学习笔记
1.简介 什么是OSI模型呢? OSI模型全名Open System InterConnect 即开放式系统互联,是国际标准化组织(ISO)提出的一个试图使各种计算机在世界范围内互连为网络的标准框架, ...
- 基于jQuery的自定义插件:实现整屏分页转换的功能
动态创建jQuery插件 一.实现功能: 1.基本功能:自适应式整屏分页功能的实现 2.通过鼠标点击标签页转换分页,支持键盘上下左右键的转换分页,同样支持 鼠标滚轮上下滑动转换分页 3.切屏时的动画效 ...
- java中难度大一点的面试题
1.请大概描述一下Vector和ArrayList的区别,Hashtable和HashMap的区别.(5) (1)Vector和ArrayList的异同 实现原理,功能相同,可以互用 主要区别: Ve ...
- 进击 spring !!
1.spring简介 Spring 是一个开源框架,是为了解决企业应用程序开发复杂性而创建的.框架的主要优势之一就是其分层架构,分层架构允许您选择使用某一个组件,同时为 J2EE 应用程序开发提供集成 ...
- Android Weekly Notes Issue #256
Android Weekly Issue #256 May 7th, 2017 Android Weekly Issue #256 本期内容包括: 一个给ViewPager切换时加动画的库; Tail ...
- 交换机进行VLAN划分
一个交换机连接两个虚拟局域网(vlan10和vlan20),通过设置交换机,实现同一虚拟局域网内部相互通信,不同虚拟局域网内不能相互通信 (vlan 将整个网络划分多个广播域,vlan主机不受地理位置 ...
- 简单的3D图片轮播dome
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...
- Android ec环境配置
ec环境配置 1.0概述 鉴于很多同事,或者新从事android开发,虽然会做android的开发,但是会遇见一些最基本的环境搭建问题,本文仅作为(win7 64位系统)eclipse中集成andro ...
- C语言中,隐藏结构体的细节
我们都知道,在C语言中,结构体中的字段都是可以访问的.或者说,在C++ 中,类和结构体的主要区别就是类中成员变量默认为private,而结构体中默认为public.结构体的这一个特性,导致结构体中封装 ...