咱們經常會延遲某件任務的執行,或者讓某件任務週期性的執行。而後也會在某些時候須要取消掉以前延遲執行的任務。html
延遲操做的方案通常有三種:app
1.NSObject的方法:oop
2.使用NSTimer的方法:優化
3.使用GCD的方法:this
通常狀況下,咱們選擇使用GCD的dispatch_after。編碼
由於若是不用GCD,編碼須要注意如下三個細節:spa
1.必須保證有一個活躍的runloop。線程
performSelector和scheduledTimerWithTimeInterval方法都是基於runloop的。咱們知道,當一個應用啓動時,系統會開啓一個主線程,而且把主線程的runloop激活,也就是run起來,而且主線程的runloop是不會中止的。因此,當這兩個方法在主線程能夠被正常調用。但狀況每每不是這樣的。實際編碼中,咱們更多的邏輯是放在子線程中執行的。而子線程的runloop是默認關閉的。這時若是不手動激活runloop,performSelector和scheduledTimerWithTimeInterval的調用將是無效的。code
2.NSTimer的建立與撤銷必須在同一個線程操做、performSelector的建立與撤銷必須在同一個線程操做。orm
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的引用計數永遠不會降到0,dealloc方法也就不會被調用。這時若是不調用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作擴展,以知足常見的使用場景。
轉載鏈接:http://www.jianshu.com/p/0c050af6c5ee