不知道你們有沒有想過這個問題,一個應用開始執行之後放在那裏,假設不正確它進行不論什麼操做。這個應用就像精巧了同樣,不會自發的有不論什麼動做發生。但是假設咱們點擊界面上的一個button。這個時候就會有對應的button響應事件發生。給咱們的感受就像應用一直處於隨時待命的狀態。在沒人操做的時候它一直在歇息,在讓它幹活的時候,它就能馬上響應。事實上,這就是run loop的功勞。css
1.1 線程任務的類型
再來講說線程。有些線程執行的任務是一條直線,起點到終點;而還有一些線程要乾的活則是一個圓。不斷循環,直到經過某種方式將它終止。html
直線線程如簡單的Hello World,執行打印完,它的生命週期便結束了。像曇花一現那樣;圓類型的如操做系統,一直執行直到你關機。在IOS中,圓型的線程就是經過run loop不停的循環實現的。編程
1.2 線程與run loop的關係
Run loop,正如其名,loop表示某種循環,和run放在一塊兒就表示一直在執行着的循環。實際上,run loop和線程是緊密相連的,可以這樣說run loop是爲了線程而生,沒有線程。它就沒有存在的必要。Run loops是線程的基礎架構部分,Cocoa和CoreFundation都提供了run loop對象方便配置和管理線程的run loop(如下都已Cocoa爲例)。xcode
每一個線程。包含程序的主線程(main thread)都有與之對應的run loop對象。安全
1.2.1 主線程的run loop默認是啓動的。markdown
iOS的應用程序裏面,程序啓動後會有一個例如如下的main() 函數:架構
int main(int argc, char *argv[])
{
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([appDelegate class]));
}
}
重點是UIApplicationMain() 函數,這種方法會爲main thread 設置一個NSRunLoop 對象,這就解釋了本文開始說的爲何咱們的應用可以在無人操做的時候歇息,需要讓它幹活的時候又能馬上響應。app
1.3 關於run loop的幾點說明
1.3.1 Cocoa中的NSRunLoop類並不是線程安全的異步
咱們不能再一個線程中去操做另一個線程的run loop對象。那很是可能會形成意想不到的後果。函數
只是幸運的是CoreFundation中的不透明類CFRunLoopRef是線程安全的,而且兩種類型的run loop全然可以混合使用。Cocoa中的NSRunLoop類可以經過實例方法:
獲取對應的CFRunLoopRef類。來達到線程安全的目的。
1.3.2 Run loop的管理並不全然是本身主動的。
咱們仍必須設計線程代碼以在適當的時候啓動run loop並正確響應輸入事件,固然前提是線程中需要用到run loop。而且,咱們還需要使用while/for語句來驅動run loop可以循環執行。如下的代碼就成功驅動了一個run loop:
BOOL isRunning = NO;
do {
isRunning = [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDatedistantFuture]];
} while (isRunning);
1.3.3 Run loop同一時候也負責autorelease pool的建立和釋放
在使用手動的內存管理方式的項目中,會經常常使用到很是多本身主動釋放的對象,假設這些對象不可以被即時釋放掉,會形成內存佔用量急劇增大。Run loop就爲咱們作了這種工做,每當一個執行循環結束的時候,它都會釋放一次autorelease pool,同一時候pool中的所有本身主動釋放類型變量都會被釋放掉。
1.3.4 Run loop的長處
一個run loop就是一個事件處理循環,用來不停的監聽和處理輸入事件並將其分配到對應的目標上進行處理。假設僅僅是想實現這個功能,你可能會想一個簡單的while循環不就可以實現了嗎,用得着費老大勁來作個那麼複雜的機制?顯然,蘋果的架構設計師不是吃乾飯的,你想到的他們早就想過了。
首先,NSRunLoop是一種更加高明的消息處理模式,他就高明在對消息處理過程進行了更好的抽象和封裝,這樣才幹是的你不用處理一些很是瑣碎很是低層次的詳細消息的處理,在NSRunLoop中每一個消息就被打包在input source或者是timer source(見後文)中了。
其次,也是很是重要的一點。使用run loop可以使你的線程在有工做的時候工做,沒有工做的時候休眠,這可以大大節省系統資源。
2.1輸入事件來源
Run loop接收輸入事件來自兩種不一樣的來源:輸入源(input source)和定時源(timer source)。兩種源都使用程序的某一特定的處理例程來處理到達的事件。圖-1顯示了run loop的概念結構以及各類源。
需要說明的是。當你建立輸入源。你需要將其分配給run loop中的一個或多個模式(什麼是模式,下文將會講到)。模式僅僅會在特定事件影響監聽的源。
大多數狀況下,run loop執行在默認模式下。但是你也可以使其執行在本身定義模式。若某一源在當前模式下不被監聽,那麼不論什麼其生成的消息僅僅在run loop執行在其關聯的模式下才會被傳遞。
圖-1 Runloop的結構和輸入源類型
2.1.1輸入源(input source)
傳遞異步事件,一般消息來自於其它線程或程序。輸入源傳遞異步消息給對應的處理例程,並調用runUntilDate:方法來退出(在線程裏面相關的NSRunLoop對象調用)。
2.1.1.1基於port的輸入源
基於port的輸入源由內核本身主動發送。
Cocoa和Core Foundation內置支持使用port相關的對象和函數來建立的基於port的源。
好比,在Cocoa裏面你歷來不需要直接建立輸入源。你僅僅要簡單的建立port對象,並使用NSPort的方法把該port加入到run loop。port對象會本身處理建立和配置輸入源。
在Core Foundation,你必須人工建立port和它的run loop源。咱們可以使用port相關的函數(CFMachPortRef,CFMessagePortRef。CFSocketRef)來建立合適的對象。
如下的樣例展現了怎樣建立一個基於port的輸入源,將其加入到run loop並啓動:
voidcreatePortSource()
{
CFMessagePortRef port = CFMessagePortCreateLocal(kCFAllocatorDefault, CFSTR("com.someport"),myCallbackFunc, NULL, NULL);
CFRunLoopSourceRef source = CFMessagePortCreateRunLoopSource(kCFAllocatorDefault, port, 0);
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
while (pageStillLoading) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
CFRunLoopRun();
[pool release];
}
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
CFRelease(source);
}
2.1.1.2本身定義輸入源
本身定義的輸入源需要人工從其它線程發送。
爲了建立本身定義輸入源,必須使用Core Foundation裏面的CFRunLoopSourceRef類型相關的函數來建立。
你可以使用回調函數來配置本身定義輸入源。Core Fundation會在配置源的不一樣地方調用回調函數,處理輸入事件。在源從run loop移除的時候清理它。
除了定義在事件到達時本身定義輸入源的行爲,你也必須定義消息傳遞機制。源的這部分執行在單獨的線程裏面。並負責在數據等待處理的時候傳遞數據給源並通知它處理數據。消息傳遞機制的定義取決於你。但最好不要過於複雜。建立並啓動本身定義輸入源的示比例如如下:
voidcreateCustomSource()
{
CFRunLoopSourceContext context = {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
while (pageStillLoading) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
CFRunLoopRun();
[pool release];
}
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
CFRelease(source);
}
2.1.1.3Cocoa上的Selector源
除了基於port的源。Cocoa定義了本身定義輸入源,贊成你在不論什麼線程執行selector方法。和基於port的源同樣。執行selector請求會在目標線程上序列化。減緩不少在線程上贊成多個方法easy引發的同步問題。
不像基於port的源。一個selector執行完後會本身主動從run loop裏面移除。
當在其它線程上面執行selector時,目標線程須有一個活動的run loop。對於你建立的線程。這意味着線程在你顯式的啓動run loop以前是不會執行selector方法的。而是一直處於休眠狀態。
NSObject類提供了類似例如如下的selector方法:
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)argwaitUntilDone:(BOOL)wait modes:(NSArray *)array;
2.1.2定時源(timer source)
定時源在預設的時間點同步方式傳遞消息。這些消息都會發生在特定時間或者反覆的時間間隔。定時源則直接傳遞消息給處理例程,不會立刻退出run loop。
需要注意的是,雖然定時器可以產生基於時間的通知,但它並不是實時機制。和輸入源同樣,定時器也和你的run loop的特定模式相關。
假設定時器所在的模式當前未被run loop監視,那麼定時器將不會開始直到run loop執行在對應的模式下。類似的,假設定時器在run loop處理某一事件期間開始。定時器會一直等待直到下次run loop開始對應的處理程序。
假設run loop再也不執行,那定時器也將永遠不啓動。
建立定時器源有兩種方法。
方法一:
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:4.0
target:self
selector:@selector(backgroundThreadFire:) userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timerforMode:NSDefaultRunLoopMode];
方法二:
[NSTimer scheduledTimerWithTimeInterval:10
target:self
selector:@selector(backgroundThreadFire:)
userInfo:nil
repeats:YES];
2.2 RunLoop觀察者
源是在合適的同步或異步事件發生時觸發,而run loop觀察者則是在run loop自己執行的特定時候觸發。
你可以使用run loop觀察者來爲處理某一特定事件或是進入休眠的線程作準備。
你可以將run loop觀察者和如下事件關聯:
1. Runloop入口
2. Runloop什麼時候處理一個定時器
3. Runloop什麼時候處理一個輸入源
4. Runloop什麼時候進入睡眠狀態
5. Runloop什麼時候被喚醒,但在喚醒以前要處理的事件
6. Runloop終止
和定時器類似,在建立的時候你可以指定run loop觀察者可以僅僅用一次或循環使用。若僅僅用一次。那麼在它啓動後。會把它本身從run loop裏面移除,而循環的觀察者則不會。定義觀察者並把它加入到run loop。僅僅能使用Core Fundation。
如下的樣例演示了怎樣建立run loop的觀察者:
- (void)addObserverToCurrentRunloop
{
// The application uses garbage collection, so noautorelease pool is needed.
NSRunLoop*myRunLoop = [NSRunLoop currentRunLoop];
// Create a run loop observer and attach it to the runloop.
CFRunLoopObserverContext context = {0, self, NULL, NULL, NULL};
CFRunLoopObserverRef observer =CFRunLoopObserverCreate(kCFAllocatorDefault,
kCFRunLoopBeforeTimers, YES, 0, &myRunLoopObserver, &context);
if (observer)
{
CFRunLoopRef cfLoop = [myRunLoop getCFRunLoop];
CFRunLoopAddObserver(cfLoop, observer, kCFRunLoopDefaultMode);
}
}
當中。kCFRunLoopBeforeTimers表示選擇監聽定時器觸發前處理事件。後面的YES表示循環監聽。
2.3 RunLoop的事件隊列
每次執行run loop。你線程的run loop對會本身主動處理以前未處理的消息,並通知相關的觀察者。詳細的順序例如如下:
通知觀察者run loop已經啓動
通知觀察者不論什麼即將要開始的定時器
通知觀察者不論什麼即將啓動的非基於port的源
啓動不論什麼準備好的非基於port的源
假設基於port的源準備好並處於等待狀態,立刻啓動;並進入步驟9。
通知觀察者線程進入休眠
將線程置於休眠直到任一如下的事件發生:
某一事件到達基於port的源
定時器啓動
Run loop設置的時間已經超時
run loop被顯式喚醒
通知觀察者線程將被喚醒。
處理未處理的事件
假設用戶定義的定時器啓動,處理定時器事件並從新啓動run loop。
進入步驟2
假設輸入源啓動,傳遞對應的消息
假設run loop被顯式喚醒而且時間還沒超時,從新啓動run loop。進入步驟2
通知觀察者run loop結束。
因爲定時器和輸入源的觀察者是在對應的事件發生以前傳遞消息,因此通知的時間和實際事件發生的時間之間可能存在偏差。假設需要精確時間控制。你可以使用休眠和喚醒通知來幫助你校對實際發生事件的時間。
因爲當你執行run loop時定時器和其它週期性事件經常需要被傳遞,撤銷run loop也會終止消息傳遞。
典型的樣例就是鼠標路徑追蹤。
因爲你的代碼直接獲取到消息而不是經由程序傳遞,所以活躍的定時器不會開始直到鼠標追蹤結束並將控制權交給程序。
Run loop可以由run loop對象顯式喚醒。其它消息也可以喚醒run loop。
好比,加入新的非基於port的源會喚醒run loop從而可以立刻處理輸入源而不需要等待其它事件發生後再處理。
從這個事件隊列中可以看出:
①假設是事件到達,消息會被傳遞給對應的處理程序來處理, runloop處理完當次事件後,run loop會退出。而不管以前預約的時間到了沒有。你可以又一次啓動run loop來等待下一事件。
②假設線程中有需要處理的源,但是響應的事件沒有到來的時候,線程就會休眠等待對應事件的發生。這就是爲何run loop可以作到讓線程有工做的時候忙於工做。而沒工做的時候處於休眠狀態。
2.4何時使用run loop
僅當在爲你的程序建立輔助線程的時候,你才需要顯式執行一個run loop。Run loop是程序主線程基礎設施的關鍵部分。因此,Cocoa和Carbon程序提供了代碼執行主程序的循環並本身主動啓動run loop。IOS程序中UIApplication的run方法(或Mac OS X中的NSApplication)做爲程序啓動步驟的一部分,它在程序正常啓動的時候就會啓動程序的主循環。類似的。RunApplicationEventLoop函數爲Carbon程序啓動主循環。假設你使用xcode提供的模板建立你的程序,那你永遠不需要本身去顯式的調用這些例程。
對於輔助線程。你需要推斷一個run loop是不是必須的。假設是必須的。那麼你要本身配置並啓動它。
你不需要在不論什麼狀況下都去啓動一個線程的run loop。
比方,你使用線程來處理一個預先定義的長時間執行的任務時。你應該避免啓動run loop。
Run loop在你要和線程有不少其它的交互時才需要。比方如下狀況:
使用port或本身定義輸入源來和其它線程通訊
使用線程的定時器
Cocoa中使用不論什麼performSelector…的方法
使線程週期性工做
假設你決定在程序中使用run loop。那麼它的配置和啓動都很是easy。
和所有線程編程同樣。你需要計劃好在輔助線程退出線程的情形。
讓線程天然退出每每比強制關閉它更好。
學習參考連接: