iOS开发之UITableView中计时器的几种实现方式(NSTimer、DispatchSource、CADisplayLink)

最近工做比较忙,可是仍是出来更新博客了。今天博客中所涉及的内容并不复杂,都是一些平时常见的一些问题,经过这篇博客算是对UITableView中使用定时器的几种方式进行总结。本篇博客会给出在TableView中使用NSTimer或者DispatchSourcer中常见的五种方式。固然下方第一种方式是常规作法,不过也是UITableView中使用NSTimer的一个坑。其余三种方式是为了绕过这个坑的解决方案。git

固然,本篇博客共涉及到了UITableView中使用定时器的四种实现方式,固然应该也还有其余实现方式,只不过目前我没有涉及到。欢迎在评论区提供其余实现方式,我会及时的整合到目前的Demo中。github

接下来咱们先来总结一下本篇博客所涉及的四种方式:编程

  • 第一种就是直接在 TableView的Cell上使用NSTimer,固然这种方式是有问题的,稍后会介绍。
  • 第二种是将NSTimer添加到当前线程所对应的RunLoop中的 commonModes中。
  • 第三种是经过 Dispatch中的TimerSource来实现定时器。
  • 第四种是开启一个新的子线程,将NSTimer添加到这个子线程中的RunLoop中,并使用 DefaultRunLoopModes来执行。
  • 第五种方式就是使用 CADisplayLink来实现。

下方咱们将会根据具体的示例来详细的介绍以上这五种实现方式。异步

 

 

1、在Cell中直接使用NSTimeroop

首先咱们按照常规作法,直接在UITableView的Cell上添加相应的NSTimer, 并使用scheduledTimer执行相应的代码块。这种方式没有什么特殊的就是对Timer的直接使用。下方是咱们本部分的Timer的使用代码,固然是使用Swift来实现的,不过与OC的代码差很少。代码以下所示 :spa

  

 

上述代码比较简单,就是在Cell上添加了一个定时器,而后没1秒更新一次时间,并在Cell的timeLabel上显示,运行效果以下所示。从该运行效果中咱们不难发现,当咱们滑动TableView时,该定时器就中止了工做。具体缘由就是当前线程的RunLoop在TableView滑动时将DefaultMode切换到了TrackingRunLoopMode。由于Timer默认是添加在RunLoop上的DefaultMode上的,当Mode切换后Timer就中止了运行。线程

可是当中止滑动后,Mode又切换了回来,因此Timer有能够正常工做了。代理

  

 

为了进一步看一下Mode的切换,咱们能够在相应的地方获取当前线程的RunLoop而且打印对应的Mode。下方代码就是在TableView所对应的VC上添加的,咱们在viewDidLoad()、viewDidAppear()以及scrollViewDidScroll()这个代理方法中对当前线程所对应的RunLoop下的currentMode进行了打印,其代码以下。对象

  

  

 

下方就是最终的运行结果。从输出结果中咱们不难看出,在viewDidLoad()方法中打印的Current ModeUIInitializationRunLoopMode, 从该Mode的名字中咱们不难发现,该Mode负责UI的初始化。在viewDidApperar()方法中,也就是UI显示后,RunLoop的Mode切换成了kCFRunLoopDefaultMode。紧接着,咱们去滑动TableView,而后在scrollViewDidScroll()代理方法中打印滑动时当前RunLoop所对应的Mode。从下方运行结果不难看出,当TableView滑动时,打印出的currentModel为UITrackingRunLoopMode。当中止滑动后,点击Show Current Mode按钮获取当前Mode时,打印的有时RunLoopDefaultMode。具体以下所示:blog

   

 

 

2、将Timer添加到CommonMode中

上一部分的定时器是不能正常运行的,由于NSTimer对象默认添加到了当前RunLoopDefaultMode中,而在切换成TrackingRunLoopMode时,定时器就中止了工做。解决该问题最直接方法是,将NSTimer在TrackingRunLoopMode中也添加一份。这样的话不管是在DefaultMode仍是TrackingRunLoopMode中,定时器都会正常的工做。

若是你对RunLoop比较熟悉的话,能够知道CommonModes就是DefaultModeTrackingRunLoopMode的集合,因此咱们只须要将NSTimer对象与当前线程所对应的RunLoop中的CommonModes关联便可,具体代码以下所示:

  

 

上述代码与第一部分的代码不一样的地方在于咱们将建立好的定时器添加到了当前RunLoop中的CommonModes中,这样的话能够保证TableView在滑动时定时器也能够正常运行。上述代码最终的运行效果以下所示。

  

从该运行效果咱们不难发现,当该TableView滚动式,其Cell上的定时器是能够正常工做的。可是当咱们滑动右上角的这个TableView时,第一个的TableView中的定时器也是不能正常工做的,由于这些TableView都在主线程中工做,也就是说这些TableView所在的RunLoop是同一个。

 

 

3、将Timer添加到子线程的RunLoop下的DefaultMode中

接下来咱们来看另外一种解决方案,就是开启一个新的子线程,而后将Timer添加到这个子线程所对应的RunLoop中。固然由于是子线程的RunLoop,在添加Timer时,咱们能够将Timer添加到子线程中的RunLoop中的DefaultMode中。添加完毕后,手动运行该RunLoop。

由于是在子线程中添加的Timer, Timer确定是在子线程中工做的,因此在更新UI时,咱们须要在主线程中进行更新,具体代码以下所示:

   

 

在上述代码中咱们能够看到咱们使用全局的并行队列来异步建立了一个Timer对象,而后将该对象添加进了该异步线程中的DefaultRunLoopMode中,而后运行该RunLoop。固然在子线程中更新UI仍是须要在主线程中去操做的。下方就是上述代码的运行效果。从该效果中咱们不难看出,当滑动TableView时定时器是能够正常工做的。

  

 

 

4、DispatchTimerSource

接下来咱们就不使用NSTimer来实现定时器了。在以前的博客中聊GCD时其中用到了DispatchTimerSource来实现定时器。接下来咱们就在TableView的Cell上添加DispatchTimerSource,而后看一下运行效果。固然下方代码片断咱们是在全局队列中添加的DispatchTimerSource,在主线程中进行更新。固然咱们也能够在mainQueue中添加DispatchTimerSource,这样也是能够正常工做的。固然咱们不建议在MainQueue中作,由于在编程时尽可能的把一些和主线程关联不太大的操做放到子线程中去作。代码以下所示:

  

 

接下来咱们来看一下上述的代码的运行效果,从该效果中咱们能够看出该定时器是能够正常工做的。

  

 

 

5、CADisplayLink

接下来咱们来使用CADisplayLink来实现定时器功能,在以前的博客中咱们也使用过CADisplayLink,不过是用来计算FPS的。下方代码片断中咱们就使用CADisplayLink来实现的定时器。CADisplayLink能够添加到RunLoop中,RunLoop的每一次循环都会触发CADisplayLink所关联的方法。在屏幕不卡顿的状况下,每次循环的时间时1/60秒

下方代码,为了避免让屏幕的卡顿等引发的主线程所对应的RunLoop阻塞所形成的定时器不精确的问题。咱们开启了一个新的线程,而且将CADisplayLink对象添加到这个子线程的RunLoop中,而后在主线程中更新UI便可。具体代码以下:

  

 

咱们对上述代码运行,下方是其对应的运行结果。从下方运行结果中咱们不难看出,在TableView滚动时该定时器也是能够正常运行的。固然该方式实现的定时器的精度是比较高的。

  

 

通过上述五大部分,咱们罗列了定时器的几种实现方式,经过对比咱们不难发现其优劣性。上述定时器中DispatchSourceTime以及CADisplayLink的精度要比NSTimer的精度要高。从代码实现中咱们不难看出CADisplayLink的精度是比较高的。

本篇博客所涉及代码的github分享地址为:https://github.com/lizelu/NSTimerWithRunLoop

相关文章
相关标签/搜索