52個(gè)有效方法(52) - 別忘了NSTimer會(huì)保留其目標(biāo)對(duì)象

計(jì)時(shí)器要和“運(yùn)行循環(huán)”(run loop)相關(guān)聯(lián)备恤,運(yùn)行循環(huán)到時(shí)會(huì)觸發(fā)任務(wù)编整。創(chuàng)建NSTimer時(shí)找御,可以將其“預(yù)先安排”在當(dāng)前的運(yùn)行循環(huán)中元镀,也可以先創(chuàng)建好,然后由開發(fā)者來調(diào)度霎桅。無論采用哪種方式栖疑,只有把計(jì)時(shí)器放在運(yùn)行循環(huán)里,它才能正常觸發(fā)任務(wù)滔驶。

NSTimer常用的創(chuàng)建方法有以下兩種

  • 第一種創(chuàng)建方式
    + (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL) repeats;
    
    • scheduledTimerWithTimeInterval 在主線程創(chuàng)建的定時(shí)器會(huì)在創(chuàng)建后自動(dòng)將 timer 添加到主線程的 runloop 并啟動(dòng).
    • 主線程的 runloopModeNSDefaultRunLoopMode遇革,但是在 ScrollView 滑動(dòng)時(shí)執(zhí)行的是 UITrackingRunLoopMode.NSDefaultRunLoopMode被掛起,定時(shí)器失效揭糕,等到停止滑動(dòng)才恢復(fù).
    • 因此需要將 timer 分別加入 UITrackingRunLoopModeNSDefaultRunLoopMode
    [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
    [[NSRunLoop mainRunLoop] addTimer:timer forMode: UITrackingRunLoopMode];
    
    或者直接添加到 NSRunLoopCommonModes
    [[NSRunLoop mainRunLoop] addTimer:timer forMode: NSRunLoopCommonModes];
    
  • 第二種創(chuàng)建方式
    + (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti target:(id)aTarget selector:(SEL)aSelector userInfo:(nullable id)userInfo repeats:(BOOL) repeats;
    
    • timerWithTimeInterval 創(chuàng)建的定時(shí)器不會(huì)直接啟動(dòng)萝快,而需要手動(dòng)添加到 runloop 中;為防止出現(xiàn)滑動(dòng)視圖時(shí)定時(shí)器被掛起著角,可直接添加到 NSRunLoopCommonModes.

保留環(huán)問題

重復(fù)執(zhí)行模式的計(jì)時(shí)器揪漩,很容易引入保留環(huán)

@interface EOCClass : NSObject
- (void)startPolling;
- (void)stopPolling;
@end

@implementation EOCClass{
    NSTimer *_poliTimer;
}

- (id) init{
    return [super init];
}

- (void)dealloc{
    [_poliTimer invalidate];
}

- (void)stopPolling{
    [_poliTimer invalidate];
    _poliTimer = nil;
}

- (void)startPolling{
    _poliTimer = [NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:@selector(p_doPoll) userInfo:nil repeats:YES];
}

- (void)p_doPoll{
    // Poll the resource
}

如果創(chuàng)建了本類實(shí)例,并調(diào)用了startPolling方法吏口。創(chuàng)建計(jì)時(shí)器的時(shí)候奄容,由于目標(biāo)對(duì)象是self,所以要保留此實(shí)例产徊。然而昂勒,因?yàn)橛?jì)時(shí)器是用實(shí)例變量存放的,所以實(shí)例也保留了計(jì)數(shù)器舟铜,于是就產(chǎn)生了保留環(huán)戈盈。
調(diào)用stopPolling方法或令系統(tǒng)將實(shí)例回收(會(huì)自動(dòng)調(diào)用dealloc方法)可以使計(jì)時(shí)器失效,從而打破循環(huán)谆刨,但無法確保startPolling方法一定調(diào)用塘娶,而由于計(jì)時(shí)器保存著實(shí)例,實(shí)例永遠(yuǎn)不會(huì)被系統(tǒng)回收痴荐。當(dāng)EOCClass實(shí)例的最后一個(gè)外部引用移走之后血柳,實(shí)例仍然存活,而計(jì)時(shí)器對(duì)象也就不可能被系統(tǒng)回收生兆,除了計(jì)時(shí)器外沒有別的引用再指向這個(gè)實(shí)例难捌,實(shí)例就永遠(yuǎn)丟失了膝宁,造成內(nèi)存泄漏。

由于計(jì)時(shí)器保留其目標(biāo)對(duì)象,而對(duì)象又保留計(jì)時(shí)器,所以導(dǎo)致保留環(huán)

解決方案:采用塊為計(jì)時(shí)器添加新功能根吁,為NSTimer添加分類

@interface NSTimer (EOCBlocksSupport)
+ (NSTimer*)eoc_scheduledTimerWithTimeInterval:(NSTimeInterval)interval block:(void(^)())block repeats:(BOOL)repeats;
@end

@implementation NSTimer( EOCBlocksSupport)

+ (NSTimer*)eoc_scheduledTimerWithTimeInterval:(NSTimeInterval)interval block:(void (^)())block repeats:(BOOL)repeats{
    return [self scheduledTimerWithTimeInterval:interval target:self selector:@selector(eoc_blockInvoke:) userInfo:[block copy] repeats:repeats];
}

+ (void)eoc_blockInvoke:(NSTimer*)timer{
    void (^block)() = timer.userInfo;
    if (block) {
        block();
    }
}

在外部調(diào)用時(shí)范例


- (void)startPolling{
    __weak EOCClass *weakSelf = self;
    _poliTimer = [NSTimer eoc_scheduledTimerWithTimeInterval:5.0 block:^{
        EOCClass *strongSelf = weakSelf;
        [strongSelf p_doPoll];
    } repeats:YES];
}

這段代碼先定義了一個(gè)弱引用指向self员淫,然后用塊捕獲這個(gè)引用,這樣self就不會(huì)被計(jì)時(shí)器所保留击敌,當(dāng)塊開始執(zhí)行時(shí)介返,立刻生成strong引用,保證實(shí)例在執(zhí)行器繼續(xù)存活沃斤。

采用這種寫法之后圣蝎,如果外界指向 EOCClass 實(shí)例的最后一個(gè)引用將其釋放,則該實(shí)例就可為系統(tǒng)所回收了衡瓶∨枪回收過程中還會(huì)調(diào)用計(jì)時(shí)器的 invalidate 方法,這樣的話哮针,計(jì)時(shí)器就不會(huì)再執(zhí)行任務(wù)了关面。此處使用 weak 引用還能令程序更加安全,因?yàn)橛袝r(shí)開發(fā)者可能在編寫 dealloc 時(shí)忘了調(diào)用計(jì)時(shí)器的 invalidate 方法十厢,從而導(dǎo)致計(jì)時(shí)器再次運(yùn)行等太,若發(fā)生此類情況,則塊里的 weakSelf 會(huì)變成 nil蛮放。

要點(diǎn)
  1. NSTimer對(duì)象會(huì)保留其目標(biāo)缩抡,直到計(jì)時(shí)器本身失效為止,調(diào)用invalidate方法可令計(jì)時(shí)器失效筛武,另外缝其,一次性的計(jì)時(shí)器在觸發(fā)任務(wù)之后也會(huì)失效挎塌。

  2. 反復(fù)執(zhí)行任務(wù)的計(jì)時(shí)器徘六,很容易引入保環(huán),如果這種計(jì)時(shí)器的目標(biāo)對(duì)象又保留了計(jì)時(shí)器本身榴都,那肯定會(huì)導(dǎo)致保留環(huán)待锈。這種環(huán)狀保留關(guān)系,可能是直接發(fā)生的嘴高,也可能是通過其他對(duì)象間接發(fā)生的竿音。

  3. 可以擴(kuò)充NSTimer的功能,用“塊”來打破保留環(huán)拴驮。不過春瞬,除非NSTimer將來在公共接口里提供此功能,否則必須創(chuàng)建分類套啤,將相關(guān)實(shí)現(xiàn)代碼加入其中宽气。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子萄涯,更是在濱河造成了極大的恐慌绪氛,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件涝影,死亡現(xiàn)場(chǎng)離奇詭異枣察,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)燃逻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門序目,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人伯襟,你說我怎么就攤上這事宛琅。” “怎么了逗旁?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵嘿辟,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我片效,道長(zhǎng)红伦,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任淀衣,我火速辦了婚禮昙读,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘膨桥。我一直安慰自己蛮浑,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布只嚣。 她就那樣靜靜地躺著沮稚,像睡著了一般。 火紅的嫁衣襯著肌膚如雪册舞。 梳的紋絲不亂的頭發(fā)上蕴掏,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音调鲸,去河邊找鬼盛杰。 笑死,一個(gè)胖子當(dāng)著我的面吹牛藐石,可吹牛的內(nèi)容都是我干的即供。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼于微,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼逗嫡!你這毒婦竟也來了办素?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤祸穷,失蹤者是張志新(化名)和其女友劉穎性穿,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體雷滚,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡需曾,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了祈远。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片呆万。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖车份,靈堂內(nèi)的尸體忽然破棺而出谋减,到底是詐尸還是另有隱情,我是刑警寧澤扫沼,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布出爹,位于F島的核電站,受9級(jí)特大地震影響缎除,放射性物質(zhì)發(fā)生泄漏严就。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一器罐、第九天 我趴在偏房一處隱蔽的房頂上張望梢为。 院中可真熱鬧,春花似錦轰坊、人聲如沸铸董。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽粟害。三九已至,卻和暖如春樊零,著一層夾襖步出監(jiān)牢的瞬間我磁,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工驻襟, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人芋哭。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓沉衣,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親减牺。 傳聞我的和親對(duì)象是個(gè)殘疾皇子豌习,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

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