iOS 用CADisplayLink實現(xiàn)定時器及其比較

這篇文章會涉及到什么呢?

CADisplayLink的基本使用方法

OC中的三種定時器:CADisplayLink恰起、NSTimer、GCD

runloop淺析

CADisplayLink

點進CADisplayLink的頭文件我們能看到和泌,其實他的方法并不多村缸,而且他的功能很單一祠肥,就是作為一個定時器的存在武氓。

不過既然蘋果專門提供了這么一個類,就一定是有他的存在意義的仇箱。他的優(yōu)勢就在于他的執(zhí)行頻率是根據(jù)設(shè)備屏幕的刷新頻率來計算的县恕。換句話講,他也是時間間隔最準(zhǔn)確的定時器剂桥。

還是在使用中介紹吧忠烛。

- (void)viewDidLoad {

? ? [super viewDidLoad];? ? ? ?

? ? self.view.backgroundColor = [UIColor grayColor];

? ? ///target selector 模式初始化一個實例

? ? self.timerInC = [CADisplayLink displayLinkWithTarget:self selector:@selector(changeImg)];

? ? ///暫停

? ? self.timerInC.paused = YES;

? ? ///selector觸發(fā)間隔

? ? self.timerInC.frameInterval = 2;

? ? self.imgV = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 200, 200)];

? ? self.imgV.contentMode = UIViewContentModeScaleAspectFill;

? ? self.imgV.center = self.view.center;

? ? [self.view addSubview:self.imgV];

? ? ///加入一個runLoop

? ? [self.timerInC addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];

? ? UIButton * button = [UIButton buttonWithType:(UIButtonTypeSystem)];

? ? [button setFrame:CGRectMake(0, 0, 100, 30)];

? ? button.center = CGPointMake(self.view.center.x, self.view.center.y + 200);

? ? [self.view addSubview:button];

? ? [button setTitle:@"開始播放" forState:(UIControlStateNormal)];

? ? [button setBackgroundColor:[UIColor whiteColor]];

? ? [button addTarget:self action:@selector(gifAction) forControlEvents:(UIControlEventTouchUpInside)];

}

-(void)changeImg

{

? ? self.currentIndex ++;

? ? if (self.currentIndex > 75) {

? ? ? ? self.currentIndex = 1;

? ? }

? ? self.imgV.image = [UIImage imageNamed:[NSString stringWithFormat:@"%ld.jpg",self.currentIndex]];

}

-(void)gifAction

{

? ? self.timerInC.paused = !self.timerInC.paused;

}

CADisplayTimer

我們可以從頭文件中看到,蘋果只提供了一個生成實例的接口权逗。

+(CADisplayLink *)displayLinkWithTarget:(id)target selector:(SEL)sel;

通過這個方法美尸,可以以target/selector模式生成一個綁定了觸發(fā)事件的實例冤议。參數(shù)target、selector可以類比button师坎,我就不做具體講解了恕酸。

然而你只生成一個實例你的事件是不會被觸發(fā)的,這是因為你沒有把他加入到runloop當(dāng)中胯陋。

-(void)addToRunLoop:(NSRunLoop *)runloop forMode:(NSString *)mode;

你可以調(diào)用這個方法將實例加入到一個選定的runloop中蕊温,這時我們的事件就能被觸發(fā)了。

-(void)removeFromRunLoop:(NSRunLoop *)runloop forMode:(NSString *)mode;

有添加當(dāng)然會有移除遏乔,當(dāng)你要從某個runloop中移除當(dāng)前實例的時候你可以調(diào)用上面的方法义矛。

類比NSTimer,CADisplayLink也有一個計時器銷毀的方法:

-(void)invalidate;

調(diào)用這個方法盟萨,會從所有runLoop中移除當(dāng)前實例燃领,這個方法可以用于不需要計時器后對他進行釋放前的操作沛慢。

好吧,CADisplayLink就這四個方法。以及四個屬性:

timestamp钮糖,獲取上一次selector被執(zhí)行的時間戳。這個屬性是一個只讀屬性茫叭,而且你要記住的是只有當(dāng)selector被執(zhí)行過一次之后這個值才會被取到有效值会烙。這個屬性同上是用來比較當(dāng)前圖層時間與上一次selector執(zhí)行時間只差,從而來計算本次UI應(yīng)該發(fā)生的改變的進度(例如視圖做移動效果)韭赘。

duration缩滨,獲取當(dāng)前設(shè)備的屏幕刷新時間間隔。同timestamp一樣泉瞻,他也是個只讀屬性脉漏,并且也需要selector觸發(fā)一次才可以取值。值的一提的是袖牙,當(dāng)前iOS設(shè)備的刷新頻率都是60HZ侧巨。也就是說每16.7ms刷新一次。作用也與timestamp相同鞭达,都可以用于輔助計算司忱。不過需要說明的一點是,如果CPU過于繁忙畴蹭,duration的值是會浮動的坦仍。

paused,看名字就能看出來叨襟,是控制計時器暫停與恢復(fù)的屬性繁扎。設(shè)置為YES的時候會暫停事件的觸發(fā)。

frameInterval,事件觸發(fā)間隔梳玫。是指兩次selector觸發(fā)之間間隔幾次屏幕刷新爹梁,默認(rèn)值為1,也就是說屏幕每刷新一次提澎,執(zhí)行一次selector卫键,這個也可以間接用來控制動畫速度。

兩次selector觸發(fā)的時間間隔是time = frameInterVal * duration虱朵。必須注意的是莉炉,selector執(zhí)行所需要的時間一定要小于其觸發(fā)間隔,否則會造成掉幀情況碴犬。

總體來說絮宁,CADisplayLink的使用還是比較簡單的。

三種定時器的優(yōu)勢與劣勢

CADisplayLink

基本用法上文剛剛介紹過服协。

優(yōu)勢:依托于設(shè)備屏幕刷新頻率觸發(fā)事件绍昂,所以其觸發(fā)時間上是最準(zhǔn)確的。也是最適合做UI不斷刷新的事件偿荷,過渡相對流暢窘游,無卡頓感。

缺點:

由于依托于屏幕刷新頻率跳纳,若果CPU不堪重負(fù)而影響了屏幕刷新忍饰,那么我們的觸發(fā)事件也會受到相應(yīng)影響。

selector觸發(fā)的時間間隔只能是duration的整倍數(shù)寺庄。

selector事件如果大于其觸發(fā)間隔就會造成掉幀現(xiàn)象艾蓝。

CADisplayLink不能被繼承。

NSTimer

基本用法:

self.timerInN = [NSTimer timerWithTimeInterval:0.032 target:self selector:@selector(changeImg) userInfo:nil repeats:YES];

? ? [[NSRunLoop currentRunLoop] addTimer:self.timerInN forMode:NSRunLoopCommonModes];

NSTimer的使用方法也相對簡單斗塘。

首先赢织,有5個方法可以為我們提供NSTimer實例。

分三類馍盟,以timer開頭的兩個類方法于置,以schedule開頭的兩個類方法以及以init開頭的一個實例方法。

以timer開頭的兩個類方法是靈活度最高的兩個方法贞岭。這兩個方法的不同點在于綁定事件的方式八毯。一個使用NSInvocation進行轉(zhuǎn)發(fā)消息,一個使用target/selector模式綁定事件曹步∠懿剩總之就是綁定timer的觸發(fā)事件休讳,這里不做展開講解讲婚。

后面兩個參數(shù)分別是用戶參數(shù)以及重復(fù)模式。

但是單單生成了實例還是不會觸發(fā)我們的事件俊柔,像CADisplayLink一樣我們也需要將他加入到runloop中,之后就可以觸發(fā)我們的事件了筹麸。

只要是使用NSTimer就一定要加入到runloop中才可以觸發(fā)我們的事件活合,你可能會說schedule開頭那兩個類方法就不用添加runloop,這其實是個錯覺物赶,是系統(tǒng)為你將timer添加到了currentRunLoop中白指,defaultModel。

最后一個init開頭的實例方法就是給timer添加了一個定時啟動酵紫,這里就不贅述了告嘲。

NSTimer還有兩個實例方法,fire和invalid奖地。分別是立即執(zhí)行事件和銷毀timer橄唬。這兩個方法比較重要,稍后我會著重講解一下参歹。

接著說一下他的五個屬性仰楚。

fireDate,設(shè)置當(dāng)前timer的事件的觸發(fā)時間犬庇。通常我們使用這個屬性來做計時器的暫停與恢復(fù)僧界。

///暫停計時器

self.timer.fireDate = [NSDate distantFuture];

///恢復(fù)計時器

self.timer.fireDate = [NSDate distantPast];

timeInterval,只讀屬性,獲取當(dāng)前timer的事件的觸發(fā)間隔臭挽。

tolerance捂襟,允許誤差時間。我們知道NSTimer事件的觸發(fā)事件是不準(zhǔn)確的欢峰,完全取決于當(dāng)前runloop處理的時間笆豁。如果當(dāng)前runloop在處理復(fù)雜運算,則timer執(zhí)行時間將會被推遲赤赊,直到復(fù)雜運算結(jié)束后立即執(zhí)行觸發(fā)事件闯狱,之后再按照初始設(shè)置的節(jié)奏去執(zhí)行。當(dāng)設(shè)置tolerance之后在允許范圍內(nèi)的延遲可以觸發(fā)事件抛计,超過的則不觸發(fā)哄孤。關(guān)于tolerance的設(shè)置,蘋果有這么一段介紹:

As the user of the timer, you will have the best idea of what an appropriate tolerance for a timer may be. A general rule of thumb, though, is to set the tolerance to at least 10% of the interval, for a repeating timer. Even a small amount of tolerance will have a significant positive impact on the power usage of your application. The system may put a maximum value of the tolerance.

翻譯成人話就是蘋果給了你一個設(shè)置tolerance的參考值吹截,就是timeInterval的十分之一瘦陈。

valid,只讀屬性波俄,獲取當(dāng)前timer是否有效晨逝。

userInfo,用戶參數(shù)懦铺,在初始化的時候傳入的用戶參數(shù)捉貌。

說到這里其實NSTimer也就基本介紹完成了,不過老司機還是想著重講一下NSTimer。

關(guān)于fire方法

You can use this method to fire a repeating timer without interrupting its regular firing schedule. If the timer is non-repeating, it is automatically invalidated after firing, even if its scheduled fire date has not arrived.

網(wǎng)上很多人對fire方法的解釋其實并不正確趁窃。fire并不是立即激活定時器牧挣,而是立即執(zhí)行一次定時器方法。當(dāng)加入到runloop中timer不需要激活即可按照設(shè)定的時間觸發(fā)事件醒陆。fire只是相當(dāng)于手動讓timer觸發(fā)一次事件瀑构。如果timer設(shè)置的repeat為NO,則fire之后timer立即銷毀刨摩。如果timer的repeat為YES寺晌,則到了之前設(shè)置的時間他依舊會按部就班的觸發(fā)事件。fire只是單獨觸發(fā)了一次事件澡刹,并不影響原timer的節(jié)奏折剃。

fire

如上圖,默認(rèn)情況且像屋,根據(jù)我寫的代碼怕犁,timerB是不會執(zhí)行的,應(yīng)為當(dāng)前mode并不正確(后面會說)己莺。但是當(dāng)我點擊button也就是執(zhí)行fire方法時奏甫,我們看到timerB響應(yīng)了事件。

關(guān)于invalid方法

我們知道NSTimer使用的時候如果不注意的話凌受,是會造成內(nèi)存泄漏的阵子。原因是我們生成實例的時候,會對控制器retain一下胜蛉。如果不對其進行管理則VC的永遠(yuǎn)不會引用計數(shù)為零挠进,進而造成內(nèi)存泄漏。

所以誊册,當(dāng)我們不需要的timer的時候领突,請如下操作:

[self.timer invalid];

self.timer = nil;

這樣Timer會對VC進行一次release。所以一定不要忘記調(diào)用invalid方法案怯。

順便提一句君旦,如果生成timer實例的時候repeat為NO,那當(dāng)觸發(fā)事件結(jié)束后嘲碱,系統(tǒng)也會自動調(diào)用invalid一次金砍。

關(guān)于runloop

有時我們將timer添加到runloop中,而依舊不觸發(fā)事件麦锯。這時候我們應(yīng)該考慮我們添加到的runloop是否是活躍的runloop恕稠。只有成為活躍的runloop,才會執(zhí)行runloop中的資源扶欣。

非活躍runloop

關(guān)于mode

即使是目標(biāo)runloop為活躍runloop依然可能不執(zhí)行鹅巍,這時候就要考慮目標(biāo)runloop是否處于我們指定的mode千扶。如果不是我們指定的mode,依然不會執(zhí)行我們的方法昆著。

非指定runloopMode

我們看到县貌,我將timerB加入到UITrackingRunLoopMode模式中术陶,默認(rèn)我們的timerB是不會執(zhí)行的凑懂。因為默認(rèn)情況下runloop是處于NSDefaultRunLoopMode中的。當(dāng)scrollView及其子類滾動的時候梧宫,runloop會自動切換為追蹤模式(UITrackingRunLoopMode)接谨。這是我們的計時器就會工作了。

切換為正確的Mode

那我們來說一下runloop的幾種mode:

Default模式

定義:NSDefaultRunLoopMode(Cocoa) kCFRunLoopDefaultMode (Core Foundation)

描述:默認(rèn)模式中幾乎包含了所有輸入源(NSConnection除外),一般情況下應(yīng)使用此模式塘匣。

Connection模式

定義:NSConnectionReplyMode(Cocoa)

描述:處理NSConnection對象相關(guān)事件脓豪,系統(tǒng)內(nèi)部使用,用戶基本不會使用忌卤。

Modal模式

定義:NSModalPanelRunLoopMode(Cocoa)

描述:處理modal panels事件扫夜。

Event tracking模式

定義:UITrackingRunLoopMode(iOS)

NSEventTrackingRunLoopMode(cocoa)

描述:在拖動loop或其他user interface tracking loops時處于此種模式下,在此模式下會限制輸入事件的處理驰徊。例如笤闯,當(dāng)手指按住UITableView拖動時就會處于此模式。

Common模式

定義:NSRunLoopCommonModes(Cocoa) kCFRunLoopCommonModes (Core Foundation)

描述:這是一個偽模式棍厂,其為一組run loop mode的集合颗味,將輸入源加入此模式意味著在Common Modes中包含的所有模式下都可以處理。在Cocoa應(yīng)用程序中牺弹,默認(rèn)情況下Common Modes包含default modes,modal modes,event Tracking modes.可使用CFRunLoopAddCommonMode方法想Common Modes中添加自定義modes浦马。

注:iOS中僅NSDefaultRunLoopMode,UITrackingRunLoopMode张漂,NSRunLoopCommonModes三種可用mode晶默。

你們知道蘋果手機為什么崛起的這么快么?第一是因為他是諾基亞年代唯一能與塞班并肩的智能系統(tǒng)(畢竟當(dāng)時用黑莓的很少)航攒,當(dāng)時還沒有安卓荤胁。第二就是他的流暢的UI。

為什么他可以做到UI如德芙一樣縱享絲滑呢屎债?因為它賦予了UI極高的地位仅政。全局僅有一條主線程,用來刷新UI盆驹。需要不斷重繪的scrollView及其子類圆丹,享有一個專用的runloopMode,UITrackingRunLoopMode躯喇。當(dāng)scrollView發(fā)生滾動時辫封,當(dāng)前runloop會切換為UITrackingRunLoopMode硝枉。所以正如上面提到過的,如果你的定時器加到NSDefaultRunLoopMode中那么滾動的時候倦微,計時器動作就停止了妻味。這時,你需要將timer加載NSRunLoopCommonModes中欣福,才能保證滾動與停止時你的timer都會觸發(fā)事件责球。這個對于你的輪播圖可是很有用的哦。

NSTimer的優(yōu)勢:使用相對靈活拓劝,應(yīng)用廣泛

劣勢:受runloop影響嚴(yán)重雏逾,同時易造成內(nèi)存泄漏(調(diào)用invalid方法解決)

GCD中的timer——dispatch_source_t

其實說dispatch_source_t是timer這樣是狹隘的。dispatch_source_t是GCD為我們預(yù)留的源類型對象郑临。

GCD方法眾多栖博,而且各種牛逼的應(yīng)用,老司機也并不能玩轉(zhuǎn)GCD厢洞,所以這里還是主要講解一下GCD中Timer的用法吧仇让。

self.timerInG = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());

? ? ? ? ? ? dispatch_source_set_timer(self.timerInG,? dispatch_walltime(NULL,0 * NSEC_PER_SEC), 0.032 * NSEC_PER_SEC, 0);

? ? ? ? ? ? dispatch_source_set_event_handler(self.timerInG, ^{

? ? ? ? ? ? ? ? [self changeImg];

? ? ? ? ? ? });

dispatch_source_create(,,,)

這個方法用于返回一個dispatch_source_t對象。第一個參數(shù)為源類型躺翻,最后一個參數(shù)為資源要加入的隊列丧叽。

dispatch_source_set_timer(,,,)

這個方法用來設(shè)置我們timer的相關(guān)信息。第一個參數(shù)是我們的timer對象获枝,第二個是timer事件首次觸發(fā)的延遲時間蠢正,第三個參數(shù)是timer時間觸發(fā)的時間間隔,最后一個參數(shù)是timer觸發(fā)的允許延遲值省店。類比NSTimer的tolerance嚣崭。建議值也是十分之一。

dispatch_source_set_event_handler(,)

這個方法用來設(shè)置timer的觸發(fā)事件懦傍。第一個參數(shù)為Timer對象雹舀,第二個為回調(diào)block。

dispatch_resume()

用來激活源對象

dispatch_suspend()

用來暫停源對象

dispatch_source_cancel()

用來銷毀定時器粗俱。

另外需要注意的是说榆,dispatch_source_t? 一定要被設(shè)置為成員變量,否則將會立即被釋放寸认。

關(guān)于GCD的timer使用起來相對簡單签财,不過,其實操作不當(dāng)?shù)脑捯矔斐蓛?nèi)存泄漏偏塞!

處于掛起(也就是掉用過 dispatch_suspend())的源是不能釋放的唱蒸。這樣就會造成內(nèi)存泄漏。

所以建議控制器添加一個標(biāo)識符灸叼,記錄源是否處于掛起狀態(tài)神汹,在dealloc事件中判斷當(dāng)前源是否被掛起庆捺,如果被掛起,則resume屁魏,即可解決內(nèi)存泄漏問題滔以。同時如果某個源掛起后不需要恢復(fù)則直接調(diào)用dispatch_source_cancel銷毀就好。

GCDTimer的優(yōu)勢:不受當(dāng)前runloopMode的影響氓拼。

劣勢:雖然說不受runloopMode的影響你画,但是其計時效應(yīng)仍不是百分之百準(zhǔn)確的。另外披诗,他的觸發(fā)事件也有可能被阻塞撬即,當(dāng)GCD內(nèi)部管理的所有線程都被占用時立磁,其觸發(fā)事件將被延遲呈队。

作者:老司機Wicky

鏈接:http://www.reibang.com/p/434ec6911148

來源:簡書

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市唱歧,隨后出現(xiàn)的幾起案子宪摧,更是在濱河造成了極大的恐慌,老刑警劉巖颅崩,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件几于,死亡現(xiàn)場離奇詭異,居然都是意外死亡沿后,警方通過查閱死者的電腦和手機沿彭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來尖滚,“玉大人喉刘,你說我怎么就攤上這事∑崤” “怎么了睦裳?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長撼唾。 經(jīng)常有香客問我廉邑,道長,這世上最難降的妖魔是什么倒谷? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任蛛蒙,我火速辦了婚禮,結(jié)果婚禮上渤愁,老公的妹妹穿的比我還像新娘牵祟。我一直安慰自己,他們只是感情好猴伶,可當(dāng)我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布课舍。 她就那樣靜靜地躺著塌西,像睡著了一般。 火紅的嫁衣襯著肌膚如雪筝尾。 梳的紋絲不亂的頭發(fā)上捡需,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天,我揣著相機與錄音筹淫,去河邊找鬼站辉。 笑死,一個胖子當(dāng)著我的面吹牛损姜,可吹牛的內(nèi)容都是我干的饰剥。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼摧阅,長吁一口氣:“原來是場噩夢啊……” “哼汰蓉!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起棒卷,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤顾孽,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后比规,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體若厚,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年蜒什,在試婚紗的時候發(fā)現(xiàn)自己被綠了测秸。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡灾常,死狀恐怖霎冯,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情岗憋,我是刑警寧澤肃晚,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站仔戈,受9級特大地震影響关串,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜监徘,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一晋修、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧凰盔,春花似錦墓卦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽睁本。三九已至,卻和暖如春忠怖,著一層夾襖步出監(jiān)牢的瞬間呢堰,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工凡泣, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留枉疼,地道東北人。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓鞋拟,卻偏偏與公主長得像骂维,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子贺纲,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,033評論 2 355

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