iOS面試總結(jié)——精確定時(shí)器

金三銀四,祝大家能找到滿意的工作~

話不多說(shuō),進(jìn)入正題

定時(shí)器相信大家肯定不會(huì)陌生儡遮,iOS中常用的定時(shí)器有三種蔫慧,分別是NSTimer,CADisplayLink和GCD。

NSTimer

兩種方式創(chuàng)建

    NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(test) userInfo:nil repeats:YES];
    // 停止定時(shí)器
    [timer invalidate]; 
     timer == nil 

創(chuàng)建方式2

    NSTimer *timer = [NSTimer timerWithTimeInterval:2 target:self selector:@selector(test) userInfo:nil repeats:YES];
    // 將定時(shí)器添加到runloop中,否則定時(shí)器不會(huì)啟動(dòng)
    [[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    // 停止定時(shí)器
    [timer invalidate];
    timer == nil 

方式1會(huì)自動(dòng)將創(chuàng)建的定時(shí)器以默認(rèn)方式添加到當(dāng)前線程runloop中,而無(wú)需手動(dòng)添加蛤虐。但是在此種模式下,當(dāng)滾動(dòng)屏幕時(shí)runloop會(huì)進(jìn)入另外一種模式肝陪,定時(shí)器會(huì)暫停驳庭,為了解決這種問(wèn)題,可以像方式2那樣把定時(shí)器添加到NSRunLoopCommonModes模式下氯窍。

方式1和方式2在設(shè)置后都會(huì)在間隔設(shè)定的時(shí)間(本例中設(shè)置為2s)后執(zhí)行test方法饲常,如果需要立即執(zhí)行可以使用下面的代碼。
[time fire];

不過(guò)狼讨,NSTimer相對(duì)來(lái)說(shuō)是不精確的贝淤,參考蘋(píng)果官方文檔介紹timer
咳咳,鳥(niǎo)語(yǔ)政供,筆者大致翻譯了一下重點(diǎn)內(nèi)容??

NSTimer 不是一個(gè)基于真實(shí)時(shí)間的機(jī)制播聪。NSTimer被激發(fā)需要滿足三個(gè)條件,
1.NSTimer被添加到特定mode的runloop中布隔;
2.該mode型的runloop正在運(yùn)行离陶;
3.到達(dá)激發(fā)時(shí)間。因?yàn)橐粋€(gè)run loop需要管理大量的輸入源执泰,為了提NSTimer的效率枕磁,時(shí)間間隔限制為50-100毫秒比較合理渡蜻。如果一個(gè)NSTimer的激發(fā)時(shí)間
出現(xiàn)在一個(gè)耗時(shí)的方法中术吝,或者當(dāng)前run loop的mode沒(méi)有監(jiān)測(cè)該NSTimer,那么定時(shí)器就不會(huì)被激發(fā)茸苇,直到下一次run loop檢測(cè)到該NSTimer時(shí)才會(huì)激發(fā)排苍。
因此,NSTimer的實(shí)際激發(fā)時(shí)間很有可能會(huì)比規(guī)劃時(shí)間延后一段時(shí)間。

哎学密,再來(lái)這樣解釋一下

NSTimer導(dǎo)致誤差的原因:
1.NSTimer加在main runloop中淘衙,模式是NSDefaultRunLoopMode,main負(fù)責(zé)所有主線程事件腻暮,例如UI界面的操作彤守,復(fù)雜的運(yùn)算毯侦,這樣在同一個(gè)runloop中timer就會(huì)產(chǎn)生阻塞。
2.模式的改變具垫。主線程的 RunLoop 里有兩個(gè)預(yù)置的 Mode:kCFRunLoopDefaultMode 和 UITrackingRunLoopMode侈离。
當(dāng)你創(chuàng)建一個(gè) Timer 并加到 DefaultMode 時(shí),Timer 會(huì)得到重復(fù)回調(diào)筝蚕,但此時(shí)滑動(dòng)一個(gè)ScrollView時(shí)卦碾,RunLoop 會(huì)將 mode 切換為 TrackingRunLoopMode,這時(shí) Timer 就不會(huì)被回調(diào)起宽,并且也不會(huì)影響到滑動(dòng)操作洲胖。所以就會(huì)影響到NSTimer不準(zhǔn)的情況。
PS:DefaultMode 是 App 平時(shí)所處的狀態(tài)坯沪,rackingRunLoopMode 是追蹤 ScrollView 滑動(dòng)時(shí)的狀態(tài)绿映。

解決辦法

方案1.在主線程中進(jìn)行NSTimer操作,但是將NSTimer實(shí)例加到main runloop的特定mode(模式)中腐晾。避免被復(fù)雜運(yùn)算操作或者UI界面刷新所干擾绘梦。
NSTimer *timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(test) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

方案2.在子線程中進(jìn)行NSTimer的操作,再在主線程中修改UI界面顯示操作結(jié)果赴魁;
- (void)timer2 {
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(newThread) object:nil];
    [thread start];
}
- (void)newThread {
    @autoreleasepool {
    [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(showTime) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] run];
      }
}
CADisplayLink
// 創(chuàng)建displayLink
    CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(test:)];
    // 將創(chuàng)建的displaylink添加到runloop中卸奉,否則定時(shí)器不會(huì)執(zhí)行
    [displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];

    // 停止定時(shí)器
    [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方法 
需要注意的地方

調(diào)用時(shí)機(jī)

CADisplayLink是一個(gè)和屏幕刷新率同步的定時(shí)器類。CADisplayLink以特定模式注冊(cè)到runloop后芦岂,每當(dāng)屏幕顯示內(nèi)容刷新結(jié)束的時(shí)候瘪弓,runloop就會(huì)向CADisplayLink指定的target發(fā)送一次指定的selector消息,CADisplayLink類對(duì)應(yīng)的selector就會(huì)被調(diào)用一次禽最,所以可以使用CADisplayLink做一些和屏幕操作相關(guān)的操作腺怯。

重要屬性

1.frameInterval
  NSInteger類型的值,用來(lái)設(shè)置間隔多少幀調(diào)用一次selector方法川无,默認(rèn)值是1呛占,即每幀都調(diào)用一次。
2.duration
  readOnly的CFTimeInterval值懦趋,表示兩次屏幕刷新之間的時(shí)間間隔晾虑。需要注意的是,該屬性在target的selector被首次調(diào)用以后才會(huì)被賦值。selector的調(diào)用間隔時(shí)間計(jì)算方式是:調(diào)用間隔時(shí)間 = duration × frameInterval帜篇。
3.timestamp
只讀的CFTimeInterval值糙捺,表示屏幕顯示的上一幀的時(shí)間戳,這個(gè)屬性通常被target用來(lái)計(jì)算下一幀中應(yīng)該顯示的內(nèi)容笙隙。
CADisplayLink注意點(diǎn)總結(jié)

注意點(diǎn):
iOS并不能保證能以每秒60次的頻率調(diào)用回調(diào)方法继找,這取決于:
1、CPU的空閑程度
如果CPU忙于其它計(jì)算逃沿,就沒(méi)法保證以60HZ執(zhí)行屏幕的繪制動(dòng)作婴渡,導(dǎo)致跳過(guò)若干次調(diào)用回調(diào)方法的機(jī)會(huì),跳過(guò)次數(shù)取決CPU的忙碌程度凯亮。
2边臼、執(zhí)行回調(diào)方法所用的時(shí)間
如果執(zhí)行回調(diào)時(shí)間大于重繪每幀的間隔時(shí)間,就會(huì)導(dǎo)致跳過(guò)若干次回調(diào)調(diào)用機(jī)會(huì)假消,這取決于執(zhí)行時(shí)間長(zhǎng)短柠并。

總結(jié):
從原理上不難看出,CADisplayLink使用場(chǎng)合相對(duì)專一富拗,適合做界面的不停重繪臼予,比如視頻播放的時(shí)候需要不停地獲取下一幀用于界面渲染。

GCD定時(shí)器

一次性定時(shí)
 dispatch_time_t timer = dispatch_time(DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC);

 dispatch_after(timer, dispatch_get_main_queue(), ^(void){

        NSLog(@"GCD-----%@",[NSThread currentThread]);

    }); 
重復(fù)執(zhí)行的定時(shí)器
@property (nonatomic ,strong)dispatch_source_t timer;//  注意:此處應(yīng)該使用強(qiáng)引用 strong
{
    //0.創(chuàng)建隊(duì)列
    dispatch_queue_t queue = dispatch_get_main_queue();
    //1.創(chuàng)建GCD中的定時(shí)器
    /*
     第一個(gè)參數(shù):創(chuàng)建source的類型 DISPATCH_SOURCE_TYPE_TIMER:定時(shí)器
     第二個(gè)參數(shù):0
     第三個(gè)參數(shù):0
     第四個(gè)參數(shù):隊(duì)列
     */
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);

    //2.設(shè)置時(shí)間等
    /*
     第一個(gè)參數(shù):定時(shí)器對(duì)象
     第二個(gè)參數(shù):DISPATCH_TIME_NOW 表示從現(xiàn)在開(kāi)始計(jì)時(shí)
     第三個(gè)參數(shù):間隔時(shí)間 GCD里面的時(shí)間最小單位為 納秒
     第四個(gè)參數(shù):精準(zhǔn)度(表示允許的誤差,0表示絕對(duì)精準(zhǔn))
     */
    dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);

    //3.要調(diào)用的任務(wù)
    dispatch_source_set_event_handler(timer, ^{
        NSLog(@"GCD-----%@",[NSThread currentThread]);
    });

    //4.開(kāi)始執(zhí)行
    dispatch_resume(timer);

    //
    self.timer = timer;
} 

注意的地方: 此處注意一定要強(qiáng)引用定時(shí)器 啃沪,否則定時(shí)器執(zhí)行到 } 后將會(huì)被釋放粘拾,無(wú)定時(shí)效果。GCD定時(shí)器時(shí)間非常精準(zhǔn)创千,最小的定時(shí)時(shí)間可以達(dá)到1納秒缰雇,所以用在非常精確的定時(shí)場(chǎng)合。

補(bǔ)充一下

NSObject的方法也有類似功能的方法

- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;

敲黑板總結(jié)

1.NSTimer和performSelector必須保證有一個(gè)活躍的runloop追驴。

performSelector和scheduledTimerWithTimeInterval方法都是基于runloop的械哟。我們知道,當(dāng)一個(gè)應(yīng)用啟動(dòng)時(shí)殿雪,系統(tǒng)會(huì)開(kāi)啟一個(gè)主線程暇咆,并且把主線程的runloop激活,也就是run起來(lái)丙曙,并且主線程的runloop是不會(huì)停止的爸业。所以,當(dāng)這兩個(gè)方法在主線程可以被正常調(diào)用河泳。但情況往往不是這樣的沃呢。實(shí)際編碼中,我們更多的邏輯是放在子線程中執(zhí)行的拆挥。而子線程的runloop是默認(rèn)關(guān)閉的。這時(shí)如果不手動(dòng)激活runloop,performSelector和scheduledTimerWithTimeInterval的調(diào)用將是無(wú)效的纸兔。

2.NSTimer的創(chuàng)建與撤銷必須在同一個(gè)線程操作惰瓜、performSelector的創(chuàng)建與撤銷必須在同一個(gè)線程操作。

3.內(nèi)存管理有潛在泄露的風(fēng)險(xiǎn)

scheduledTimerWithTimeInterval方法將target設(shè)為A對(duì)象時(shí)汉矿,A對(duì)象會(huì)被這個(gè)timer所持有崎坊,也就是會(huì)被retain一次,timer會(huì)被當(dāng)前的runloop所持有洲拇。performSelector:withObject:afterDelay:方法實(shí)際上是在當(dāng)前線程的runloop里幫你創(chuàng)建的一個(gè)timer去執(zhí)行任務(wù)奈揍,所以和scheduledTimerWithTimeInterval方法一樣會(huì)retain其調(diào)用對(duì)象。但是赋续,我們往往不希望因?yàn)檫@些延遲操作而影響對(duì)象的生命周期男翰,更甚至是,導(dǎo)致對(duì)象無(wú)法釋放纽乱。

4.CADisplayLink

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ì)就靈活的多话原。
iOS設(shè)備的屏幕刷新頻率是固定的,CADisplayLink在正常情況下會(huì)在每次刷新結(jié)束都被調(diào)用诲锹,精確度相當(dāng)高繁仁。

NSTimer的精確度就顯得低了點(diǎn),比如NSTimer的觸發(fā)時(shí)間到的時(shí)候归园,runloop如果在忙于別的調(diào)用黄虱,觸發(fā)時(shí)間就會(huì)推遲到下一個(gè)runloop周期。更有甚者庸诱,在OS X v10.9以后為了盡量避免在NSTimer觸發(fā)時(shí)間到了而去中斷當(dāng)前處理的任務(wù)捻浦,NSTimer新增了tolerance屬性,讓用戶可以設(shè)置可以容忍的觸發(fā)的時(shí)間范圍桥爽。

5.GCD

若使用dispatch_after朱灿,系統(tǒng)會(huì)幫我們處理線程級(jí)的邏輯,這樣也我們更易于享受系統(tǒng)對(duì)線程所做的優(yōu)化钠四。除此之外盗扒,我們不用關(guān)心runloop的問(wèn)題跪楞。并且調(diào)用的對(duì)象也不會(huì)被強(qiáng)行持有,這樣上述的內(nèi)存問(wèn)題也不復(fù)存在侣灶。當(dāng)然甸祭,需要注意block會(huì)持有其傳入的對(duì)象,但這可以通過(guò)weakself解決褥影。所以在這種延遲操作方案中池户,使用dispatch_after更佳。

但是呢凡怎,dispatch_after有個(gè)致命的弱點(diǎn):dispatch_after一旦執(zhí)行后校焦,就不能撤銷了。而performSelector可以使用cancelPreviousPerformRequestsWithTarget方法撤銷统倒,NSTimer也可以調(diào)用invalidate進(jìn)行撤銷寨典。(注意:撤銷任務(wù)與創(chuàng)建timer任務(wù)必須在同一個(gè)線程,即同一個(gè)runloop)所以我們還是得用NSTimer或者performSelector嗎檐薯?

NO凝赛,其實(shí)GCD也有timer的功能。

筆者自己封裝了利用GCD實(shí)現(xiàn)的定時(shí)器坛缕,大家可以拿去直接使用墓猎。

傳送門(mén)

end...

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市赚楚,隨后出現(xiàn)的幾起案子毙沾,更是在濱河造成了極大的恐慌,老刑警劉巖宠页,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件左胞,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡举户,警方通過(guò)查閱死者的電腦和手機(jī)烤宙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)俭嘁,“玉大人躺枕,你說(shuō)我怎么就攤上這事」┨睿” “怎么了拐云?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)近她。 經(jīng)常有香客問(wèn)我叉瘩,道長(zhǎng),這世上最難降的妖魔是什么粘捎? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任薇缅,我火速辦了婚禮危彩,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘捅暴。我一直安慰自己恬砂,他們只是感情好咧纠,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布蓬痒。 她就那樣靜靜地躺著,像睡著了一般漆羔。 火紅的嫁衣襯著肌膚如雪梧奢。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,950評(píng)論 1 291
  • 那天演痒,我揣著相機(jī)與錄音亲轨,去河邊找鬼。 笑死鸟顺,一個(gè)胖子當(dāng)著我的面吹牛惦蚊,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播讯嫂,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼蹦锋,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了欧芽?” 一聲冷哼從身側(cè)響起莉掂,我...
    開(kāi)封第一講書(shū)人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎千扔,沒(méi)想到半個(gè)月后憎妙,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡曲楚,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年厘唾,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片龙誊。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡抚垃,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出载迄,到底是詐尸還是另有隱情讯柔,我是刑警寧澤,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布护昧,位于F島的核電站魂迄,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏惋耙。R本人自食惡果不足惜捣炬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一熊昌、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧湿酸,春花似錦婿屹、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至铁坎,卻和暖如春蜂奸,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背硬萍。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工扩所, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人朴乖。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓祖屏,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親买羞。 傳聞我的和親對(duì)象是個(gè)殘疾皇子袁勺,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350

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

  • 最近看了很多RunLoop的文章,看完很懵逼哩都,決心整理一下魁兼,文章中大部分內(nèi)容都是引用大神們的,但好歹對(duì)自己有個(gè)交代...
    小涼介閱讀 6,700評(píng)論 12 79
  • 一. NSTimer NSTimer的初始化方法有以下幾種: 會(huì)自動(dòng)啟動(dòng), 并加入* MainRunloop*的*...
    codeshow閱讀 500評(píng)論 0 0
  • 定時(shí)器漠嵌,用來(lái)延遲或重復(fù)執(zhí)行某些方法咐汞,例如:網(wǎng)絡(luò)定時(shí)刷新,UI間隔刷新儒鹿,動(dòng)畫(huà)效果......iOS中的定時(shí)器大致分為...
    sweetpf閱讀 726評(píng)論 1 2
  • 1 Runloop機(jī)制原理 深入理解RunLoop http://www.cocoachina.com/ios/2...
    Kevin_Junbaozi閱讀 3,998評(píng)論 4 30
  • That'?s a love story约炎,can you hear it植阴? Warsaw Station 500路...
    Russland閱讀 493評(píng)論 0 0