一犬性、NSTimer的類方法和實(shí)例初始化方法
這三個(gè)方法直接將timer添加到了當(dāng)前runloop default mode诡挂,而不需要我們自己操作碎浇,當(dāng)然這樣的代價(jià)是runloop只能是當(dāng)前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)建璃俗,不會(huì)自動(dò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
當(dāng)使用NSTimer
的scheduledTimerWithTimeInterval
方法時(shí)城豁。事實(shí)上此時(shí)Timer會(huì)被加入到當(dāng)前線程的Run Loop中苟穆,且模式是默認(rèn)的NSDefaultRunLoopMode
。而如果當(dāng)前線程就是主線程,也就是UI線程時(shí)雳旅,某些UI事件跟磨,比如UIScrollView
的拖動(dòng)操作,會(huì)將Run Loop切換成NSEventTrackingRunLoopMode模式
攒盈,在這個(gè)過(guò)程中抵拘,默認(rèn)的NSDefaultRunLoopMode
模式中注冊(cè)的事件是不會(huì)被執(zhí)行的。也就是說(shuō)型豁,此時(shí)使用scheduledTimerWithTimeInterva
l添加到Run Loop中的Timer就不會(huì)執(zhí)行僵蛛。
所以為了設(shè)置一個(gè)不被UI干擾的Timer,我們需要手動(dòng)創(chuàng)建一個(gè)Timer迎变,然后使用NSRunLoop
的addTimer:forMode:
方法來(lái)把Timer按照指定模式加入到Run Loop中充尉。這里使用的模式是:NSRunLoopCommonModes
,這個(gè)模式等效于
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加入到當(dāng)前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)引用導(dǎo)致一些對(duì)象無(wú)法銷毀泪电,一定的情況下會(huì)對(duì)我們?cè)斐捎绊懀貏e是我們要在dealloc
中釋放一些資源的時(shí)候纪铺。如:當(dāng)開(kāi)啟定時(shí)器以后,testTimerDeallo
方法一直執(zhí)行,即使dismiss
此控制器以后,也是一直在打印,而且dealloc方法不會(huì)執(zhí)行.循環(huán)引用造成了內(nèi)存泄露,控制器不會(huì)被釋放.
問(wèn)題分析
主要由于NSTimer對(duì)象和調(diào)用NSTimer的視圖控制器對(duì)象相互強(qiáng)引用了相速,其中NSTimer對(duì)視圖控制器的引用發(fā)生在最后一個(gè)參數(shù)reapets為YES的時(shí)候,因?yàn)樾枰貜?fù)執(zhí)行操作鲜锚,所以需要強(qiáng)引用調(diào)用對(duì)象突诬,那么解決辦法有兩點(diǎn):
- (1)讓視圖控制器對(duì)NSTimer的引用變成弱引用
- (2)讓NSTimer對(duì)視圖控制器的引用變成弱引用
分析一下兩種方法,第一種方法如果控制器對(duì)NSTimer的引用改為弱引用芜繁,則會(huì)出現(xiàn)NSTimer直接被回收旺隙,所以不可使,因此我們只能從第二種方法入手
解決辦法:
__weak typeof(self) weakSelf = self; 不能解決
使用一個(gè)NSTimer的Catagory骏令,然后重寫(xiě)初始化方法蔬捷,在實(shí)現(xiàn)中利用block,從而在調(diào)用的時(shí)候可以使用weakSelf在block執(zhí)行任務(wù)榔袋,從而解除NSTimer對(duì)target(視圖控制器)的強(qiáng)引用周拐。
@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
定義一個(gè)NSTimer
的類別,在類別中定義一個(gè)類方法凰兑。類方法有一個(gè)類型為塊的參數(shù)(定義的塊位于棧上妥粟,為了防止塊被釋放,需要調(diào)用copy
方法吏够,將塊移到堆上)勾给。使用這個(gè)類別的方式如下:
__weak ViewController *weakSelf = self;
_timer = [NSTimer jq_scheduledTimerWithTimeInterval:5.0
block:^{
__strong ViewController *strongSelf = weakSelf;
[strongSelf startCounting];
}
repeats:YES];
使用這種方案就可以防止NSTimer
對(duì)類的保留滩报,從而打破了循環(huán)引用的產(chǎn)生。__strong ViewController *strongSelf = weakSelf
主要是為了防止執(zhí)行塊的代碼時(shí)锦秒,類被釋放了露泊。在類的dealloc
方法中,記得調(diào)用[_timer invalidate]
旅择。
四惭笑、NSTimer和CADisplayLink的區(qū)別
CADisplayLink是一個(gè)能讓我們以和屏幕刷新率相同的頻率將內(nèi)容畫(huà)到屏幕上的定時(shí)器。我們?cè)趹?yīng)用中創(chuàng)建一個(gè)新的 CADisplayLink 對(duì)象生真,把它添加到一個(gè) runloop
中沉噩,并給它提供一個(gè)target
和selector
在屏幕刷新的時(shí)候調(diào)用。
一但 CADisplayLink 以特定的模式注冊(cè)到runloop之后柱蟀,每當(dāng)屏幕需要刷新的時(shí)候川蒙,runloop就會(huì)調(diào)用CADisplayLink綁定的target上的selector,這時(shí)target可以讀到 CADisplayLink 的每次調(diào)用的時(shí)間戳长已,用來(lái)準(zhǔn)備下一幀顯示需要的數(shù)據(jù)畜眨。例如一個(gè)視頻應(yīng)用使用時(shí)間戳來(lái)計(jì)算下一幀要顯示的視頻數(shù)據(jù)。在UI做動(dòng)畫(huà)的過(guò)程中术瓮,需要通過(guò)時(shí)間戳來(lái)計(jì)算UI對(duì)象在動(dòng)畫(huà)的下一幀要更新的大小等等康聂。
在添加進(jìn)runloop的時(shí)候我們應(yīng)該選用高一些的優(yōu)先級(jí),來(lái)保證動(dòng)畫(huà)的平滑胞四√裰可以設(shè)想一下,我們?cè)趧?dòng)畫(huà)的過(guò)程中辜伟,runloop被添加進(jìn)來(lái)了一個(gè)高優(yōu)先級(jí)的任務(wù)氓侧,那么,下一次的調(diào)用就會(huì)被暫停轉(zhuǎn)而先去執(zhí)行高優(yōu)先級(jí)的任務(wù)导狡,然后在接著執(zhí)行CADisplayLink的調(diào)用约巷,從而造成動(dòng)畫(huà)過(guò)程的卡頓,使動(dòng)畫(huà)不流暢旱捧。另外 CADisplayLink 不能被繼承载庭。
1、創(chuàng)建方法
displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(handleDisplayLink:)];
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
2廊佩、停止方法
[displayLink invalidate];
displayLink = nil;
當(dāng)把CADisplayLink對(duì)象add到runloop中后,selector就能被周期性調(diào)用靖榕,類似于重復(fù)的NSTimer被啟動(dòng)了标锄;執(zhí)行invalidate操作時(shí),CADisplayLink對(duì)象就會(huì)從runloop中移除茁计,selector調(diào)用也隨即停止料皇,類似于NSTimer的invalidate
方法谓松。
3、CADisplayLink 與 NSTimer有什么不同?
- (1)原理不同
CADisplayLink是一個(gè)能讓我們以和屏幕刷新率同步的頻率將特定的內(nèi)容畫(huà)到屏幕上的定時(shí)器類践剂。 CADisplayLink以特定模式注冊(cè)到runloop后鬼譬, 每當(dāng)屏幕顯示內(nèi)容刷新結(jié)束的時(shí)候,runloop就會(huì)向 CADisplayLink指定的target發(fā)送一次指定的selector消息逊脯, CADisplayLink類對(duì)應(yīng)的selector就會(huì)被調(diào)用一次优质。
NSTimer以指定的模式注冊(cè)到runloop后,每當(dāng)設(shè)定的周期時(shí)間到達(dá)后军洼,runloop會(huì)向指定的target發(fā)送一次指定的selector消息巩螃。
- (2)周期設(shè)置方式不同
iOS設(shè)備的屏幕刷新頻率(FPS)是60Hz,因此CADisplayLink的selector 默認(rèn)調(diào)用周期是每秒60次匕争,這個(gè)周期可以通過(guò)frameInterval屬性設(shè)置避乏, CADisplayLink的selector每秒調(diào)用次數(shù)=60/ frameInterval。比如當(dāng) frameInterval設(shè)為2甘桑,每秒調(diào)用就變成30次拍皮。因此, CADisplayLink 周期的設(shè)置方式略顯不便跑杭。
NSTimer的selector調(diào)用周期可以在初始化時(shí)直接設(shè)定铆帽,相對(duì)就靈活的多。
- (3)精確度不同
iOS設(shè)備的屏幕刷新頻率是固定的艘蹋,CADisplayLink在正常情況下會(huì)在每次刷新結(jié)束都被調(diào)用锄贼,精確度相當(dāng)高。
NSTimer的精確度就顯得低了點(diǎn)女阀,比如NSTimer的觸發(fā)時(shí)間到的時(shí)候宅荤,runloop如果在阻塞狀態(tài),觸發(fā)時(shí)間就會(huì)推遲到下一個(gè)runloop周期浸策。并且 NSTimer新增了tolerance屬性冯键,讓用戶可以設(shè)置可以容忍的觸發(fā)的時(shí)間的延遲范圍。
- (4)使用場(chǎng)景
CADisplayLink使用場(chǎng)合相對(duì)專一庸汗,適合做UI的不停重繪惫确,比如自定義動(dòng)畫(huà)引擎或者視頻播放的渲染。
NSTimer的使用范圍要廣泛的多蚯舱,各種需要單次或者循環(huán)定時(shí)處理的任務(wù)都可以使用改化。