咱们经常会延迟某件任务的执行,或者让某件任务周期性的执行。而后也会在某些时候须要取消掉以前延迟执行的任务。html
延迟操做的方案通常有三种:app
1.NSObject的方法:oop
2.使用NSTimer的方法:优化
3.使用GCD的方法:this
通常状况下,咱们选择使用GCD的dispatch_after。编码
由于若是不用GCD,编码须要注意如下三个细节:spa
1.必须保证有一个活跃的runloop。线程
performSelector和scheduledTimerWithTimeInterval方法都是基于runloop的。咱们知道,当一个应用启动时,系统会开启一个主线程,而且把主线程的runloop激活,也就是run起来,而且主线程的runloop是不会中止的。因此,当这两个方法在主线程能够被正常调用。但状况每每不是这样的。实际编码中,咱们更多的逻辑是放在子线程中执行的。而子线程的runloop是默认关闭的。这时若是不手动激活runloop,performSelector和scheduledTimerWithTimeInterval的调用将是无效的。code
2.NSTimer的建立与撤销必须在同一个线程操做、performSelector的建立与撤销必须在同一个线程操做。orm
3.内存管理有潜在泄露的风险
scheduledTimerWithTimeInterval方法将target设为A对象时,A对象会被这个timer所持有,也就是会被retain一次,timer会被当前的runloop所持有。performSelector:withObject:afterDelay:方法其实是在当前线程的runloop里帮你建立的一个timer去执行任务,因此和scheduledTimerWithTimeInterval方法同样会retain其调用对象。可是,咱们每每不但愿由于这些延迟操做而影响对象的生命周期,更甚至是,致使对象没法释放。举个例子:
你建立的对象X中有一个实例变量_timer,方法fireTimer触发一个timer,方法cancel取消这个timer。在对象X须要销毁的时候,须要将它的timer取消掉。
不幸的是,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:
这样咱们就规避了NSTimer的三个缺陷。
到这里问题基本获得了解决,可是咱们还能够作的更好:)
1.GCD的timer使用的API比较冗余,每次使用都会copy代码。2.没有repeats的选项,若只想执行一次还得本身写标记位控制。这些问题咱们均可以封装成一个统一的API:
这样,外部只需调用这个两个接口,用起来和NSTimer同样方便!
上面的代码就建立了一个名叫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。这部分对任务处理的能力,也是能够集成到咱们的组件中的。咱们能够向外部提供一个枚举类型的选项,以选择其对任务的处理类型:
或者场景是,在开始新一次计时的时候,取消上一次的计时,可是将上一次计时的任务,合并到新的一次计时中,最终一并执行。
针对这两种场景,JX_GCDTimerManager提供了两个option选项:
若是你不care这些使用场景的话,默认使用AbandonPreviousAction就好了。须要注意的是,同一个timer建议保持同一个任务处理方式,即相同的ActionOption,若是须要切换option,请注意一下切换的衔接问题。
你们也能够自行去对actionOption作扩展,以知足常见的使用场景。
转载链接:http://www.jianshu.com/p/0c050af6c5ee