iOS開發(fā) 之 不要告訴我你會(huì)用NSTimer!

目錄

引言

為什么想起來(lái)要討論NSTimer? 源自這兩天工作中的遇到的一個(gè)問(wèn)題:

專職iOS開發(fā)也一年有余了, 但是在跟蹤自己寫的ViewController釋放時(shí), 發(fā)現(xiàn)ViewController的dealloc方法死活沒(méi)走到, 心里咯噔一下, 不會(huì)又內(nèi)存泄漏了? ??

一切都是很完美的節(jié)奏啊: ViewController初始化時(shí), 創(chuàng)建Sub UIView, 創(chuàng)建數(shù)據(jù)結(jié)構(gòu), 創(chuàng)建NSTimer

然后在dealloc里, 釋放NSTimer, 然后NSTimer = nil, 哪里會(huì)有什么問(wèn)題?

不對(duì)! 移除NSTimer后dealloc就愉快滴走了起來(lái), 難道NSTimer的用法一直都不對(duì)?

結(jié)果發(fā)現(xiàn), 真的是不對(duì)! ??

好吧, 故事就講到這里, 馬上開始今天的NSTimer之旅吧

創(chuàng)建NSTimer

創(chuàng)建NSTimer的常用方法是

+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)target selector:(SEL)aSelector userInfo:(id)userInfo repeats:(BOOL)repeats

創(chuàng)建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

這幾種方法除了創(chuàng)建方式不同(參數(shù)), 方法類型不同(類方法, 對(duì)象方法), 還有其他不同么?

當(dāng)然有, 不然Apple沒(méi)必要這么作, 開這么多接口, 作者(好像就是我??)也沒(méi)必要這么作, 寫這么長(zhǎng)軟文

他們的區(qū)別很簡(jiǎn)單:

how-to-user-nstimer-01.png

scheduledTimerWithTimeInterval相比它的小伙伴們不僅僅是創(chuàng)建了NSTimer對(duì)象, 還把該對(duì)象加入到了當(dāng)前的runloop中!

等等, runloop是什么鬼? 在此不解釋runloop的原理, 但是使用NSTimer你必須要知道的是

NSTimer只有被加入到runloop, 才會(huì)生效, 即NSTimer才會(huì)被真正執(zhí)行

所以說(shuō), 如果你想使用timerWithTimeInterval或initWithFireDate的話, 需要使用NSRunloop的以下方法將NSTimer加入到runloop中

- (void)addTimer:(NSTimer *)aTimer forMode:(NSString *)mode
how-to-user-nstimer-02.png

銷毀NSTimer

知道了如何創(chuàng)建NSTimer后, 我們來(lái)說(shuō)說(shuō)如何銷毀NSTimer, 銷毀NSTimer不是很簡(jiǎn)單的么?

用invalidate方法啊, 好像還有個(gè)fire方法, 實(shí)在不行直接將NSTimer對(duì)象置nil, 這樣iOS系統(tǒng)就幫我們銷毀了

是的, 曾經(jīng)的我也是如此混沌滴這么認(rèn)為著, 那么這幾種方法是不是真的都可以銷毀NSTimer呢?

invalidate與fire

我們來(lái)看看Apple Documentation對(duì)這兩個(gè)方法的權(quán)威解釋吧

  • 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

  • fire

Causes the receiver’s message to be sent to its target

If the timer is non-repeating, it is automatically invalidated after firing

理解了上面的幾句話, 你就完完全全理解了invalidate和fire的區(qū)別了, 下面的示意圖更直觀

how-to-user-nstimer-03.png

總之, 如果想要銷毀NSTimer, 那么確定, 一定以及肯定要調(diào)用invalidate方法

invalidate與=nil

就像銷毀其他強(qiáng)應(yīng)用(不用我解釋強(qiáng)引用了吧, 否則你還是別浪費(fèi)時(shí)間往下看了)對(duì)象一樣, 我們是否可以將NSTimer置nil, 而讓iOS系統(tǒng)幫我們銷毀NSTimer呢?

答案是: 當(dāng)然不可以! (詳見上述的結(jié)論, "總之, 巴拉巴拉...")

為什么不可以? 其他強(qiáng)引用對(duì)象都可以, 為什么NSTimer對(duì)象不可以? 你說(shuō)不可以就可以? 憑什么信你?

好吧, 我們來(lái)看下使用NSTimer時(shí), ARC是怎么工作的

  • 首先, 是創(chuàng)建NSTimer, 加入到runloop后, 除了ViewController之外iOS系統(tǒng)也會(huì)強(qiáng)引用NSTimer對(duì)象
how-to-user-nstimer-04.png
  • 當(dāng)調(diào)用invalidate方法時(shí), 移除runloop后, iOS系統(tǒng)會(huì)解除對(duì)NSTimer對(duì)象的強(qiáng)引用, 當(dāng)ViewController銷毀時(shí), ViewController和NSTimer就都可以釋放了
how-to-user-nstimer-05.png
  • 當(dāng)將NSTimer對(duì)象置nil時(shí), 雖然解除了ViewController對(duì)NSTimer的強(qiáng)引用, 但是iOS系統(tǒng)仍然對(duì)NSTimer和ViewController存在著強(qiáng)引用關(guān)系

神馬? iOS系統(tǒng)對(duì)NSTimer有強(qiáng)引用很好理解, 對(duì)ViewController本來(lái)不就是強(qiáng)引用么?

這里所說(shuō)的iOS系統(tǒng)對(duì)ViewController的強(qiáng)引用, 不是指為了實(shí)現(xiàn)View顯示的強(qiáng)引用, 而是指iOS為了實(shí)現(xiàn)NSTimer而對(duì)ViewController進(jìn)行的額外強(qiáng)引用 (我去, 能不能不要這么拗口, 欺負(fù)我語(yǔ)文不好)

不瞞您說(shuō), 我的語(yǔ)文其實(shí)也是一般般, 所以show me the code

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));

各位請(qǐng)注意, 創(chuàng)建NSTimer和銷毀NSTimer后, ViewController(就是這里的self)引用計(jì)數(shù)的變化

2016-07-06 13:53:21.950 NSTimerAndDeallocDemo[2028:697020] Retain count is 7
2016-07-06 13:53:21.950 NSTimerAndDeallocDemo[2028:697020] Retain count is 8
2016-07-06 13:53:21.950 NSTimerAndDeallocDemo[2028:697020] Retain count is 7

如果你還是不理解, 那只能用"殺手锏"了, 美圖伺候!

how-to-user-nstimer-06.png

關(guān)于上圖, @JasonHan0991 有不同的解釋, 詳見評(píng)論區(qū), 在此表示感謝!

結(jié)論

綜上所述, 銷毀NSTimer的正確姿勢(shì)應(yīng)該是

[_timer invalidate]; // 真正銷毀NSTimer對(duì)象的地方
_timer = nil; // 對(duì)象置nil是一種規(guī)范和習(xí)慣

慢著, 這個(gè)結(jié)論好像不妥吧?

這都被你發(fā)現(xiàn)了! 銷毀NSTimer的時(shí)機(jī)也是至關(guān)重要的!

如果將上述銷毀NSTimer的代碼放到ViewController的dealloc方法里, 你會(huì)發(fā)現(xiàn)dealloc還是永遠(yuǎn)不會(huì)走的

所以我們要將上述代碼放到ViewController的其他生命周期方法里, 例如ViewWillDisappear中

綜上所述, 銷毀NSTimer的正確姿勢(shì)應(yīng)該是 (這句話我怎么看著這么眼熟, 是的, 這次真的結(jié)論了)

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];

    [_timer invalidate];
    _timer = nil;
}

NSTimer與runloop

上面說(shuō)到scheduledTimerWithTimeInterval方法時(shí), 有這么一句

schedules it on the current run loop in the default mode

加到runloop這件事就不必再解釋了, 而這個(gè)default mode應(yīng)該如何理解呢?

其實(shí)我是不想談runloop的(因?yàn)槔斫獠簧? 所以怕誤導(dǎo)人民群眾), 但是這里不得不解釋下了

runloop會(huì)運(yùn)行在不同的mode, 簡(jiǎn)單來(lái)說(shuō)有以下兩種mode

  • NSDefaultRunLoopMode, 默認(rèn)的mode

  • UITrackingRunLoopMode, 當(dāng)處理UI滾動(dòng)操作時(shí)的mode

所以scheduledTimerWithTimeInterval創(chuàng)建的NSTimer在UI滾動(dòng)時(shí), 是不會(huì)被及時(shí)觸發(fā)的, 因?yàn)榇藭r(shí)NSTimer被加到了default mode

如果想要runloop運(yùn)行在UITrackingRunLoopMode時(shí), 仍然及時(shí)觸發(fā)NSTimer那應(yīng)該怎么辦呢?

應(yīng)該使用timerWithTimeInterval或initWithFireDate, 在創(chuàng)建完NSTimer后, 自己加入到指定的runloop mode

[[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];

NSRunLoopCommonModes又是什么鬼? 不是說(shuō)好的只有兩種mode么?

是滴, 請(qǐng)注意這里的復(fù)數(shù)形式modes, 說(shuō)明它不是一個(gè)mode, 它是mode的集合!

通常情況下NSDefaultRunLoopMode和UITrackingRunLoopMode都已經(jīng)被加入到了common modes集合中, 所以不論runloop運(yùn)行在哪種mode下, NSTimer都會(huì)被及時(shí)觸發(fā)

最后, 我們來(lái)做個(gè)小測(cè)驗(yàn), 來(lái)結(jié)束今天的NSTimer討論吧

測(cè)驗(yàn): 請(qǐng)問(wèn)下面的NSTimer哪個(gè)更準(zhǔn)時(shí)?

// 1
[NSTimer scheduledTimerWithTimeInterval:TimerInterval
                                 target:self
                               selector:@selector(timerSelector:)
                               userInfo:nil
                                repeats:TimerRepeats];

// 2
[[NSRunLoop currentRunLoop] addTimer:_timer
                             forMode:NSDefaultRunLoopMode];

// 3
[[NSRunLoop currentRunLoop] addTimer:_timer
                             forMode:NSRunLoopCommonModes];

答案, 就不貼了, 相信你肯定知道的; 另外, 關(guān)于runloop, 計(jì)劃后續(xù)會(huì)有單獨(dú)的文章來(lái)詳細(xì)討論之

附錄

更多文章, 請(qǐng)支持我的個(gè)人博客

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末旷痕,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子顽冶,更是在濱河造成了極大的恐慌欺抗,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件强重,死亡現(xiàn)場(chǎng)離奇詭異绞呈,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)间景,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門佃声,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人倘要,你說(shuō)我怎么就攤上這事圾亏。” “怎么了碗誉?”我有些...
    開封第一講書人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)父晶。 經(jīng)常有香客問(wèn)我哮缺,道長(zhǎng),這世上最難降的妖魔是什么甲喝? 我笑而不...
    開封第一講書人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任尝苇,我火速辦了婚禮,結(jié)果婚禮上埠胖,老公的妹妹穿的比我還像新娘糠溜。我一直安慰自己,他們只是感情好直撤,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開白布非竿。 她就那樣靜靜地躺著,像睡著了一般谋竖。 火紅的嫁衣襯著肌膚如雪红柱。 梳的紋絲不亂的頭發(fā)上承匣,一...
    開封第一講書人閱讀 49,007評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音锤悄,去河邊找鬼韧骗。 笑死,一個(gè)胖子當(dāng)著我的面吹牛零聚,可吹牛的內(nèi)容都是我干的袍暴。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼隶症,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼政模!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起沿腰,我...
    開封第一講書人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤览徒,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后颂龙,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體习蓬,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年措嵌,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了躲叼。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡企巢,死狀恐怖枫慷,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情浪规,我是刑警寧澤或听,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布,位于F島的核電站笋婿,受9級(jí)特大地震影響誉裆,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜缸濒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一足丢、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧庇配,春花似錦斩跌、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至啸澡,卻和暖如春揭糕,著一層夾襖步出監(jiān)牢的瞬間萝快,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工著角, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留揪漩,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓吏口,卻偏偏與公主長(zhǎng)得像奄容,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子产徊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

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

  • 再一次面試中被問(wèn)到nstimer的爭(zhēng)取使用方法昂勒,原理,我當(dāng)時(shí)就說(shuō)了[_timer invalidate],time...
    iOS開發(fā)小平哥閱讀 4,049評(píng)論 1 13
  • 創(chuàng)建NSTimer 創(chuàng)建NSTimer的常用方法是: + (NSTimer *)scheduledTimerWit...
    LanWor閱讀 1,356評(píng)論 0 2
  • NSTimer是iOS最常用的定時(shí)器工具之一,在使用的時(shí)候常常會(huì)遇到各種各樣的問(wèn)題著洼,最常見的是內(nèi)存泄漏已球,通常我們使...
    bomo閱讀 1,177評(píng)論 0 7
  • 九宮格 時(shí)間:5分鐘 關(guān)鍵詞:小毛蟲 做好自己往核、泥土、小馬、小草、美麗刁岸、小羊、小螞蟻她我、小螳螂 關(guān)鍵詞:小毛蟲虹曙、美麗...
    李宇宙rourou閱讀 479評(píng)論 2 2
  • 原題 代碼庫(kù)的版本號(hào)是從 1 到 n 的整數(shù)。某一天番舆,有人提交了錯(cuò)誤版本的代碼酝碳,因此造成自身及之后版本的代碼在單元...
    Jason_Yuan閱讀 549評(píng)論 0 1