定时器在我们每个人做的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, );

2.创建timer添加到队列中

dispatch_source_t  timer =  dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, , , 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即可,如下所示:

- (void)scheduledDispatchTimerWithName:(NSString *)timerName
timeInterval:(double)interval
queue:(dispatch_queue_t)queue
repeats:(BOOL)repeats
actionOption:(ActionOption)option
action:(dispatch_block_t)action{ if (nil == timerName)
return; if (nil == queue)
queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, ); dispatch_source_t timer = [self.timerContainer objectForKey:timerName];
if (!timer) {
timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, , , queue);
dispatch_resume(timer);
[self.timerContainer setObject:timer forKey:timerName];
} /* timer精度为0.1秒 */
dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, interval * NSEC_PER_SEC), interval * NSEC_PER_SEC, 0.1 * NSEC_PER_SEC); __weak typeof(self) weakSelf = self; switch (option) { case AbandonPreviousAction:
{
/* 移除之前的action */
[weakSelf removeActionCacheForTimer:timerName]; dispatch_source_set_event_handler(timer, ^{
action(); if (!repeats) {
[weakSelf cancelTimerWithName:timerName];
}
});
}
break; case MergePreviousAction:
{
/* cache本次的action */
[self cacheAction:action forTimer:timerName]; dispatch_source_set_event_handler(timer, ^{
NSMutableArray *actionArray = [self.actionBlockCache objectForKey:timerName];
[actionArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
dispatch_block_t actionBlock = obj;
actionBlock();
}];
[weakSelf removeActionCacheForTimer:timerName]; if (!repeats) {
[weakSelf cancelTimerWithName:timerName];
}
});
}
break;
}
} - (void)cancelTimerWithName:(NSString *)timerName{ dispatch_source_t timer = [self.timerContainer objectForKey:timerName]; if (!timer) {
return;
} [self.timerContainer removeObjectForKey:timerName];
dispatch_source_cancel(timer); [self.actionBlockCache removeObjectForKey:timerName];
}

上面的代码就创建了一个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  如果文章对您有帮助的话,请star,谢谢!

最新文章

  1. 新手如何在gdb中存活
  2. Filezilla 适用于 Win2003 和 WinXP 的版本
  3. office快速制作简历
  4. 闲扯 『 document.write 』
  5. 启动tomcat后struts框架报异常严重: Exception starting filter struts2 Unable to load configuration.
  6. Windows下通过bat脚本实现自动上传文件到ftp服务器
  7. JUnit 单元测试 配置
  8. vps选择
  9. LoaderManager使用详解(三)---实现Loaders
  10. _cdel stdcall
  11. canvas总结:线段宽度与像素边界
  12. mysql函数二
  13. [Python] 文科生零基础学编程系列三——数据运算符的基本类别
  14. oracle ebs应用产品安全性-安全性规则
  15. 使用github 的相关博客
  16. 【转载】opencv实现人脸检测
  17. 日常踩坑 — 相邻元素之间的margin合并问题。
  18. Kafka consumer group位移0ffset重设
  19. Activiti搭建
  20. Python开发【模块】:Celery 分布式异步消息任务队列

热门文章

  1. spring启动,spring mvc ,要不要xml配置,基于注解配置
  2. Mybatis入门——基础方式的增删该查、mapper动态代理方式的CRUD、类型转换器
  3. 20190221 beautiful soup 入门
  4. Debian8.8同步时间
  5. callable和runnable的区别
  6. redis day02 下
  7. 吴裕雄--天生自然python学习笔记:python 用pygame模块开发俄罗斯方块游戏
  8. 分享一个腾讯域名拦截检测api
  9. 《杜拉拉升职记》//TODO
  10. LGOJ3327 【SDOI2015】约数个数和