河北工程大学网站开发成本,厦门建设网官方网站,潍坊市安丘建设局网站,找网站公司做网站用了织梦可以吗我们常常会延迟某件任务的执行#xff0c;或者让某件任务周期性的执行。然后也会在某些时候需要取消掉之前延迟执行的任务。 延迟操作的方案一般有三种#xff1a; 1.NSObject的方法#xff1a; 2.使用NSTimer的方法#xff1a; 3.使用GCD的方法#xff1a; 一般情况下或者让某件任务周期性的执行。然后也会在某些时候需要取消掉之前延迟执行的任务。 延迟操作的方案一般有三种 1.NSObject的方法 2.使用NSTimer的方法 3.使用GCD的方法 一般情况下我们选择使用GCD的dispatch_after。 因为如果不用GCD编码需要注意以下三个细节 1.必须保证有一个活跃的runloop。 performSelector和scheduledTimerWithTimeInterval方法都是基于runloop的。我们知道当一个应用启动时系统会开启一个主线程并且把主线程的runloop激活也就是run起来并且主线程的runloop是不会停止的。所以当这两个方法在主线程可以被正常调用。但情况往往不是这样的。实际编码中我们更多的逻辑是放在子线程中执行的。而子线程的runloop是默认关闭的。这时如果不手动激活runloopperformSelector和scheduledTimerWithTimeInterval的调用将是无效的。 2.NSTimer的创建与撤销必须在同一个线程操作、performSelector的创建与撤销必须在同一个线程操作。 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的引用计数永远不会降到0dealloc方法也就不会被调用。这时如果不调用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做扩展以满足常见的使用场景。 详细的实现见git源码。 综上选择使用GCD的技术有助于我们提高代码的健壮性与稳定性。 注文中若有错误恭请斧正哈) 源码地址https://github.com/Joeyqiushi/JX_GCDTimer