NSTimer

創建NSTimer

創建NSTimer的常用方法是:

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats

創建NSTimer的不常用方法是

+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats

- (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)ti target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats

三者之間的區別是:


scheduledTimerWithTimeInterval相比它的小伙伴們不僅僅是創建了NSTimer對象, 還把該對象加入到了當前的runloop中,runloop的模式為默認模式(NSDefaultRunLoopMode)!

NSTimer只有被加入到runloop, 才會生效, 即NSTimer才會被真正執行

所以說, 如果你想使用timerWithTimeInterval或initWithFireDate的話, 需要使用NSRunloop的以下方法將NSTimer加入到runloop中

- (void)addTimer:(NSTimer *)aTimer forMode:(NSString *)mode


也就是說:

NSTimer ?*timer=[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(changeTimeAtTimedisplay) userInfo:nil repeats:YES];

NSTimer ?*timer=[NSTimer timerWithTimeInterval:10 target:self selector:@selector(changeTimeAtTimedisplay) userInfo:nil repeats:YES];

NSRunLoop *runloop=[NSRunLoop ?currentRunLoop];

[runloop addTimer:timer ?forMode:NSDefaultRunLoopMode];

是同效的。(initWithFireDate 方法創建的timer同第二種的使用方式一樣)


關于 - (void)fire; 方法

其實他并不是真的啟動一個定時器,從之前的初始化方法中我們也可以看到,建立的時候,在適當的時間,定時器就會自動啟動,也即NSTimer是不準時的。那么,fire方法的作用是什么呢,官方解釋是:

You can use this method to fire a repeating timer without interrupting its regular firing schedule. If the timer is non-repeating, it is automatically invalidated after firing, even if its scheduled fire date has not arrived.

大概意思就是fire并不是啟動一個定時器,只是提前觸發而已。我們來用一個按鈕操作fire方法試驗一下:

在一個Controller中創建一個NSTimer屬性=>

? ? //創建一個定時器,

self.timer= [NSTimer ?scheduledTimerWithTimeInterval:10.0 ?target:self ?selector:@selector(timerAction) ?userInfo:nil ?repeats:YES];

//當然這個定時器會自動啟動,只不多過了十秒之后,才觸發

timerAction事件里面:

- (void)timerAction {

static ?int ?a =0;

NSLog(@"定時開始了---- %d",a++);

}

然后單擊一個按鈕的時候:

- (IBAction)startTime:(id)sender {

//只是簡單地調用一下這個方法,看到底功能是什么

[_timer ?fire];

NSLog(@"定時fire了");

}

打印結果是:

2017-09-27 12:06:15.020 runloop--02[16073:598629]定時開始了---- 0

2017-09-27 12:06:15.020 runloop--02[16073:598629]定時fire

2017-09-27 12:06:16.543 runloop--02[16073:598629]定時開始了---- 1

2017-09-27 12:06:26.542 runloop--02[16073:598629]定時開始了---- 2

2017-09-27 12:06:36.543 runloop--02[16073:598629]定時開始了---- 3

2017-09-27 12:06:46.542 runloop--02[16073:598629]定時開始了---- 4

結果解釋:

定時器開始執行一次方法(即10秒之后)timerAction 之后,第一次執行a為0;下一次10秒后,a將為1,但是當我們點擊按鈕,執行了一次fire之后,定時器提前執行了一次timerAction方法,立即將a加1了;而后再一個10秒之后,定時器又按照設定將a加了1,變成2。。。。。

即 ?fire ?方法只是提前出發定時器的執行,但不影響定時器的設定時間。

當我們,改為NO時,即不讓它循環觸發時,我們此時再單擊開始按鈕。會猛然發現,a+1了,但當我們再點擊開始按鈕時,會發現a不再加1。原因是:我們的定時器,被設置成只觸發一次,再fire的時候,觸發一次,該定時器,就被自動銷毀了,以后再fire也不會觸發了。

銷毀NSTimer

invalidate 方法

Stops the receiver from ever firing again and requests its removal from its run loop

This method is the only way to remove a timer from an NSRunLoop object

將timer從它的runloop鐘移除,所以:

如果想要銷毀NSTimer, 那么確定, 一定以及肯定要調用invalidate方法

repeat為YES的timer需要顯示得進行invalidate銷毀。

invalidate與=nil

不能簡單得把_timer置為nil來銷毀timer,原因:

? ?1、首先, 是創建NSTimer, 加入到runloop后, 除了ViewController之外iOS系統也會強引用NSTimer對象


2、當調用invalidate方法時, 移除runloop后, iOS系統會解除對NSTimer對象的強引用, 當ViewController銷毀時, ViewController和NSTimer就都可以釋放了


3、當將NSTimer對象置nil時, 雖然解除了ViewController對NSTimer的強引用, 但是iOS系統仍然對NSTimer和ViewController存在著強引用關系

這里所說的iOS系統對ViewController的強引用, 不是指為了實現View顯示的強引用, 而是指iOS為了實現NSTimer而對ViewController進行的額外強引用

NSLog(@"Retain count is %ld", CFGetRetainCount((__bridge CFTypeRef)self));

_timer = [NSTimer scheduledTimerWithTimeInterval:TimerInterval

target:self? selector:@selector(timerSelector:)? userInfo:nil? repeats:TimerRepeats];

NSLog(@"Retain count is %ld", CFGetRetainCount((__bridge CFTypeRef)self));

[_timer invalidate];

NSLog(@"Retain count is %ld", CFGetRetainCount((__bridge CFTypeRef)self));

各位請注意, 創建NSTimer和銷毀NSTimer后, ViewController(就是這里的self)引用計數的變化

Retain count is 7

Retain count is 8

Retain count is 7


綜上所述, 銷毀NSTimer的正確姿勢應該是

[_timer invalidate]; // 真正銷毀NSTimer對象的地方

如果將上述銷毀NSTimer的代碼放到ViewController的dealloc方法里, 你會發現dealloc還是永遠不會走的 (此外還有另一種說法:

其實就是timer對viewController進行了強調應用,原因是因為,如果要讓timer運行的時候執行viewController下面的timerSelector:,timer需要知道target,并且保存這個target,以便于在以后執行這個代碼 [target performSelector:], 這里的target就是指viewController。所以,timer和viewController是相互強調引用的。 但是這樣看起來,就形成了retain cycle。為了解除retain cycle,我覺得,在-(void)invalidate;這個方法下,timer之前保存的target被設置為nil,強制斷開了引用環。這點和設置timer = nil是差不多的。 但是invalidate還做了另外一個動作,就是解除了runloop對timer的強調引用,使得timer成功停止。

) 所以 timer只要沒有銷毀,就一直保持著對target也就是vc的強引用,dealloc方法就不會走。

所以我將上述代碼放到ViewController的其他生命周期方法里, 例如ViewWillDisappear中

綜上所述, 銷毀NSTimer的正確姿勢應該是?

- (void)viewWillDisappear:(BOOL)animated {

[super viewWillDisappear:animated];

[_timer invalidate];

_timer = nil;

}


參考鏈接:

NSTimer 使用

iOS開發 之 不要告訴我你會用NSTimer!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容