iOS 学习之 TableView 中多个定时器实现方案
前言
电商项目中有很多这种倒计时抢购界面,要在 TableView 上刷新接下来要展示的商品的倒计时。
听到这个上面的这个需求,第一点你会想到什么了,定时器没错,接下来是 Cell 里面放定时器吗,那么如果是这样的话会产生什么样的后果呢?
不妨实战一下写个 Demo 看看效果到底如何,创建一个 Tableview,然后每一行的 cell 的高度设置 成100 ,在 iphone 6 的屏幕上大致创建至少 6 个定时器左右 (只是在屏幕内的 cell 创建定时器) 这个就足以导致性能下降,结果滑了几下,页面上的数据显示就混乱了。
我们都知道,Tableview 是复用机制,在手指滑动的时候,会不停的调用 cellForRowAtIndexPath
这个方法读取复用 cell,也就意味着在这个时候会不停的去刷新 cell 上显示的数据源。定时器放到 cell 内部处理,很容易出现数据对不上的情况,而且还会造成大量的无意义的额外性能开销。
优化方案
既然在 cell 中创建 NSTimer 不可取,那么我们就在 Controller 中创建好了,但是有一个问题,在 Controller 中创建的话我们如何知道要刷新哪一条 cell 数据呢,别着急,iOS 提供了一个 NSMutableArray *visiableCells
可读的数组属性。此属性可以得到屏幕下正在显示中的 cell,也就是说只要能够获取到 cell,我们就能通过 Timer 刷新 cell 上的数据,一个定时器全局就可以处理倒计时抢购需求。
思路分析完毕,下面开始编码。
创建一个定时器,注意:把 NSTimer 放进 RunLoop 中
1 | - (void)creatTimer { |
问题:为什么要把定时器放进 RunLoop 中?
首先,抽象的了解一下 RunLoop,RunLoop 大致可以想象成一个死循环,像下面这样
1 | do { |
然而实际会比这种简单的循环复杂的多,RunLoop 通过 do-while 来让程序持续运行,接受用户输入,调度事件,同时当每户没有输入、没有调度事件的时候就让 CPU 休息,节省 CPU。
- 每条线程都有且只有一个 RunLoop,所以
[NSRunLoop currentRunLoop]
类似于懒加载,如果当前线程没有 RunLoop 就创建一个 RunLoop,如果线程有 RunLoop 就获得当前线程的 RunLoop;- NSRunLoopCommonModes 是 RunLoop 的运行模式。RunLoop 只能在一种运行模式下运行,如果切换运行模式,RunLoop 会退出当前运行模式,进入另一个运行模式。系统默认注册5种运行模式,但是我们用到的3种分别是:
- NSDefaultRunLoopMode // App 的默认 Mode,通常主线程是在这个 Mode 下运行
- UITrackingRunLoopMode // 界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
- NSRunLoopCommonModes // 这是一个伪模式,其为一组 RunLoopModel 的集合,将输入源加入此模式意味着在 CommonModes 中包含的所有模式下都可以处理
NSDefaultRunLoopMode 是默认模式,当有 ScrollView 的滚动的时候,RunLoop 会退出NSDefaultRunLoopMode 模式,进入 UITrackingRunLoopMod e模式获得更流畅的滚动效果,如果将 NSTimer 放在 NSDefaultRunLoopMode 模式下面,那么当 ScrollView 滚动的时候,NSTimer 将不会运行。
如果 ScrollView 停止滚动的时候,会从 UITrackingRunLoopMode 切换到 NSDefaultRunLoopMode 模式,如果将 NSTimer 放在 UITrackingRunLoopMode 模式下面,那么只有滚动 ScrollView 的时候,NSTimer 才运行。
如果要要两种模式下面都运行 NSTimer 怎么办呢?当然,可以在两种 Mode 里面都加入 NSTimer。另外一种方法,系统提供了 CommonModes 属性。如果将将 NSTimer 放入此模式下,那么任何模式下 NSTimer 都会运行。
有了定时器创建那肯定有销毁,所以一定不能忘记
1 | - (void)viewWillAppear:(BOOL)animated { |
执行倒计时
1 | - (void)countdownAction { |
iOS 中数组有这样一个方法 makeObjectsPerformSelector
,这个方法的意思是数组里面的对象都执行一个方法,我们只需要在 cell 中定义 reloadTimer
方法,用来更新界面数据,这样下来代码量减少了很多,一个定时器来全局管理倒计时就基本能达到要求。