Runloop 是 iOS 和 OSX 中和線程相關(guān)的基礎(chǔ)概念蹂季。提供了線程循環(huán)處理事件的能力,當(dāng)有事件處理時喚醒線程工作,空閑時使線程進(jìn)入休眠高氮,以合理的利用系統(tǒng)資源慧妄。
Runloop 是線程相關(guān)的概念,和線程是一一對應(yīng)的剪芍。一個線程對應(yīng)一個runloop塞淹,默認(rèn)情況下,主線程的 runloop 是啟動的罪裹,后臺線程的 runloop 需要自己啟動饱普。
Runloop 的工作模型類似 windows 中的消息機(jī)制(Event loop),接受事件(消息)->處理->等待新事件(消息)坊谁,偽代碼如下:
loop()
{
init();
while(get_message(&msg) != quit){
handle_message(msg);
}
}
Runloop 中需要處理的事件分2類:
- 輸入源(input source):分發(fā)異步事件费彼,通常都是來自其他線程或者Application。
- performSelector源
- 基于端口的源(mach port)
- 自定義輸入源
- 計時器:定時的或者一定間隔的觸發(fā)同步事件口芍。
Runloop 的運(yùn)行結(jié)構(gòu)圖如下圖:
Runloop 其實是一個對象箍铲,提供了一個入口函數(shù)來執(zhí)行上面的事件循環(huán),在函數(shù)內(nèi)部實現(xiàn)了接受消息->處理->等待新消息
這樣的一個循環(huán)流程鬓椭,直到循環(huán)結(jié)束颠猴。
Runloop 運(yùn)行中會發(fā)出許多通知,比如狀態(tài)的改變小染,可以監(jiān)聽這些通知作對應(yīng)的處理翘瓮。
iOS 和 OSX 中提供了2個這樣的對象:NSRunloop和CFRunLoopRef。
內(nèi)部邏輯大致如下圖:
運(yùn)行模式
運(yùn)行模式定義了一個集合裤翩,當(dāng) Runloop 運(yùn)行在某個模式下時资盅,只會監(jiān)聽某些類型的輸入源事件和計時器事件,以及分發(fā)某些特定種類的通知踊赠,啟動 Runloop 時會指定一種模式呵扛。
常見的一些運(yùn)行模式:
- NSDefaultRunLoopMode:默認(rèn)模式,包括了大部分的操作事件筐带,絕大部份情況下都應(yīng)該使用這個模式配置自己的 runloop
- NSEventTrackingRunLoopMode:典型的當(dāng) scrollView 滾動的時候是運(yùn)行在該模式下今穿,此時的不接受Timer事件,所以Timer會失效
- NSRunLoopCommonModes:模式組伦籍,一個模式將自己標(biāo)記為“common”屬性后就會在該組中蓝晒,運(yùn)行在該模式下,能處理的事件就是組中各個模式能處理事件的合集帖鸦,在默認(rèn)情況下芝薇,NSDefaultRunLoopMode 和NSEventTrackingRunLoopMode 被標(biāo)記為 “common” 屬性
輸入源
- 基于端口的源:監(jiān)聽Mach port,cocoa 提供了內(nèi)置的類來實現(xiàn)這類輸入源 NSPort (IPC相關(guān))
- 自定義輸入源:
- PerformSelector源:主要是那些和時間能扯上關(guān)系的API作儿,比如:(其實是通過timer實現(xiàn)的)
- performSelectorOnMainThread:withObject:waitUntilDone:
- performSelector:withObject:afterDelay:
- performSelector:onThread:withObject:waitUntilDone:
Runloop 的使用
- 使用端口或者自定義輸入源來實現(xiàn)線程間的通信
- 在線程中使用計時器
- 使用performSelector方法
- 保持線程執(zhí)行周期性的任務(wù)
Runloop 的應(yīng)用
- Autorelease pool:runloop 會自動維護(hù)autorelease pool的創(chuàng)建和釋放洛二,autorelease pool 會在下一次runloop循環(huán)之前釋放 pool 中的對象,所以在像for,while這樣的循環(huán)中灭红,如果需要大量創(chuàng)建臨時對象時需要自己創(chuàng)建autorelease pool侣滩,避免臨時內(nèi)存峰值過高,比如圖片相關(guān)的操作
- 事件響應(yīng)(觸摸/鎖屏/搖晃等):source1 事件
- 手勢識別:監(jiān)聽特定的通知变擒,處理手勢的回調(diào)
- 界面更新:監(jiān)聽特定的通知君珠,處理界面的刷新操作
- 定時器:NSTimer 和 CADisplayLink
- PerformSelector:AfterDelay:
- GCD:dispatch_async(dispatch_get_main_queue(), block)
附 Runloop 的部分源碼圖:
一個使用 Runloop 的簡單例子
- (void)threadMain
{
// The application uses garbage collection, so no autorelease pool is needed.
NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];
// Create a run loop observer and attach it to the run loop.
CFRunLoopObserverContext context = {0, self, NULL, NULL, NULL};
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
kCFRunLoopAllActivities, YES, 0, &myRunLoopObserver, &context);
if (observer)
{
CFRunLoopRef cfLoop = [myRunLoop getCFRunLoop];
CFRunLoopAddObserver(cfLoop, observer, kCFRunLoopDefaultMode);
}
// Create and schedule the timer.
[NSTimer scheduledTimerWithTimeInterval:0.1 target:self
selector:@selector(doFireTimer:) userInfo:nil repeats:YES];
NSInteger loopCount = 10;
do
{
// Run the run loop 10 times to let the timer fire.
[myRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
loopCount--;
}
while (loopCount);
}