Objective-C三種定時器CADisplayLink / NSTimer / GCD的使用

來源:互聯網
上載者:User

標籤:and   interval   cpu   調用   產生   拖動   ase   sign   研究   

OC中的三種定時器:CADisplayLink、NSTimer、GCD

 

我們先來看看CADiskplayLink, 點進標頭檔裡面看看, 用注釋來說明下

@interface CADisplayLink : NSObject{@private  void *_impl;  //指標}+ (CADisplayLink *)displayLinkWithTarget:(id)target selector:(SEL)sel;
//唯一一個初始化方法- (void)addToRunLoop:(NSRunLoop *)runloop forMode:(NSRunLoopMode)mode;
//將建立好點執行個體添加到RunLoop中- (void)removeFromRunLoop:(NSRunLoop *)runloop forMode:(NSRunLoopMode)mode;//從RunLoop中移除
- (void)invalidate;
//銷毀執行個體@property(readonly, nonatomic) CFTimeInterval timestamp;   //上一次Selector被調用到時間, 唯讀@property(readonly, nonatomic) CFTimeInterval duration;   //螢幕重新整理時間間隔, 目前iOS重新整理頻率是60HZ, 所以重新整理時間間隔是16.7ms@property(readonly, nonatomic) CFTimeInterval targetTimestamp CA_AVAILABLE_IOS_STARTING(10.0, 10.0, 3.0);//下一次被調用到時間
@property(getter=isPaused, nonatomic) BOOL paused;  //設定為YES的時候會暫停事件的觸發@property(nonatomic) NSInteger frameInterval       CA_AVAILABLE_BUT_DEPRECATED_IOS (3.1, 10.0, 9.0, 10.0, 2.0, 3.0, "use preferredFramesPerSecond");

//事件觸發間隔。是指兩次selector觸發之間間隔幾次螢幕重新整理,預設值為1,也就是說螢幕每重新整理一次,執行一次selector,這個也可以間接用來控制動畫速度
@property(nonatomic) NSInteger preferredFramesPerSecond CA_AVAILABLE_IOS_STARTING(10.0, 10.0, 3.0); 
//每秒現實多少幀

@end

從標頭檔來看CADisplayLink的使用還是挺簡單的, 下面上代碼:

- (void)viewDidLoad {        [super viewDidLoad];        self.displayLink = [CADisplayLink displayLinkWithTarget:self                                                   selector:@selector(logCount)];    self.displayLink.frameInterval  = 2;    //螢幕重新整理2次調用一次Selector        [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];    }- (void)logCount {        self.count ++;    NSLog(@"Count = %ld", self.count);        if (self.count > 99) {                self.count = 0;        [self.displayLink invalidate]; //直接銷毀    }}

代碼很簡單就不做說明了

需要注意的是CADisplayLink必須要添加到可以執行的RunLoop中才會執行, 當添加到某一個RunLoop後如果該RunLoop暫停或者該RunLoop的Model改變了, 計時器也會暫停

比如我們給TableView添加計時器到當前RunLoop的NSDefaultRunLoopMode model中, 當螢幕一半顯示時計時器可以正常調用, 但當我們用手滑動TableView時, 計時器就會暫停。

因為當滑動時, RunLoop會進入到UITrackingRunLoopMode

所以當我們發現計時器沒有運行時, 可以檢查下是否有加入到正確的mode中

那我們來說一下runloop的幾種mode:

  • Default模式

定義:NSDefaultRunLoopMode (Cocoa) kCFRunLoopDefaultMode (Core Foundation)

描述:預設模式中幾乎包含了所有輸入源(NSConnection除外),一般情況下應使用此模式。

  • Connection模式

定義:NSConnectionReplyMode(Cocoa)

描述:處理NSConnection對象相關事件,系統內部使用,使用者基本不會使用。

  • Modal模式

定義:NSModalPanelRunLoopMode(Cocoa)

描述:處理modal panels事件。

  • Event tracking模式

定義:UITrackingRunLoopMode(iOS)
NSEventTrackingRunLoopMode(cocoa)

描述:在拖動loop或其他user interface tracking loops時處於此種模式下,在此模式下會限制輸入事件的處理。例如,當手指按住UITableView拖動時就會處於此模式。

  • Common模式

定義:NSRunLoopCommonModes (Cocoa) kCFRunLoopCommonModes (Core Foundation)

描述:這是一個偽模式,其為一組run loop mode的集合,將輸入源加入此模式意味著在Common Modes中包含的所有模式下都可以處理。在Cocoa應用程式中,預設情況下Common Modes包含default modes,modal modes,event Tracking modes.可使用CFRunLoopAddCommonMode方法向Common Modes中添加自訂modes。

註:iOS中僅NSDefaultRunLoopMode,UITrackingRunLoopMode,NSRunLoopCommonModes三種可用mode。

 

CADisplayLink

基本用法剛剛介紹過。

優勢:依託於裝置螢幕重新整理頻率觸發事件,所以其觸發時間上是最準確的。也是最適合做UI不斷重新整理的事件,過渡相對流暢,無卡頓感。

缺點:

  • 由於依託於螢幕重新整理頻率,若果CPU不堪重負而影響了螢幕重新整理,那麼我們的觸發事件也會受到相應影響。
  • selector觸發的時間間隔只能是duration的整倍數。
  • selector事件如果大於其觸發間隔就會造成掉幀現象。
  • CADisplayLink不能被繼承。

-------------------我是分割線---------------------

 

下面說說NSTImer, 一樣我們直接看標頭檔並用注釋說明

@interface NSTimer : NSObject+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
//執行個體化方法, 響應事件用的NSInvocation, 需要手動添加到RunLoop中才會生效
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;//執行個體化方法, 響應事件用的NSIvocation, 系統為自動幫你將timer添加到currentRunLoop中,defaultMode
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
//執行個體化方法, 響應事件用的PerformanceSelector, userInfo中可以用來傳參數,需要手動添加到RunLoop中
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
//執行個體化方法,響應事件用的PerformanceSelector, userInfo可以用來傳遞參數, 系統會自動幫你將timer添加到currentRunLoop中, defaultMode+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
//執行個體化方法, 以block的方式傳入要執行的內容, 需要手動添加到RunLoop中+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
//執行個體化方法, 以block的方式傳入要執行的內容,系統會自動幫你將timer添加到currentRunLoop中,defaultMode- (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));//跟上面類似, 只是多指定了一個開始時間
- (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)ti target:(id)t selector:(SEL)s userInfo:(nullable id)ui repeats:(BOOL)rep NS_DESIGNATED_INITIALIZER;//跟上面類似, 只是多指定了一個開始時間
- (void)fire;  //立即執行一次定時器方法, 注意不是立即開啟定時器@property (copy) NSDate *fireDate;  //當前事件的觸發事件, 一般用來做暫停和恢複@property (readonly) NSTimeInterval timeInterval;  //唯讀屬性, 擷取當前timer的觸發間隔@property NSTimeInterval tolerance NS_AVAILABLE(10_9, 7_0); //允許的誤差值- (void)invalidate; //立即銷毀timer@property (readonly, getter=isValid) BOOL valid; //唯讀屬性, 擷取當前timer是否有效@property (nullable, readonly, retain) id userInfo; //唯讀屬性, 初始化時傳入的使用者參數@end

NSTimer的內容相對多一些但也更加靈活, 有一個地方需要注意的是timer開頭的執行個體化方法需要手動添加到RunLoop, Schedule開頭的會由系統幫你添加到RunLoop

  • fireDate,設定當前timer的事件的觸發時間。通常我們使用這個屬性來做計時器的暫停與恢複
///暫停計時器self.timer.fireDate = [NSDate distantFuture];///恢複計時器self.timer.fireDate = [NSDate distantPast];
  • tolerance,允許誤差時間。我們知道NSTimer事件的觸發事件是不準確的,完全取決於當前runloop處理的時間。如果當前runloop在處理複雜運算,則timer執行時間將會被延遲,直到複雜運算結束後立即執行觸發事件,之後再按照初始設定的節奏去執行。當設定tolerance之後在允許範圍內的延遲可以觸發事件,超過的則不觸發。預設是時間間隔的1/10

 

網上很多人對fire方法的解釋其實並不正確。fire並不是立即啟用定時器,而是立即執行一次定時器方法

當加入到runloop中timer不需要啟用即可按照設定的時間觸發事件。fire只是相當於手動讓timer觸發一次事件

如果timer設定的repeat為NO,則fire之後timer立即銷毀

如果timer的repeat為YES,則到了之前設定的時間他依舊會按部就班的觸發事件

fire只是單獨觸發了一次事件,並不影響原timer的節奏

  • 關於invalid方法

我們知道NSTimer使用的時候如果不注意的話,是會造成記憶體流失的。原因是我們產生執行個體的時候,會對控制器retain一下。如果不對其進行管理則VC的永遠不會引用計數為零,進而造成記憶體流失。

所以,當我們不需要的timer的時候,請如下操作:

[self.timer invalid];self.timer = nil;

這樣Timer會對VC進行一次release。所以一定不要忘記調用invalid方法

順便提一句,如果產生timer執行個體的時候repeat為NO,那當觸發事件結束後,系統也會自動調用invalid一次

 

NSTimer的優勢:使用相對靈活,應用廣泛

劣勢:受runloop影響嚴重,同時易造成記憶體流失(調用invalid方法解決)

 

-------------------我是分割線---------------------

下面說說GCD計時器:dispatch_source_t

其實dispatch_source_t說為計時器不完全正確, 它實際上是GCD給我們用的一個來源物件

還是先直接上代碼:

#import "ViewController.h"@interface ViewController ()@property (nonatomic, assign)   NSInteger count;@property (nonatomic, strong)   dispatch_source_t tTimer;  //GCD計時器一定要設定為成員變數, 否則會立即釋放@end@implementation ViewController@synthesize tTimer;- (void)viewDidLoad {        [super viewDidLoad];        //建立GCD timer資源, 第一個參數為源類型, 第二個參數是資源要加入的隊列    self.tTimer =     dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());        //設定timer資訊, 第一個參數是我們的timer對象, 第二個是timer首次觸發延遲時間, 第三個參數是觸發時間間隔, 最後一個是是timer觸發允許的延遲值, 建議值是十分之一    dispatch_source_set_timer(self.tTimer,                              dispatch_walltime(NULL, 0 * NSEC_PER_SEC),                              0.32 * NSEC_PER_SEC,                              0);        //設定timer的觸發事件    dispatch_source_set_event_handler(self.tTimer, ^{                [self logCount];    });        //啟用timer對象    dispatch_resume(self.tTimer);}- (void)logCount {        self.count ++;    NSLog(@"Count = %ld", self.count);        if (self.count > 99) {                self.count = 0;        //暫停timer對象        dispatch_suspend(self.tTimer);                //銷毀timer, 注意暫停timer資源不能直接銷毀, 需要先resume再cancel, 否則會造成記憶體流失        //dispatch_source_cancel(self.tTimer);    }}

注釋已經很清楚了, 就不再逐條解釋(上面代碼會呦循環參考的問題, 大家自己改下)

需要注意的是, GCD timer資源必須設定為成員變數, 否則會在建立完畢後立即釋放

suspend掛起或暫停後的timer要先resume才能cancel, 掛起的timer直接cancel會造成記憶體流失

 

GCDTimer的優勢:不受當前runloopMode的影響。
劣勢:雖然說不受runloopMode的影響,但是其計時效應仍不是百分之百準確的。

另外,他的觸發事件也有可能被阻塞,當GCD內部管理的所有線程都被佔用時,其觸發事件將被延遲

 

好吧GCD我也沒用玩轉, 只說這些。 後面會找時間專門研究下

Objective-C三種定時器CADisplayLink / NSTimer / GCD的使用

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.