從定時器到RunLoop

ios 常用的定時器有三種:NSTime,CADisplayLink和GCD。

NsTimer


// 參數(shù):Interval:時間間隔? userInfo:攜帶信息,用來傳值? ? repeats:是否循環(huán)

//第一種,使用 timerWithTimeInterval:target:selector:userInfo:repeats: 方法

NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(doAnything) userInfo:nil repeats:YES];

[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];

//第二種,使用 scheduledTimerWithTimeInterval:target:selector:userInfo:repeats: 方法

[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(doSomething:) userInfo:@5 repeats:NO];


- (void)doSomething:(NSTimer *)timer{

NSString * str = [timer userInfo];

NSLog(@"%@",str);

}

- (void)doAnything{

NSLog(@"doAnything...");

}


//用第一種方式創(chuàng)建,必須手動將timer加入RunLoop,否則timer的事件是不會響應(yīng)的,其中 NSDefaultRunLoopMode 代表RunLoop的模式;iOS對外開放的RunLoop Mode有兩個;

//? FOUNDATION_EXPORT NSRunLoopMode const NSDefaultRunLoopMode;默認(rèn)的運行模式,用于大部分操作

// FOUNDATION_EXPORT NSRunLoopMode const NSRunLoopCommonModes NS_AVAILABLE(10_5, 2_0);//是一個模式集合,當(dāng)綁定一個事件源到這個模式集合的時候就相當(dāng)于綁定到了集合內(nèi)的每一個模式。用這種模式,即可以處理頁面類似于tableview和scrollerview的滑動事件 ,也可以同時保證定時器的運行

用第二種方法則默認(rèn)加入RunLoop,且默認(rèn)模式為NSDefaultRunLoopMode

// 取消、停止、開始 定時器

//關(guān)閉定時器

[myTimer setFireDate:[NSDate distantFuture]];

//開啟定時器

[myTimer setFireDate:[NSDate distantPast]];

//取消定時器(用來取消循環(huán)執(zhí)行的定時器,否則可以省略。取消之后要釋放。將計數(shù)器的repeats設(shè)置為YES的時候,self的引用計數(shù)會加1。)

[timer invalidate];

timer = nil;


CADisplayLink


CADisplayLink是一個和屏幕刷新率同步的定時器類。CADisplayLink以特定模式注冊到runloop后,每當(dāng)屏幕顯示內(nèi)容刷新結(jié)束的時候,runloop就會向CADisplayLink指定的target發(fā)送一次指定的selector消息,CADisplayLink類對應(yīng)的selector就會被調(diào)用一次,所以可以使用CADisplayLink做一些和屏幕操作相關(guān)的操作。相比于NSTimer,CADisplayLink使用場合相對專一,適合做UI的不停重繪,比如自定義動畫引擎或者視頻播放的渲染。NSTimer的使用范圍要廣泛的多,各種需要單次或者循環(huán)定時處理的任務(wù)都可以使用。但是因為iOS設(shè)備的屏幕刷新頻率是固定的,CADisplayLink在正常情況下會在每次刷新結(jié)束都被調(diào)用,精確度相當(dāng)高。NSTimer則會因為現(xiàn)成阻塞,產(chǎn)生誤差

// 創(chuàng)建displayLink

CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(change)];

// 將創(chuàng)建的displaylink添加到runloop中,否則定時器不會執(zhí)行

[displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];

// 停止定時器

[displayLink invalidate];

displayLink = nil;


- (void)change {

_imageView.transform = CGAffineTransformRotate(_imageView.transform, M_PI / 100);

}


GCD定時器(精度很高,不受RunLoop的Mode影響)


一次性定時

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]);

});

循環(huán)定時器

@property (nonatomic ,strong)dispatch_source_t timer;//? 注意:此處應(yīng)該使用強引用 strong

{

NSTimeInterval period = 1.0; //設(shè)置時間間隔

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);

dispatch_source_set_timer(_timer, dispatch_walltime(NULL, 0), period * NSEC_PER_SEC, 0); //每秒執(zhí)行

//要調(diào)用的任務(wù)

dispatch_source_set_event_handler(_timer, ^{ //在這里執(zhí)行事件

[self ?doAnything];

});

//.開始執(zhí)行

dispatch_resume(_timer);

//釋放

self.timer = nil;

}

GCD定時器時間非常精準(zhǔn),最小的定時時間可以達(dá)到1納秒,所以用在非常精確的定時場合。


RunLoop




RunLoop是線程相關(guān)的的基礎(chǔ)框架的一部分。一個run loop就是一個事件處理的循環(huán),用來不停的調(diào)度工作以及處理輸入事件。其實內(nèi)部就是do-while循環(huán),這個循環(huán)內(nèi)部不斷地處理各種任務(wù)(比 如Source,Timer,Observer)。使用run loop的目的是讓你的線程在有工作的時候忙于工作,而沒工作的時候處于休眠狀態(tài)。

RunLoop是基于線程而存在的,每條線程都有唯一的一個與之對應(yīng)的RunLoop對象,主線程的RunLoop已經(jīng)自動創(chuàng)建和啟動,子線程的RunLoop需要主動創(chuàng)建、調(diào)用run方法啟動。RunLoop在第一次獲取時創(chuàng)建,在線程結(jié)束時銷毀

Core Foundation中包含了關(guān)于RunLoop的5個類:

CFRunLoopRef

CFRunLoopModeRef

CFRunLoopModeRef 類并沒有對外暴露,只是通過 CFRunLoopRef的接口進行了封裝,代表RunLoop的運行模式

一個 RunLoop 包含若干個 Mode,每個 Mode 又包含若干個 Source/Timer/Observer。

系統(tǒng)默認(rèn)注冊了5個Mode:

NSDefaultRunLoopMode:App的默認(rèn)Mode,通常主線程是在這個Mode下運行

UITrackingRunLoopMode:界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其他 Mode 影響

UIInitializationRunLoopMode: 在剛啟動 App 時第進入的第一個 Mode,啟動完成后就不再使用

GSEventReceiveRunLoopMode: 接受系統(tǒng)事件的內(nèi)部 Mode,通常用不到

NSRunLoopCommonModes: 這是一個占位用的Mode,不是一種真正的Mode

CFRunLoopModeRef 類并沒有對外暴露,蘋果對外暴露的Mode也只有兩個;NSDefaultRunLoopMode和NSRunLoopCommonModes,NSRunLoopCommonModes更像是一個模式集合。一個 Mode 可以將自己標(biāo)記為"Common"屬性(通過將其 ModeName 添加到 RunLoop 的 "commonModes" 中)。每當(dāng) RunLoop 的內(nèi)容發(fā)生變化時,RunLoop 都會自動將 _commonModeItems 里的 Source/Observer/Timer 同步到具有 "Common" 標(biāo)記的所有Mode里。


假如存在這樣一個頁面,tableview的headerView是一個輪播圖,當(dāng)tableview被拖動時,輪播圖自動播放圖片的定時器失效。

通常添加定時器到RunLoop里面,如果選擇的Mode為NSDefaultRunLoopMode,那么當(dāng)tableview拖動時,便會郵NSDefaultRunLoopMode 切換為UITrackingRunLoopMode,如果需要切換 Mode,只能退出 Loop,再重新指定一個 Mode 進入,所以在滑動的時候定時器失效;這樣做主要是為了分隔開不同組的 Source/Timer/Observer,讓其互不影響,所以如果把在[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];的Mode換成NSRunLoopCommonModes,在定時器滑動時就不會對定時器產(chǎn)生影響;NSRunLoopCommonModes更像是是一個模式集合,當(dāng)綁定一個事件源到這個模式集合的時候就相當(dāng)于綁定到了集合內(nèi)的每一個模式。(每當(dāng) RunLoop 的內(nèi)容發(fā)生變化時,RunLoop 都會自動將 _commonModeItems 里的 Source/Observer/Timer 同步到具有 "Common" 標(biāo)記的所有Mode里)

CFRunLoopSourceRef

CFRunLoopSourceRef 是事件產(chǎn)生的地方。Source有兩個版本:Source0 和 Source1。

Source0 非基于Port的

,只包含了一個回調(diào)(函數(shù)指針),它并不能主動觸發(fā)事件。使用時,你需要先調(diào)用 CFRunLoopSourceSignal(source),將這個 Source 標(biāo)記為待處理,然后手動調(diào)用 CFRunLoopWakeUp(runloop) 來喚醒 RunLoop,讓其處理這個事件。

基于Port的,通過內(nèi)核和其他線程通信,接收、分發(fā)系統(tǒng)事件

CFRunLoopTimerRef

CFRunLoopTimerRef是基于時間的觸發(fā)器,CFRunLoopTimerRef基本上說的就是NSTimer,它受RunLoop的Mode影響,GCD的定時器不受RunLoop的Mode影響

CFRunLoopObserverRef

CFRunLoopObserverRef是觀察者,能夠監(jiān)聽RunLoop的狀態(tài)改變


runloop應(yīng)用場景除了定時器,還有AutoreleasePool、PerformSelecter,手勢識別,界面更行,事件處理等等;

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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