timer中的那些坑

引言

我們?cè)谑褂胻imer的時(shí)候多多少少都遇到過一些坑,今天就來說說timer使用中的那些坑

1.循環(huán)引用導(dǎo)致的內(nèi)存泄露的問題

@implementation SecondController {
    NSTimer *_testTimer;
}

- (void)viewDidLoad {
    _testTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(handleTestTimer) userInfo:nil repeats:NO];
}

- (void)dealloc {
  NSLog(@"dealloc!");
    [_testTimer invalidate];
}

- (void)handleTestTimer {
    NSLog(@"hello world!");
}

@end

我們可能寫過類似上面的代碼,一般情況下它是可以正常執(zhí)行的侵状,我們并沒有過多的去想timer的問題,但是實(shí)際上這樣寫是有問題的蝙砌。如果創(chuàng)建timer時(shí)repeats:YES随夸,再運(yùn)行的話,我們發(fā)現(xiàn)dealloc函數(shù)就永遠(yuǎn)不會(huì)調(diào)用了(我們這里SecondController是被另一個(gè)vc push進(jìn)來的)。

引起這個(gè)問題的原因就是:timer會(huì)強(qiáng)引用自己的target比默,在上面的例子中幻捏,我們的vc是強(qiáng)引用_testTimer對(duì)象的,但是創(chuàng)建這個(gè)timer的時(shí)候我們的target傳入的是self命咐,此時(shí)就導(dǎo)致了tiemr也強(qiáng)引用了self篡九,導(dǎo)致循環(huán)引用的產(chǎn)生。

準(zhǔn)確的說醋奠,timer在isValid為YES的時(shí)候是強(qiáng)引用自己的target的榛臼,所以一般我們都把invalidate的時(shí)機(jī)放在viewWillDisappear:或viewDidDisappear:的時(shí)候,這樣vc就會(huì)正常釋放了钝域。

@implementation SecondController {
    NSTimer *_testTimer;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    _testTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(handleTestTimer) userInfo:nil repeats:YES];
}

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    if (_testTimer.isValid) {
        [_testTimer invalidate];
    }
}

- (void)dealloc {
    NSLog(@"dealloc!");
}

- (void)handleTestTimer {
    NSLog(@"hello world!");
}
@end

上面這樣就可以正常釋放了讽坏,但這里還要說一點(diǎn):

循環(huán)引用和內(nèi)存泄露還是稍有不同的

就拿我們的timer舉例,如果我們?cè)趧?chuàng)建timer的時(shí)候repeats:NO例证,但是觸發(fā)的時(shí)機(jī)是30s路呜,但是我們?cè)谶@個(gè)vc中停留小于30s,就會(huì)造成暫時(shí)的循環(huán)引用织咧,但是這種情況也不能說是內(nèi)存泄露胀葱,從我們退出vc開始到timer觸發(fā)時(shí)的這一段時(shí)間內(nèi),vc和timer造成了循環(huán)引用笙蒙,但是當(dāng)timer觸發(fā)后抵屿,你會(huì)發(fā)現(xiàn),vc的dealloc也被調(diào)用了捅位,此時(shí)vc和timer的內(nèi)存都能夠得到釋放轧葛,循環(huán)引用是有暫時(shí)性的,所以要理解循環(huán)引用和內(nèi)存泄露是稍有不同的艇搀。

可能有人會(huì)想尿扯,為什么非要用一個(gè)全部變量呢?直接:

[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(handleTestTimer) userInfo:nil repeats:YES];

不引入任何全局或局部變量不行嗎焰雕?答案是不行衷笋,即使像上面那樣,創(chuàng)建了timer矩屁,雖然沒有引入任何變量,但是依然會(huì)產(chǎn)生循環(huán)引用吝秕,如果repeats:YES泊脐,就會(huì)造成內(nèi)存泄露了。

為什么沒有任何全局或局部變量的timer仍然會(huì)循環(huán)引用烁峭?下面要說的坑會(huì)解決這個(gè)問題晨抡。

2.不用scheduledTimerWithTimeInterval方式創(chuàng)建timer的問題

@implementation SecondController {
    NSTimer *_testTimer;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    _testTimer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(handleTestTimer) userInfo:nil repeats:YES];
}

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    if (_testTimer.isValid) {
        [_testTimer invalidate];
    }
}

- (void)dealloc {
    NSLog(@"dealloc!");
    [_testTimer invalidate];
}

- (void)handleTestTimer {
    NSLog(@"hello world!");
}

@end

當(dāng)我們不用scheduledTimerWithTimeInterval方式創(chuàng)建timer的時(shí)候你會(huì)發(fā)現(xiàn)timer并不能正常的進(jìn)行回調(diào)。

有人會(huì)想,是不是這種情況要手動(dòng)觸發(fā)呢耘柱,但是即使我們調(diào)用:

[_testTimer fire];

也還是不能讓timer正常的回調(diào)如捅。

正確的做法是:

[[NSRunLoop currentRunLoop] addTimer:_testTimer forMode:NSDefaultRunLoopMode];

這里就解釋了為什么不設(shè)置成任何全局或局部變量的timer也會(huì)導(dǎo)致循環(huán)應(yīng)用的問題,因?yàn)槲覀円裻imer放到runloop里调煎,這個(gè)過程中timer就會(huì)一直在內(nèi)存中镜遣,即使你沒有給他賦值給任何變量,這也說明runloop在addTimer的時(shí)候也是強(qiáng)引用的士袄。

這里有一個(gè)關(guān)于timer很重要的一點(diǎn):NSTimer是基于Runloop的

這里重點(diǎn)不是要講解Runloop悲关,畢竟Runloop要比timer復(fù)雜的多,但為了幫助大家理解娄柳,這里簡(jiǎn)單的說說Runloop:

1.Runloop是用于管理線程的寓辱,它可以讓一個(gè)線程在有任務(wù)執(zhí)行的時(shí)候去執(zhí)行,沒有任務(wù)執(zhí)行的時(shí)候去休息赤拒。

2.一個(gè)線程總是對(duì)應(yīng)一個(gè)或零個(gè)Runloop對(duì)象秫筏,主線程默認(rèn)有一個(gè)Runloop對(duì)象,其他線程默認(rèn)沒有Runloop對(duì)象挎挖,但是可以通過currentRunLoop創(chuàng)建一個(gè)Runloop對(duì)象这敬。

3.Runloop可以保證程序循環(huán)執(zhí)行,而非線性執(zhí)行蕉朵,實(shí)際上Runloop就是用while循環(huán)來實(shí)現(xiàn)的崔涂,而且它并不是iOS系統(tǒng)特有的概念,只要能保證程序非線性的執(zhí)行的系統(tǒng)都有先關(guān)的概念始衅,像Android里的looper冷蚂。

3.timer觸發(fā)時(shí)機(jī)不準(zhǔn)的問題

timer發(fā)生回調(diào)的時(shí)機(jī)有時(shí)候并不一定完全由你設(shè)置的回調(diào)時(shí)間來決定,這要說到runloop mode汛闸,為了說明這個(gè)坑蝙茶,還是簡(jiǎn)單的介紹一下runloop mode。

1.一般常用的runloop mode是Default和Common modes蛉拙,除此之外尸闸,還有Connection彻亲、Modal孕锄、Event tracking等不同的mode。

2.一個(gè)線程可以對(duì)應(yīng)一個(gè)或零個(gè)runloop苞尝,一個(gè)runloop可以對(duì)應(yīng)一個(gè)或多個(gè)runloop mode畸肆,一個(gè)runloop mode中會(huì)有多個(gè)source。

3.一個(gè)runloop同時(shí)只能執(zhí)行一個(gè)runloop mode里的source宙址,系統(tǒng)對(duì)不同的runloop mode有不同的處理策略和優(yōu)先級(jí)轴脐。

我們常用的Default mode,優(yōu)先級(jí)很低,例如當(dāng)我們滾動(dòng)scrollview的時(shí)候大咱,此時(shí)runloop會(huì)切換到Event tracking的mode上恬涧,此時(shí)處于Default mode里的timer就不能得到執(zhí)行,直到這個(gè)runloop把mode切換到Default的時(shí)候timer才能被觸發(fā)碴巾,而沒有觸發(fā)timer的這段時(shí)間會(huì)按照timer設(shè)置的回調(diào)時(shí)間取整的進(jìn)行一次性觸發(fā)多次timer回調(diào)溯捆。為了保證timer的回調(diào)時(shí)機(jī)準(zhǔn)確,我們可以把timer放在Common modes里厦瓢。

4.子線程中timer的坑

@implementation SecondController {
    NSTimer *_testTimer;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"1:%p", [NSRunLoop currentRunLoop]);
    [self performSelectorInBackground:@selector(testTimer) withObject:nil];
}

- (void)testTimer {
    NSLog(@"2:%p", [NSRunLoop currentRunLoop]);
    _testTimer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(handleTestTimer) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:_testTimer forMode:NSDefaultRunLoopMode];
}

- (void)viewWillDisappear:(BOOL)animated {
    [super viewWillDisappear:animated];
    if (_testTimer.isValid) {
        [_testTimer invalidate];
    }
}

- (void)dealloc {
    NSLog(@"dealloc!");
    [_testTimer invalidate];
}

- (void)handleTestTimer {
    NSLog(@"hello world!");
}

@end

當(dāng)我們用performSelector的方式把timer放到子線程中的runloop里時(shí)發(fā)現(xiàn)timer又不好使了提揍。

這個(gè)坑的主要原因是,子線程默認(rèn)是沒有runloop的煮仇,currentRunloop的方式是可以獲得runloop的劳跃,但是此時(shí)的這個(gè)runloop并沒有run,我們要在[[NSRunLoop currentRunLoop] addTimer:_testTimer forMode:NSDefaultRunLoopMode];后調(diào)用[[NSRunLoop currentRunLoop] run];才可以使得這個(gè)timer正常工作的浙垫。

結(jié)論

一個(gè)小小的timer當(dāng)我們仔細(xì)去研究的時(shí)候發(fā)現(xiàn)它大有文章可做刨仑,好多東西都值得我們深入研究,例如timer會(huì)強(qiáng)引用自己的target導(dǎo)致循環(huán)引用绞呈,所以我們?cè)诿看斡胻imer的時(shí)候都要小心贸人,當(dāng)timer頁面要消失的時(shí)候我們總是要調(diào)用invalidate, 這樣很麻煩,我們能不能使用timer的時(shí)候不用去考慮它的釋放問題佃声,這個(gè)問題就是自釋放的問題艺智,我們可以想想能不能實(shí)現(xiàn)一個(gè)自釋放的timer。

歡迎大家和我交流溝通圾亏,若文章中有錯(cuò)誤和紕漏十拣,懇請(qǐng)指正,謝謝志鹃,如果感覺評(píng)論不方便歡迎微博私信夭问。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市曹铃,隨后出現(xiàn)的幾起案子缰趋,更是在濱河造成了極大的恐慌,老刑警劉巖陕见,帶你破解...
    沈念sama閱讀 219,490評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件秘血,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡评甜,警方通過查閱死者的電腦和手機(jī)灰粮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來忍坷,“玉大人粘舟,你說我怎么就攤上這事熔脂。” “怎么了柑肴?”我有些...
    開封第一講書人閱讀 165,830評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵霞揉,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我晰骑,道長(zhǎng)零聚,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,957評(píng)論 1 295
  • 正文 為了忘掉前任些侍,我火速辦了婚禮隶症,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘岗宣。我一直安慰自己蚂会,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評(píng)論 6 393
  • 文/花漫 我一把揭開白布耗式。 她就那樣靜靜地躺著胁住,像睡著了一般。 火紅的嫁衣襯著肌膚如雪刊咳。 梳的紋絲不亂的頭發(fā)上彪见,一...
    開封第一講書人閱讀 51,754評(píng)論 1 307
  • 那天,我揣著相機(jī)與錄音娱挨,去河邊找鬼余指。 笑死,一個(gè)胖子當(dāng)著我的面吹牛跷坝,可吹牛的內(nèi)容都是我干的酵镜。 我是一名探鬼主播,決...
    沈念sama閱讀 40,464評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼柴钻,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼淮韭!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起贴届,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤靠粪,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后毫蚓,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體占键,經(jīng)...
    沈念sama閱讀 45,847評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評(píng)論 3 338
  • 正文 我和宋清朗相戀三年绍些,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了捞慌。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片耀鸦。...
    茶點(diǎn)故事閱讀 40,137評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡柬批,死狀恐怖啸澡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情氮帐,我是刑警寧澤嗅虏,帶...
    沈念sama閱讀 35,819評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站上沐,受9級(jí)特大地震影響皮服,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜参咙,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評(píng)論 3 331
  • 文/蒙蒙 一龄广、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蕴侧,春花似錦择同、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至择葡,卻和暖如春紧武,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背敏储。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工阻星, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人已添。 一個(gè)月前我還...
    沈念sama閱讀 48,409評(píng)論 3 373
  • 正文 我出身青樓迫横,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親酝碳。 傳聞我的和親對(duì)象是個(gè)殘疾皇子矾踱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評(píng)論 2 355

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

  • 一、什么是runloop 字面意思是“消息循環(huán)疏哗、運(yùn)行循環(huán)”呛讲。它不是線程,但它和線程息息相關(guān)返奉。一般來講贝搁,一個(gè)線程一次...
    WeiHing閱讀 8,144評(píng)論 11 111
  • 這是AF2.x經(jīng)典的代碼: 首先我們要明確一個(gè)概念污尉,線程一般都是一次執(zhí)行完任務(wù)膀哲,就銷毀了往产。 而添加了runloop...
    有夢(mèng)想的老伯伯閱讀 2,001評(píng)論 5 13
  • 轉(zhuǎn)載:http://www.cocoachina.com/ios/20150601/11970.html RunL...
    Gatling閱讀 1,438評(píng)論 0 13
  • 是一個(gè)接一個(gè)的夢(mèng)境 是想起時(shí)的甜蜜 是每天翻過的日歷 是想你時(shí)涌上心頭的思念著又有些著急
    王不煩閱讀 180評(píng)論 0 0
  • 十一長(zhǎng)假又要到來了,心里是否早已美滋滋呢某宪,望著天上的月亮仿村,腦袋里轉(zhuǎn)著小算盤——好想去北京看天安門哦,好想去天安門看...
    我要改個(gè)名字閱讀 285評(píng)論 0 1