NSTimer 運(yùn)行機(jī)制2

NSTimer和它調(diào)用的函數(shù)對(duì)象間到底發(fā)生什么

timer會(huì)在未來(lái)的某個(gè)時(shí)刻執(zhí)行一次或者多次我們指定的方法,這也就牽扯出一個(gè)問題姥宝,如何保證timer在未來(lái)的某個(gè)時(shí)刻觸發(fā)指定事件的時(shí)候殴瘦,我們指定的方法是有效的呢妓雾?

解決方法很簡(jiǎn)單,只要將指定給timer的方法的接收者retain一份就搞定了殖熟,實(shí)際上系統(tǒng)也是這樣做的彻犁。不管是重復(fù)性的timer還是一次性的timer都會(huì)對(duì)它的方法的接收者進(jìn)行retain,這兩種timer的區(qū)別在于“一次性的timer在完成調(diào)用以后會(huì)自動(dòng)將自己invalidate,而重復(fù)的timer則將永生叫胁,直到你調(diào)用invalidate方法終止」保”

下面看一個(gè)小例子

SvTestObject.h

`#import <Foundation/Foundation.h>

@interface SvTestObject : NSObject

/*
 * @brief timer響應(yīng)函數(shù)驼鹅,只是用來(lái)做測(cè)試
 */
- (void)timerAction:(NSTimer*)timer;

@end
SvTestObject.m

`#import "SvTestObject.h"

@implementation SvTestObject

- (id)init
{
    self = [super init];
    if (self) {
        NSLog(@"instance %@ has been created!", self);
    }
    
    return self;
}

- (void)dealloc
{
    NSLog(@"instance %@ has been dealloced!", self);
    
    [super dealloc];
}

- (void)timerAction:(NSTimer*)timer
{
    NSLog(@"Hi, Timer Action for instance %@", self);
}

@end
SvTimerAppDelegate.m

- (void)applicationDidBecomeActive:(UIApplication *)application
{
    // test Timer retain target
    [self testNonRepeatTimer];
//    [self testRepeatTimer];
}

- (void)testNonRepeatTimer
{
    NSLog(@"Test retatin target for non-repeat timer!");
    SvTestObject *testObject = [[SvTestObject alloc] init];
    [NSTimer scheduledTimerWithTimeInterval:5 target:testObject selector:@selector(timerAction:) userInfo:nil repeats:NO];
    [testObject release];
    NSLog(@"Invoke release to testObject!");
}

- (void)testRepeatTimer
{
    NSLog(@"Test retain target for repeat Timer");
    SvTestObject *testObject2 = [[SvTestObject alloc] init];
    [NSTimer scheduledTimerWithTimeInterval:5 target:testObject2 selector:@selector(timerAction:) userInfo:nil repeats:YES];
    [testObject2 release];
    NSLog(@"Invoke release to testObject2!");
}

上面的簡(jiǎn)單例子中,我們自定義了一個(gè)繼承自NSObject的類SvTestObject急鳄,在這個(gè)類的init谤民,dealloc和它的timerAction三個(gè)方法中分別打印信息。然后在appDelegate中分別測(cè)試一個(gè)單次執(zhí)行的timer和一個(gè)重復(fù)執(zhí)行的timer對(duì)方法接受者是否做了retain操作疾宏,因此我們?cè)趦煞N情況下都是shedule完timer之后立馬對(duì)該測(cè)試對(duì)象執(zhí)行release操作张足。

測(cè)試單次執(zhí)行的timer的結(jié)果如下:


timer單次執(zhí)行

觀察輸出,我們會(huì)發(fā)現(xiàn)53分58秒的時(shí)候我們就對(duì)測(cè)試對(duì)象執(zhí)行了release操作坎藐,但是知道54分03秒的時(shí)候timer觸發(fā)完方法以后为牍,該對(duì)象才實(shí)際的執(zhí)行了dealloc方法。這就證明一次性的timer也會(huì)retain它的方法接收者岩馍,直到自己失效為之碉咆。

測(cè)試重復(fù)性的timer的結(jié)果如下:


timer 重復(fù)性的執(zhí)行

觀察輸出我們發(fā)現(xiàn),這個(gè)重復(fù)性的timer一直都在周期性的調(diào)用我們?yōu)樗付ǖ姆椒ㄖ鳎覝y(cè)試的對(duì)象也一直沒有真正的被釋放疫铜。

通過以上小例子,我們可以發(fā)現(xiàn)在timer對(duì)它的接收者進(jìn)行retain双谆,從而保證了timer調(diào)用時(shí)的正確性壳咕,但是又引入了接收者的內(nèi)存管理問題。特別是對(duì)于重復(fù)性的timer顽馋,它所引用的對(duì)象將一直存在谓厘,將會(huì)造成內(nèi)存泄露。

有問題就有應(yīng)對(duì)方法寸谜,NSTimer提供了一個(gè)方法invalidate竟稳,讓我們可以解決這種問題。不管是一次性的還是重復(fù)性的timer熊痴,在執(zhí)行完invalidate以后都會(huì)變成無(wú)效他爸,因此對(duì)于重復(fù)性的timer我們一定要有對(duì)應(yīng)的invalidate。

突然想起一種自欺欺人的寫法果善,不知道你們有沒有這么寫過

·#import "SvCheatYourself.h"

@interface SvCheatYourself () {
    NSTimer *_timer;
}

@end

@implementation SvCheatYourself

- (id)init
{
    self = [super init];
    if (self) {
        _timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(testTimer:) userInfo:nil repeats:YES];
    }
    
    return self;
}

- (void)dealloc
{
    // 自欺欺人的寫法诊笤,永遠(yuǎn)都不會(huì)執(zhí)行到,除非你在外部手動(dòng)invalidate這個(gè)timer
    [_timer invalidate];
    
    [super dealloc];
}

- (void)testTimer:(NSTimer*)timer
{
    NSLog(@"haha!");
}

@end

總結(jié):


timer都會(huì)對(duì)它的target進(jìn)行retain岭埠,我們需要小心對(duì)待這個(gè)target的生命周期問題盏混,尤其是重復(fù)性的timer蔚鸥。

NSTimer為什么要添加到RunLoop中才會(huì)有作用


前面的例子中我們使用的是一種便利方法,它其實(shí)是做了兩件事:首先創(chuàng)建一個(gè)timer许赃,然后將該timer添加到當(dāng)前runloop的default mode中止喷。也就是這個(gè)便利方法給我們?cè)斐闪酥灰獎(jiǎng)?chuàng)建了timer就可以生效的錯(cuò)覺,我們當(dāng)然可以自己創(chuàng)建timer混聊,然后手動(dòng)的把它添加到指定runloop的指定mode中去弹谁。

NSTimer其實(shí)也是一種資源,如果看過多線程變成指引文檔的話句喜,我們會(huì)發(fā)現(xiàn)所有的source如果要起作用预愤,就得加到runloop中去。同理timer這種資源要想起作用咳胃,那肯定也需要加到runloop中才會(huì)又效嘍植康。如果一個(gè)runloop里面不包含任何資源的話,運(yùn)行該runloop時(shí)會(huì)立馬退出展懈。你可能會(huì)說(shuō)那我們APP的主線程的runloop我們沒有往其中添加任何資源销睁,為什么它還好好的運(yùn)行。我們不添加存崖,不代表框架沒有添加冻记,如果有興趣的話你可以打印一下main thread的runloop,你會(huì)發(fā)現(xiàn)有很多資源来惧。

下面我們看一個(gè)小例子:

- (void)applicationDidBecomeActive:(UIApplication *)application
{
    [self testTimerWithOutShedule];
}

- (void)testTimerWithOutShedule
{
    NSLog(@"Test timer without shedult to runloop");
    SvTestObject *testObject3 = [[SvTestObject alloc] init];
    NSTimer *timer = [[NSTimer alloc] initWithFireDate:[NSDate dateWithTimeIntervalSinceNow:1] interval:1 target:testObject3 selector:@selector(timerAction:) userInfo:nil repeats:NO];
    [testObject3 release];
    NSLog(@"invoke release to testObject3");
}

- (void)applicationWillResignActive:(UIApplication *)application
{
    NSLog(@"SvTimerSample Will resign Avtive!");
}

這個(gè)小例子中我們新建了一個(gè)timer冗栗,為它指定了有效的target和selector,并指出了1秒后觸發(fā)該消息供搀,運(yùn)行結(jié)果如下:

timer 延遲執(zhí)行

觀察發(fā)現(xiàn)這個(gè)消息永遠(yuǎn)也不會(huì)觸發(fā)隅居,原因很簡(jiǎn)單,我們沒有將timer添加到runloop中趁曼。

總結(jié): 必須得把timer添加到runloop中军浆,它才會(huì)生效棕洋。

NSTimer加到了RunLoop中但遲遲的不觸發(fā)事件


為什么明明添加了挡闰,但是就是不按照預(yù)先的邏輯觸發(fā)事件呢?掰盘?摄悯?原因主要有以下兩個(gè):

1、runloop是否運(yùn)行

每一個(gè)線程都有它自己的runloop愧捕,程序的主線程會(huì)自動(dòng)的使runloop生效奢驯,但對(duì)于我們自己新建的線程,它的runloop是不會(huì)自己運(yùn)行起來(lái)次绘,當(dāng)我們需要使用它的runloop時(shí)瘪阁,就得自己?jiǎn)?dòng)撒遣。

那么如果我們把一個(gè)timer添加到了非主線的runloop中,它還會(huì)按照預(yù)期按時(shí)觸發(fā)嗎管跺?下面請(qǐng)看一段測(cè)試程序:

- (void)applicationDidBecomeActive:(UIApplication *)application
{
    [NSThread detachNewThreadSelector:@selector(testTimerSheduleToRunloop1) toTarget:self withObject:nil];
}

// 測(cè)試把timer加到不運(yùn)行的runloop上的情況
- (void)testTimerSheduleToRunloop1
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    
    NSLog(@"Test timer shedult to a non-running runloop");
    SvTestObject *testObject4 = [[SvTestObject alloc] init];
    NSTimer *timer = [[NSTimer alloc] initWithFireDate:[NSDate dateWithTimeIntervalSinceNow:1] interval:1 target:testObject4 selector:@selector(timerAction:) userInfo:nil repeats:NO];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
    // 打開下面一行輸出runloop的內(nèi)容就可以看出义黎,timer卻是已經(jīng)被添加進(jìn)去
    //NSLog(@"the thread's runloop: %@", [NSRunLoop currentRunLoop]);
    
    // 打開下面一行, 該線程的runloop就會(huì)運(yùn)行起來(lái),timer才會(huì)起作用
    //[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];
    
    [testObject4 release];
    NSLog(@"invoke release to testObject4");

    [pool release];
}

- (void)applicationWillResignActive:(UIApplication *)application
{
    NSLog(@"SvTimerSample Will resign Avtive!");
}

上面的程序中豁跑,我們新創(chuàng)建了一個(gè)線程廉涕,然后創(chuàng)建一個(gè)timer,并把它添加當(dāng)該線程的runloop當(dāng)中艇拍,但是運(yùn)行結(jié)果如下:

觀察運(yùn)行結(jié)果狐蜕,我們發(fā)現(xiàn)這個(gè)timer知道執(zhí)行退出也沒有觸發(fā)我們指定的方法,如果我們把上面測(cè)試程序中“//[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];”這一行的注釋去掉卸夕,則timer將會(huì)正確的掉用我們指定的方法层释。

2、mode是否正確

我們前面自己動(dòng)手添加runloop的時(shí)候快集,可以看到有一個(gè)參數(shù)runloopMode湃累,這個(gè)參數(shù)是干嘛的呢?

前面提到了要想timer生效碍讨,我們就得把它添加到指定runloop的指定mode中去治力,通常是主線程的defalut mode。但有時(shí)我們這樣做了勃黍,卻仍然發(fā)現(xiàn)timer還是沒有觸發(fā)事件宵统。這是為什么呢?

這是因?yàn)閠imer添加的時(shí)候覆获,我們需要指定一個(gè)mode马澈,因?yàn)橥痪€程的runloop在運(yùn)行的時(shí)候,任意時(shí)刻只能處于一種mode弄息。所以只能當(dāng)程序處于這種mode的時(shí)候痊班,timer才能得到觸發(fā)事件的機(jī)會(huì)。

舉個(gè)不恰當(dāng)?shù)睦幽×浚覀冋f(shuō)兄弟幾個(gè)分別代表runloop的mode涤伐,timer代表他們自己的才水桶,然后一群人去排隊(duì)打水缨称,只有一個(gè)水龍頭凝果,那么同一時(shí)刻,肯定只能有一個(gè)人處于接水的狀態(tài)睦尽。也就是說(shuō)你雖然給了老二一個(gè)桶器净,但是還沒輪到它,那么你就得等当凡,只有輪到他的時(shí)候你的水桶才能碰上用場(chǎng)山害。

綜上: 要讓timer生效纠俭,必須保證該線程的runloop已啟動(dòng),而且其運(yùn)行的runloopmode也要匹配浪慌。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末柑晒,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子眷射,更是在濱河造成了極大的恐慌匙赞,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,835評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件妖碉,死亡現(xiàn)場(chǎng)離奇詭異涌庭,居然都是意外死亡掸鹅,警方通過查閱死者的電腦和手機(jī)秉版,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,900評(píng)論 2 383
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)篡诽,“玉大人冗茸,你說(shuō)我怎么就攤上這事席镀。” “怎么了夏漱?”我有些...
    開封第一講書人閱讀 156,481評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵豪诲,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我挂绰,道長(zhǎng)屎篱,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,303評(píng)論 1 282
  • 正文 為了忘掉前任葵蒂,我火速辦了婚禮交播,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘践付。我一直安慰自己秦士,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,375評(píng)論 5 384
  • 文/花漫 我一把揭開白布永高。 她就那樣靜靜地躺著隧土,像睡著了一般。 火紅的嫁衣襯著肌膚如雪乏梁。 梳的紋絲不亂的頭發(fā)上次洼,一...
    開封第一講書人閱讀 49,729評(píng)論 1 289
  • 那天关贵,我揣著相機(jī)與錄音遇骑,去河邊找鬼。 笑死揖曾,一個(gè)胖子當(dāng)著我的面吹牛落萎,可吹牛的內(nèi)容都是我干的亥啦。 我是一名探鬼主播,決...
    沈念sama閱讀 38,877評(píng)論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼练链,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼翔脱!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起媒鼓,我...
    開封第一講書人閱讀 37,633評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤届吁,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后绿鸣,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體疚沐,經(jīng)...
    沈念sama閱讀 44,088評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,443評(píng)論 2 326
  • 正文 我和宋清朗相戀三年潮模,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了亮蛔。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,563評(píng)論 1 339
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡擎厢,死狀恐怖究流,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情动遭,我是刑警寧澤芬探,帶...
    沈念sama閱讀 34,251評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站厘惦,受9級(jí)特大地震影響灯节,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜绵估,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,827評(píng)論 3 312
  • 文/蒙蒙 一炎疆、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧国裳,春花似錦形入、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,712評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至渺杉,卻和暖如春蛇数,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背是越。 一陣腳步聲響...
    開封第一講書人閱讀 31,943評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工耳舅, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人倚评。 一個(gè)月前我還...
    沈念sama閱讀 46,240評(píng)論 2 360
  • 正文 我出身青樓浦徊,卻偏偏與公主長(zhǎng)得像馏予,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子盔性,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,435評(píng)論 2 348

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

  • 一霞丧、什么是NSTimer 官方給出解釋是“A timer provides a way to perform a ...
    行走的菜譜閱讀 1,139評(píng)論 0 4
  • Run loop 剖析:Runloop 接收的輸入事件來(lái)自兩種不同的源:輸入源(intput source)和定時(shí)...
    Mitchell閱讀 12,422評(píng)論 17 111
  • NSTimer是iOS最常用的定時(shí)器工具之一,在使用的時(shí)候常常會(huì)遇到各種各樣的問題冕香,最常見的是內(nèi)存泄漏蛹尝,通常我們使...
    bomo閱讀 1,184評(píng)論 0 7
  • 前言 最近離職了,可以盡情熬夜寫點(diǎn)總結(jié),不用擔(dān)心第二天上班爽并蛋疼著悉尾,這篇的主角 RunLoop 一座大山箩言,涵蓋的...
    zerocc2014閱讀 12,372評(píng)論 13 67
  • 1.今天補(bǔ)錄完了基金訓(xùn)練營(yíng)的課程內(nèi)容,順帶也回顧了一下基礎(chǔ)知識(shí) 2.今天下午把《偉大的博弈》看完了一般焕襟,了解了華爾...
    半顆糖依然很甜閱讀 154評(píng)論 0 0