最近工做比較忙,可是仍是出來更新博客了。今天博客中所涉及的內容並不複雜,都是一些平時常見的一些問題,經過這篇博客算是對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 Mode爲UIInitializationRunLoopMode, 從該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對象默認添加到了當前RunLoop的DefaultMode中,而在切換成TrackingRunLoopMode時,定時器就中止了工做。解決該問題最直接方法是,將NSTimer在TrackingRunLoopMode中也添加一份。這樣的話不管是在DefaultMode仍是TrackingRunLoopMode中,定時器都會正常的工做。
若是你對RunLoop比較熟悉的話,能夠知道CommonModes就是DefaultMode和TrackingRunLoopMode的集合,因此咱們只須要將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