NSTimer的常用API
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
Initializes a timer object with the specified object and selector.
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL)yesOrNo;
Creates a timer and schedules it on the current run loop in the default mode.
根據官方文檔方法一和方法二的區別在于:使用方法一創建的Timer不會添加到NSRunLoop需要手動添加;使用方法二會自動添加到主線程的RunLoop中。
- (void)fire;
Causes the timer's message to be sent to its target.
- (void)invalidate;
Stops the timer from ever firing again and requests its removal from its run loop.
fire
方法可以立即觸發Timer對象的target方法;invalidate
會停止Timer并將其從runloop中移除
NSRunLoop & NSTimer
當使用NSTimer
的scheduledTimerWithTimeInterval
方法時,NSTimer
的實例會被加入到當前線程的RunLoop
中,模式為默認模式NSDefaultRunLoopMode
。
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(@"主線程 %@", [NSThread currentThread]);
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(doSomething) userInfo:nil repeats:YES];
//使用NSRunLoopCommonModes模式,把timer加入到當前Run Loop中。
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}
- (void)doSomething
{
NSLog(@"Timer %@", [NSThread currentThread]);
}
控制臺輸出結果:上下打印出來的線程都是主線程。
如果當你線程是主線程也就是UI線程時,某些UI事件(UIScrollView
的滑動操作),會將RunLoop
切換到NSEventTrackingRunLoopMode
模式。
在這個過程中,默認的NSDefaultRunLoopMode
模式中注冊的事件是不會被執行的。也就是說,此時使用scheduledTimerWithTimeInterval
添加到RunLoop中的Timer就不會執行。這是一個在開發中經常碰到的場景,如何解決這個問題?我們可以在創建完一個Timer
后使用NSRunLoop
的addTimer:forMode:
方法,使用NSRunLoopCommonModes
。這是一個占位用的Mode,可以在不同情況下扮演不同的角色——NSDefaultRunLoopMode & UITrackingRunLoopMode
。
NSTimer循環引用問題
NSTimer為什么會造成循環引用?
在開發中我們經常將Timer作為控制器的屬性來使用,這樣一來控制器對Timer進行了強引用。在target-action這個過程中,Timer又對self做了強引用,這就是導致循環引用的原因了。在網上有非常多的解決方案,我總結了一下有下面幾種。
-
方法一:在
viewWillDisappear
中執行下面的操作。- (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; [_timer invalidate]; _timer = nil; } -- 蘋果文檔中關于target強引用的解釋 + (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats Parameters ti The number of seconds between firings of the timer. If ti is less than or equal to 0.0, this method chooses the nonnegative value of 0.1 milliseconds instead. target The object to which to send the message specified by aSelector when the timer fires. The timer maintains a strong reference to this object until it (the timer) is invalidated.
The timer maintains a strong reference to this object until it (the timer) is invalidated.
蘋果文檔說invalidate以后timer就不再保有對target的強引用了。所以解決循環引用的關鍵在與invalidate
方法有沒有執行。下面_timer = nil
這句話的意義是,invalidate
方法執行以后Timer就不能復用了,為了防止在其之后其他地方再次使用Timer,在這里即使將其置為nil。
方法二:引入一個代理對象,讓其弱引用self,參考YYWeakProxy
沒引入代理(Proxy)之前是:self -> timer -> self
這樣的循環引用。在引入代理(Proxy)之后是:self -> timer -> proxy ··> self
。這樣一來就打破了之前的循環引用。方法三:使用
YYTimer
和MSWeakTimer
等GCD實現的Timer。
我決定把這一部分內容拿出來單獨寫一篇對比GCD、CADisplayLink等計時器的總結,除了NSTimer還有兩種定時器:CADisplayLink & Dispatch Source Timer歡迎點贊和關注。