iOS timer計時器
timer,計時器,就是用來計時的,可以將它和要處理的動作繫結起來,讓這個動作在某段時間之後執行,或者週期性地執行。
一、timer的工作原理
timer的工作和run loop密不可分,由於平常我們使用Application Kit和UIKit來新建的app,在app的主執行緒啟動的時候就自動啟動了一個runloop,因此在主執行緒中使用timer感覺不到runloop的存在。如果要在分執行緒中使用timer,就必須要了解timer和runloop的協同工作原理,自己去新建runloop,讓它和timer一起配合工作。
簡短概括runloop和timer的工作關係就是,timer只負責計時,timer所繫結的動作,是由runloop來執行的。下面就來詳細地講講這種關係。
我們知道,runloop執行的時候需要指定執行模式(run loop mode),runloop的執行模式規定了它要監聽的事件源,以及事件發生時它要通知的物件。因此可以給runloop定義多種執行模式,runloop執行時,可以同時啟用多個執行模式。
一個timer同一時間只可以註冊到一個runloop中,但是可以新增到這個runloop的多個執行模式中。(下圖表示了這個關係)
初始化一個timer,就是給timer繫結一個要執行的動作;登記一個時間段,這個時間段用來告訴runloop,該動作需要在該時間段過去之後執行,或者每隔這個時間段執行一次。
在timer與runloop的協同工作中,timer只負責計時,而runloop負責監視其所啟用的執行模式中新增的timer是否已經達到了其初始化時登記的時間,一旦發現某個timer已經到達了這個時間,就會去執行該timer所繫結的動作(這就是所謂的fire the timer)。
由此可以見得,timer所登記的這個時間段並不是動作的絕對執行時間。timer繫結的動作具體在什麼時候執行,要看runloop是否在執行,且是否啟用了timer所加入的執行模式,還要看runloop正在處理的東西多不多,能不能及時發現timer已經計時完畢。
二、timer的具體使用
1、建立timer
(1)使用當前的runloop來建立一個timer
方法:
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo :(nullable id)userInfo repeats:(BOOL)yesOrNo;
引數TimeInterval是timer計時的週期長度,每隔時長TimeInterval,計時器就會清零一次。
這兩個方法,完成一系列動作:
- 建立timer;
- 將它加入到當前runloop的預設執行模式中;
- 讓timer開始計時。
對於這種方式新增的timer,runloop就會等它的計時時長第一次達到TimeInterval時,才會執行它繫結的動作。
(2)自己新建一個timer,之後將它註冊到某個runloop中
方法:
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
這兩個方法可以返回一個timer。
之後要用runloop物件的方法將timer註冊到這個runloop物件的某個執行模式中:
-(void)addTimer:(nonnull NSTimer *) forMode:(nonnull NSString *);
這樣新增到runloop的timer會立即開始計時。
(3)初始化一個timer,並指明它繫結的動作被執行的時間點
方法:
- (instanceType)initWithFireDate:(nonnull NSDate *) interval:(NSTimeInterval) target:(nonnull id) selector:(nonnull SEL) userInfo:(nullable id) repeats:(BOOL);
這裡的fireDate是個時間點,是相對於系統時間的,只要到達了這個時間點,runloop就會去執行timer繫結的動作。所以如果timer是一次性的,都不需要計時,interval這個引數就沒有作用了。如果是重複的timer,才需要計時,引數interval才有用。
可以用setFireDate方法來給timer設定動作的執行時間。
2、停止timer
timer需要在runloop的管理下才會有效,停止timer,只要把它從它所在的runloop中清理掉就可以實現。(或者讓runloop停止執行)。
(1)對於一次性的timer
一次性的timer都不用考慮這個問題,因為runloop執行完timer繫結的動作就會自動把這個timer從它所在的執行模式中清理掉。
(2)對於週期性的timer
方法:
-(void)invalidate;
對於週期性的timer才需要考慮這個問題。
要停止一個週期性的timer,要獲取這個timer物件的引用,在需要清理timer的時候,主動呼叫該timer物件的invalidate方法即可。
當然,也可以對一次性的timer執行這個方法,在timer的動作被runloop執行之前呼叫invalidate方法,timer的動作就永遠不會被執行了。