一、NSTimer的類方法和實例初始化方法
這三個方法直接將timer添加到了當前runloop default mode,而不需要我們自己操作,當然這樣的代價是runloop只能是當前runloop,模式是default mode:
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo;
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block;
下面五種創(chuàng)建,不會自動添加到runloop,還需調(diào)用addTimer:forMode:
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block;
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)yesOrNo;
- (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)ti target:(id)t selector:(SEL)s userInfo:(id)ui repeats:(BOOL)rep;
- (instancetype)initWithFireDate:(NSDate *)date interval:(NSTimeInterval)interval repeats:(BOOL)repeats block:(void (^)(NSTimer *timer))block;
二、NSRunLoopCommonModes和Timer
當使用NSTimer的scheduledTimerWithTimeInterval方法時。事實上此時Timer會被加入到當前線程的Run Loop中,且模式是默認的NSDefaultRunLoopMode。而如果當前線程就是主線程,也就是UI線程時,某些UI事件,比如UIScrollView的拖動操作,會將Run Loop切換成NSEventTrackingRunLoopMode模式,在這個過程中,默認的NSDefaultRunLoopMode模式中注冊的事件是不會被執(zhí)行的。也就是說,此時使用scheduledTimerWithTimeInterval添加到Run Loop中的Timer就不會執(zhí)行。
所以為了設(shè)置一個不被UI干擾的Timer,我們需要手動創(chuàng)建一個Timer,然后使用NSRunLoop的addTimer:forMode:方法來把Timer按照指定模式加入到Run Loop中。這里使用的模式是:NSRunLoopCommonModes,這個模式等效于
NSDefaultRunLoopMode和NSEventTrackingRunLoopMode的結(jié)合。(參考[Apple文檔]
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(@"主線程 %@", [NSThread currentThread]);
//創(chuàng)建Timer
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(timer_callback) userInfo:nil repeats:YES];
//使用NSRunLoopCommonModes模式,把timer加入到當前Run Loop中。
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}
//timer的回調(diào)方法
- (void)timer_callback
{
NSLog(@"Timer %@", [NSThread currentThread]);
}
輸出:
主線程 <NSThread: 0x71501e0>{name = (null), num = 1}
Timer <NSThread: 0x71501e0>{name = (null), num = 1}
Timer <NSThread: 0x71501e0>{name = (null), num = 1}
Timer <NSThread: 0x71501e0>{name = (null), num = 1}
三、NSTimer中的循環(huán)引用
循環(huán)引用導致一些對象無法銷毀,一定的情況下會對我們造成影響,特別是我們要在dealloc中釋放一些資源的時候。如:當開啟定時器以后,testTimerDeallo方法一直執(zhí)行,即使dismiss此控制器以后,也是一直在打印,而且dealloc方法不會執(zhí)行.循環(huán)引用造成了內(nèi)存泄露,控制器不會被釋放.
問題分析
主要由于NSTimer對象和調(diào)用NSTimer的視圖控制器對象相互強引用了,其中NSTimer對視圖控制器的引用發(fā)生在最后一個參數(shù)reapets為YES的時候,因為需要重復執(zhí)行操作,所以需要強引用調(diào)用對象,那么解決辦法有兩點:
- (1)讓視圖控制器對NSTimer的引用變成弱引用
- (2)讓NSTimer對視圖控制器的引用變成弱引用
分析一下兩種方法,第一種方法如果控制器對NSTimer的引用改為弱引用,則會出現(xiàn)NSTimer直接被回收,所以不可使,因此我們只能從第二種方法入手
解決辦法:
__weak typeof(self) weakSelf = self; 不能解決
使用一個NSTimer的Catagory,然后重寫初始化方法,在實現(xiàn)中利用block,從而在調(diào)用的時候可以使用weakSelf在block執(zhí)行任務(wù),從而解除NSTimer對target(視圖控制器)的強引用。
@interface NSTimer (JQUsingBlock)
+ (NSTimer *)jq_scheduledTimerWithTimeInterval:(NSTimeInterval)ti
block:(void(^)())block
repeats:(BOOL)repeats;
@end
@implementation NSTimer (JQUsingBlock)
+ (NSTimer *)jq_scheduledTimerWithTimeInterval:(NSTimeInterval)ti
block:(void(^)())block
repeats:(BOOL)repeats{
return [self scheduledTimerWithTimeInterval:ti
target:self
selector:@selector(jq_blockInvoke:)
userInfo:[block copy]
repeats:repeats];
}
+ (void)jq_blockInvoke:(NSTimer *)timer{
void(^block)() = timer.userInfo;
if (block) {
block();
}
}
@end
定義一個NSTimer的類別,在類別中定義一個類方法。類方法有一個類型為塊的參數(shù)(定義的塊位于棧上,為了防止塊被釋放,需要調(diào)用copy方法,將塊移到堆上)。使用這個類別的方式如下:
__weak ViewController *weakSelf = self;
_timer = [NSTimer jq_scheduledTimerWithTimeInterval:5.0
block:^{
__strong ViewController *strongSelf = weakSelf;
[strongSelf startCounting];
}
repeats:YES];
使用這種方案就可以防止NSTimer對類的保留,從而打破了循環(huán)引用的產(chǎn)生。__strong ViewController *strongSelf = weakSelf主要是為了防止執(zhí)行塊的代碼時,類被釋放了。在類的dealloc方法中,記得調(diào)用[_timer invalidate]。
四、NSTimer和CADisplayLink的區(qū)別
CADisplayLink是一個能讓我們以和屏幕刷新率相同的頻率將內(nèi)容畫到屏幕上的定時器。我們在應(yīng)用中創(chuàng)建一個新的 CADisplayLink 對象,把它添加到一個 runloop中,并給它提供一個target 和selector 在屏幕刷新的時候調(diào)用。
一但 CADisplayLink 以特定的模式注冊到runloop之后,每當屏幕需要刷新的時候,runloop就會調(diào)用CADisplayLink綁定的target上的selector,這時target可以讀到 CADisplayLink 的每次調(diào)用的時間戳,用來準備下一幀顯示需要的數(shù)據(jù)。例如一個視頻應(yīng)用使用時間戳來計算下一幀要顯示的視頻數(shù)據(jù)。在UI做動畫的過程中,需要通過時間戳來計算UI對象在動畫的下一幀要更新的大小等等。
在添加進runloop的時候我們應(yīng)該選用高一些的優(yōu)先級,來保證動畫的平滑??梢栽O(shè)想一下,我們在動畫的過程中,runloop被添加進來了一個高優(yōu)先級的任務(wù),那么,下一次的調(diào)用就會被暫停轉(zhuǎn)而先去執(zhí)行高優(yōu)先級的任務(wù),然后在接著執(zhí)行CADisplayLink的調(diào)用,從而造成動畫過程的卡頓,使動畫不流暢。另外 CADisplayLink 不能被繼承。
1、創(chuàng)建方法
displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(handleDisplayLink:)];
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
2、停止方法
[displayLink invalidate];
displayLink = nil;
當把CADisplayLink對象add到runloop中后,selector就能被周期性調(diào)用,類似于重復的NSTimer被啟動了;執(zhí)行invalidate操作時,CADisplayLink對象就會從runloop中移除,selector調(diào)用也隨即停止,類似于NSTimer的invalidate方法。
3、CADisplayLink 與 NSTimer有什么不同?
- (1)原理不同
CADisplayLink是一個能讓我們以和屏幕刷新率同步的頻率將特定的內(nèi)容畫到屏幕上的定時器類。 CADisplayLink以特定模式注冊到runloop后, 每當屏幕顯示內(nèi)容刷新結(jié)束的時候,runloop就會向 CADisplayLink指定的target發(fā)送一次指定的selector消息, CADisplayLink類對應(yīng)的selector就會被調(diào)用一次。
NSTimer以指定的模式注冊到runloop后,每當設(shè)定的周期時間到達后,runloop會向指定的target發(fā)送一次指定的selector消息。
- (2)周期設(shè)置方式不同
iOS設(shè)備的屏幕刷新頻率(FPS)是60Hz,因此CADisplayLink的selector 默認調(diào)用周期是每秒60次,這個周期可以通過frameInterval屬性設(shè)置, CADisplayLink的selector每秒調(diào)用次數(shù)=60/ frameInterval。比如當 frameInterval設(shè)為2,每秒調(diào)用就變成30次。因此, CADisplayLink 周期的設(shè)置方式略顯不便。
NSTimer的selector調(diào)用周期可以在初始化時直接設(shè)定,相對就靈活的多。
- (3)精確度不同
iOS設(shè)備的屏幕刷新頻率是固定的,CADisplayLink在正常情況下會在每次刷新結(jié)束都被調(diào)用,精確度相當高。
NSTimer的精確度就顯得低了點,比如NSTimer的觸發(fā)時間到的時候,runloop如果在阻塞狀態(tài),觸發(fā)時間就會推遲到下一個runloop周期。并且 NSTimer新增了tolerance屬性,讓用戶可以設(shè)置可以容忍的觸發(fā)的時間的延遲范圍。
- (4)使用場景
CADisplayLink使用場合相對專一,適合做UI的不停重繪,比如自定義動畫引擎或者視頻播放的渲染。
NSTimer的使用范圍要廣泛的多,各種需要單次或者循環(huán)定時處理的任務(wù)都可以使用。