1. 程式人生 > >iOS開發之UITableView中計時器的幾種實現方式(NSTimer、DispatchSource、CADisplayLink)

iOS開發之UITableView中計時器的幾種實現方式(NSTimer、DispatchSource、CADisplayLink)

最近工作比較忙,但是還是出來更新部落格了。今天部落格中所涉及的內容並不複雜,都是一些平時常見的一些問題,通過這篇部落格算是對UITableView中使用定時器的幾種方式進行總結。本篇部落格會給出在TableView中使用NSTimer或者DispatchSourcer中常見的五種方式。當然下方第一種方式是常規做法,不過也是UITableView中使用NSTimer的一個坑。其他三種方式是為了繞過這個坑的解決方案。

當然,本篇部落格共涉及到了UITableView中使用定時器的四種實現方式,當然應該也還有其他實現方式,只不過目前我沒有涉及到。歡迎在評論區提供其他實現方式,我會及時的整合到目前的Demo中。

接下來我們先來總結一下本篇部落格所涉及的四種方式:

  • 第一種就是直接在TableView的Cell上使用NSTimer,當然這種方式是有問題的,稍後會介紹。
  • 第二種是將NSTimer新增到當前執行緒所對應的RunLoop中的commonModes中。
  • 第三種是通過Dispatch中的TimerSource來實現定時器。
  • 第四種是開啟一個新的子執行緒,將NSTimer新增到這個子執行緒中的RunLoop中,並使用DefaultRunLoopModes來執行。
  • 第五種方式就是使用CADisplayLink來實現。

下方我們將會根據具體的示例來詳細的介紹以上這五種實現方式。

一、在Cell中直接使用NSTimer

首先我們按照常規做法,直接在UITableView的Cell上新增相應的NSTimer, 並使用scheduledTimer執行相應的程式碼塊。這種方式沒有什麼特殊的就是對Timer的直接使用。下方是我們本部分的Timer的使用程式碼,當然是使用Swift來實現的,不過與OC的程式碼差不多。程式碼如下所示 :

  

上述程式碼比較簡單,就是在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。具體如下所示:

   

二、將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是同一個。

三、將Timer新增到子執行緒的RunLoop下的DefaultMode中

接下來我們來看另一種解決方案,就是開啟一個新的子執行緒,然後將Timer新增到這個子執行緒所對應的RunLoop中。當然因為是子執行緒的RunLoop,在新增Timer時,我們可以將Timer新增到子執行緒中的RunLoop中的DefaultMode中。新增完畢後,手動執行該RunLoop。

因為是在子執行緒中新增的Timer, Timer肯定是在子執行緒中工作的,所以在更新UI時,我們需要在主執行緒中進行更新,具體程式碼如下所示:

   

在上述程式碼中我們可以看到我們使用全域性的並行佇列來非同步建立了一個Timer物件,然後將該物件新增進了該非同步執行緒中的DefaultRunLoopMode中,然後執行該RunLoop。當然在子執行緒中更新UI還是需要在主執行緒中去操作的。下方就是上述程式碼的執行效果。從該效果中我們不難看出,當滑動TableView時定時器是可以正常工作的。

  

四、DispatchTimerSource

接下來我們就不使用NSTimer來實現定時器了。在之前的部落格中聊GCD時其中用到了DispatchTimerSource來實現定時器。接下來我們就在TableView的Cell上新增DispatchTimerSource,然後看一下執行效果。當然下方程式碼片段我們是在全域性佇列中新增的DispatchTimerSource,在主執行緒中進行更新。當然我們也可以在mainQueue中新增DispatchTimerSource,這樣也是可以正常工作的。當然我們不建議在MainQueue中做,因為在程式設計時儘量的把一些和主執行緒關聯不太大的操作放到子執行緒中去做。程式碼如下所示:

  

接下來我們來看一下上述的程式碼的執行效果,從該效果中我們可以看出該定時器是可以正常工作的。

  

五、CADisplayLink

接下來我們來使用CADisplayLink來實現定時器功能,在之前的部落格中我們也使用過CADisplayLink,不過是用來計算FPS的。下方程式碼片段中我們就使用CADisplayLink來實現的定時器。CADisplayLink可以新增到RunLoop中,RunLoop的每一次迴圈都會觸發CADisplayLink所關聯的方法。在螢幕不卡頓的情況下,每次迴圈的時間時1/60秒

下方程式碼,為了不讓螢幕的卡頓等引起的主執行緒所對應的RunLoop阻塞所造成的定時器不精確的問題。我們開啟了一個新的執行緒,並且將CADisplayLink物件新增到這個子執行緒的RunLoop中,然後在主執行緒中更新UI即可。具體程式碼如下:

  

我們對上述程式碼執行,下方是其對應的執行結果。從下方執行結果中我們不難看出,在TableView滾動時該定時器也是可以正常執行的。當然該方式實現的定時器的精度是比較高的。

  

經過上述五大部分,我們羅列了定時器的幾種實現方式,通過對比我們不難發現其優劣性。上述定時器中DispatchSourceTime以及CADisplayLink的精度要比NSTimer的精度要高。從程式碼實現中我們不難看出CADisplayLink的精度是比較高的。