我们常常会延迟某件任务的执行,或者让某件任务周期性的执行。然后也会在某些时候需要取消掉之前延迟执行的任务。

延迟操作的方案一般有三种:

1.NSObject的方法:

gcdTimer

2.使用NSTimer的方法:

gcdTimer

3.使用GCD的方法:

gcdTimer

一般情况下,我们选择使用GCD的dispatch_after。

因为如果不用GCD,编码需要注意以下三个细节:

1.必须保证有一个活跃的runloop。

performSelector和scheduledTimerWithTimeInterval方法都是基于runloop的。我们知道,当一个应用启动时,系统会开启一个主线程,并且把主线程的runloop激活,也就是run起来,并且主线程的runloop是不会停止的。所以,当这两个方法在主线程可以被正常调用。但情况往往不是这样的。实际编码中,我们更多的逻辑是放在子线程中执行的。而子线程的runloop是默认关闭的。这时如果不手动激活runloop,performSelector和scheduledTimerWithTimeInterval的调用将是无效的。

2.NSTimer的创建与撤销必须在同一个线程操作、performSelector的创建与撤销必须在同一个线程操作。

3.内存管理有潜在泄露的风险

scheduledTimerWithTimeInterval方法将target设为A对象时,A对象会被这个timer所持有,也就是会被retain一次,timer会被当前的runloop所持有。performSelector:withObject:afterDelay:方法实际上是在当前线程的runloop里帮你创建的一个timer去执行任务,所以和scheduledTimerWithTimeInterval方法一样会retain其调用对象。但是,我们往往不希望因为这些延迟操作而影响对象的生命周期,更甚至是,导致对象无法释放。举个例子:

gcdTimer

你创建的对象X中有一个实例变量_timer,方法fireTimer触发一个timer,方法cancel取消这个timer。在对象X需要销毁的时候,需要将它的timer取消掉。

gcdTimer

不幸的是,dealloc方法将永远不会被调用。因为timer的引用,对象X的引用计数永远不会降到0,dealloc方法也就不会被调用。这时如果不调用cancel,对象X将永远无法释放,造成内存泄露。想想一个对象若不调用某一个方法就会造成内存泄露,这该是多大一个坑。

This method is the only way to remove a timer from an NSRunLoop object. The NSRunLoop object removes its strong reference to the timer, either just before the invalidate method returns or at some later point.

If it was configured with target and user info objects, the receiver removes its strong references to those objects as well.

You must send this message from the thread on which the timer was installed. If you send this message from another thread, the input source associated with the timer may not be removed from its run loop, which could prevent the thread from exiting properly.

上面摘自苹果官方文档对invalidate方法的解释。可以看到,当一个timer被schedule的时候,timer会持有target对象,NSRunLoop对象会持有timer。当invalidate被调用时,NSRunLoop对象会释放对timer的持有,timer会释放对target的持有。除此之外,我们没有途径可以释放timer对target的持有。所以解决内存泄露就必须撤销timer,若不撤销,target对象将永远无法释放。

若使用dispatch_after,系统会帮我们处理线程级的逻辑,这样也我们更易于享受系统对线程所做的优化。除此之外,我们不用关心runloop的问题。并且调用的对象也不会被强行持有,这样上述的内存问题也不复存在。当然,需要注意block会持有其传入的对象,但这可以通过weakself解决。所以在这种延迟操作方案中,使用dispatch_after更佳。

但是呢,dispatch_after有个致命的弱点:dispatch_after一旦执行后,就不能撤销了。而performSelector可以使用cancelPreviousPerformRequestsWithTarget方法撤销,NSTimer也可以调用invalidate进行撤销。(注意:撤销任务与创建timer任务必须在同一个线程,即同一个runloop)所以我们还是得用NSTimer或者performSelector吗?

NO,其实GCD也有timer的功能。用GCD来实现一个timer:

gcdTimer

这样我们就规避了NSTimer的三个缺陷。

到这里问题基本得到了解决,但是我们还可以做的更好:)

1.GCD的timer使用的API比较冗余,每次使用都会copy代码。2.没有repeats的选项,若只想执行一次还得自己写标记位控制。这些问题我们都可以封装成一个统一的API:

gcdTimer

这样,外部只需调用这个两个接口,用起来和NSTimer一样方便!

gcdTimer

上面的代码就创建了一个名叫myTimer的timer,这个timer将在2 seconds后执行一个block,随后timer自动停止并被释放。当然,如果repeats参数传入的是YES,那么这么timer会一个周期接一个周期的执行,直到你cancel掉这个timer。

当然,你可以在self对象的dealloc方法里面做cancel,这样保证了timer恰好运行于整个对象的生命周期中。这是NSTimer和performSelector所做不到的事情。你也可以通过queue参数控制这个timer所添加到的线程,也就是action最终执行的线程。传入nil则会默认放到子线程中执行。UI相关的操作需要传入dispatch_get_main_queue()以放到主线程中执行。

Well, we can actually do even better.

注意到,我们经常遇到的场景是,在开始新一次计时的时候,取消掉上一次的计时。也就是每次schedule之前先cancel。这部分对任务处理的能力,也是可以集成到我们的组件中的。我们可以向外部提供一个枚举类型的选项,以选择其对任务的处理类型:

gcdTimer

或者场景是,在开始新一次计时的时候,取消上一次的计时,但是将上一次计时的任务,合并到新的一次计时中,最终一并执行。

针对这两种场景,JX_GCDTimerManager提供了两个option选项:

gcdTimer

如果你不care这些使用场景的话,默认使用AbandonPreviousAction就行了。需要注意的是,同一个timer建议保持同一个任务处理方式,即相同的ActionOption,如果需要切换option,请注意一下切换的衔接问题。

大家也可以自行去对actionOption做扩展,以满足常见的使用场景。

转载连接:http://www.jianshu.com/p/0c050af6c5ee

选择 GCD 还是 NSTimer的更多相关文章

  1. 选择 GCD 还是 NSTimer ?

    我们常常会延迟某件任务的执行,或者让某件任务周期性的执行.然后也会在某些时候需要取消掉之前延迟执行的任务. 延迟操作的方案一般有三种: 1.NSObject的方法: 2.使用NSTimer的方法: 3 ...

  2. 深入理解 GCD

    前言 首先提出一些问题: dispatch_async 函数如何实现,分发到主队列和全局队列有什么区别,一定会新建线程执行任务么? dispatch_sync 函数如何实现,为什么说 GCD 死锁是队 ...

  3. GCD编程 之 略微提高篇

    额外任务:学习YouXianMing封装好的GCD源码   1.GCD串行队列与并发队列   串行队列一次只执行一个线程,按照添加到队列的顺序依次执行 并发队列一次可以执行多个线程,线程的执行没有先后 ...

  4. 多线程(NSThread、NSOperation、GCD)编程浅谈

    一.基本概念 进程:一个具有一定独立功能的程序关于某个数据集合的一次运行活动.可以理解成一个运行中的应用程序.线程:程序执行流的最小单元,线程是进程中的一个实体.同步:只能在当前线程按先后顺序依次执行 ...

  5. iOS 多线程开发之OperationQueue(二)NSOperation VS GCD

    原创Blog.转载请注明出处 blog.csdn.net/hello_hwc 欢迎关注我的iOS SDK具体解释专栏 http://blog.csdn.net/column/details/huang ...

  6. 3.多线程NSOperation

    1.NSOperation的基本操作 使用NSOperation的两个子类,NSInvocationOperation 和 NSBlockOperation 创建操作,然后将操作添加到队列中去执行 / ...

  7. iOS定时器的使用

    iOS开发中定时器经常会用到,iOS中常用的定时器有三种,分别是NSTime,CADisplayLink和GCD. NSTimer 方式1 // 创建定时器 NSTimer *timer = [NST ...

  8. iOS开发之多线程技术——NSOperation篇

    本篇将从四个方面对iOS开发中使用到的NSOperation技术进行讲解: 一.什么是NSOperation 二.我们为什么使用NSOperation 三.在实际开发中如何使用NSOperation ...

  9. iOS中的三大定时器

    iOS开发中定时器经常会用到,iOS中常用的定时器有三种,分别是NSTime,CADisplayLink和GCD. NSTimer 方式1 // 创建定时器 NSTimer *timer = [NST ...

随机推荐

  1. jpg图片在开发板上显示

    文件IO项目: 在开发板屏幕上循环显示目录里的图片 a.按照一定的间隔循环显示目录里的bmp图片 b.实现手指滑动来显示目录里的图片(bmp,jpg)上一张,下一张 d1: 1.能操控屏幕(查询开发板 ...

  2. 解析STL中典型的内存分配

    1 vector 在C++中使用vector应该是非常频繁的,但是你是否知道vector在计算内存分配是如何么? 在c++中vector是非常类似数组,但是他比数组更加灵活,这就表现在他的大小是可以自 ...

  3. AngularJS的五个超酷特性

    AngularJS是一个超棒的javascript框架,不单单对于开发人员来说非常有吸引力,对于UI设计师来说也同样出色.在这篇教程中,我们将简单的介绍AngularJS几个重量级必备特性,并且介绍它 ...

  4. 【Android】 分享一个完整的项目,适合新手!

    写这个app之前是因为看了头条的一篇文章:http://www.managershare.com/post/155110,然后心想要不做一个这样的app,让手机计算就行了.也就没多想就去开始整了.   ...

  5. C++ 头文件系列(unordered_map、unordered_set)

    简介 很明显,这两个头文件分别是map.set头文件对应的unordered版本. 所以它们有一个重要的性质就是: 乱序 如何乱序 这个unorder暗示着,这两个头文件中类的底层实现----Hash ...

  6. [CSS3] 学习笔记-HTML与CSS简单页面效果实例

    一个简单的首页的设计: html文件: <!doctype html> <html> <head> <meta charset="UTF-8&quo ...

  7. We Chall-Training: Stegano I-Writeup

    MarkdownPad Document html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,ab ...

  8. 笑谈ArcToolbox (1) ArcToolbox 的发展方向

    笑谈ArcToolbox (1) ArcToolbox 的发展方向 by 李远祥 ArcGIS工具箱ArcToolbox具有非常多的工具,相信用过的人都非常惊叹,其功能完备并且强大,种类繁多,总能找到 ...

  9. Error:(2, 0) Plugin with id 'com.github.dcendents.android-maven' not found. <a href="openFile:I:\API\PermissionGen-master\permissiongen\build.gradle">Open File</a>

    不上图说个*** 报这个错的原因是因为 你在project中的build.gradle 少写了两句话 所以报这个错 你只需要在build.gradle中添加 classpath 'com.github ...

  10. 关于nodejs express4.X框架不支持layout模板的问题解决

    网上有有种方法是安装express-partials模块,然后在 app.set(‘view engine’, ‘ejs’); 这句后面加上app.use(partials());但是,经过我的反复尝 ...