創建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;
}
參考鏈接: