這篇文章會涉及到什么呢?
CADisplayLink的基本使用方法
OC中的三種定時器:CADisplayLink恰起、NSTimer、GCD
runloop淺析
點進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的使用還是比較簡單的。
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
來源:簡書