iOS NSTimer的詳細(xì)用法

一犬性、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)使用NSTimerscheduledTimerWithTimeInterval方法時(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í)使用scheduledTimerWithTimeInterval添加到Run Loop中的Timer就不會(huì)執(zhí)行僵蛛。
所以為了設(shè)置一個(gè)不被UI干擾的Timer,我們需要手動(dòng)創(chuàng)建一個(gè)Timer迎变,然后使用NSRunLoopaddTimer:forMode:方法來(lái)把Timer按照指定模式加入到Run Loop中充尉。這里使用的模式是:NSRunLoopCommonModes,這個(gè)模式等效于
NSDefaultRunLoopModeNSEventTrackingRunLoopMode的結(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è)targetselector 在屏幕刷新的時(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ù)都可以使用改化。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市枉昏,隨后出現(xiàn)的幾起案子陈肛,更是在濱河造成了極大的恐慌,老刑警劉巖兄裂,帶你破解...
    沈念sama閱讀 218,858評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件句旱,死亡現(xiàn)場(chǎng)離奇詭異阳藻,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)谈撒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門腥泥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人啃匿,你說(shuō)我怎么就攤上這事蛔外。” “怎么了立宜?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,282評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵冒萄,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我橙数,道長(zhǎng)尊流,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,842評(píng)論 1 295
  • 正文 為了忘掉前任灯帮,我火速辦了婚禮崖技,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘钟哥。我一直安慰自己迎献,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布腻贰。 她就那樣靜靜地躺著吁恍,像睡著了一般。 火紅的嫁衣襯著肌膚如雪播演。 梳的紋絲不亂的頭發(fā)上冀瓦,一...
    開(kāi)封第一講書(shū)人閱讀 51,679評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音写烤,去河邊找鬼翼闽。 笑死,一個(gè)胖子當(dāng)著我的面吹牛洲炊,可吹牛的內(nèi)容都是我干的感局。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼暂衡,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼询微!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起狂巢,我...
    開(kāi)封第一講書(shū)人閱讀 39,311評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤拓提,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后隧膘,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體代态,經(jīng)...
    沈念sama閱讀 45,767評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年疹吃,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蹦疑。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,090評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡萨驶,死狀恐怖歉摧,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情腔呜,我是刑警寧澤叁温,帶...
    沈念sama閱讀 35,785評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站核畴,受9級(jí)特大地震影響膝但,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜谤草,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評(píng)論 3 331
  • 文/蒙蒙 一跟束、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧丑孩,春花似錦冀宴、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,988評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至仗岖,卻和暖如春逃延,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背箩帚。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,101評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工真友, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人紧帕。 一個(gè)月前我還...
    沈念sama閱讀 48,298評(píng)論 3 372
  • 正文 我出身青樓盔然,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親是嗜。 傳聞我的和親對(duì)象是個(gè)殘疾皇子愈案,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容