轉(zhuǎn)自:http://blog.csdn.net/jjunjoe/article/details/8313016
一、Runloop簡(jiǎn)介:
Run loops 是線程相關(guān)的的基礎(chǔ)框架的一部分。一個(gè) run loop 就是一個(gè)事件處理 的循環(huán),用來(lái)不停的調(diào)度工作以及處理輸入事件。
使用 run loop的目的是讓你的線程在有工作的時(shí)候忙于工作,而沒工作的時(shí)候處于休眠狀態(tài)。
Runloop還可以在loop在循環(huán)中的同時(shí)響應(yīng)其他輸入源,比如界面控件的按鈕,手勢(shì)等。
Run loop 接收輸入事件來(lái)自兩種不同的來(lái)源:
輸入源(input source)和定時(shí)源 (timer source)。
輸入源傳遞異步事件,通常消息來(lái)自于其他線程或程序。輸入源的種類:基于端口的輸入源和自定義輸入源。
定時(shí)源則傳遞同步事件,發(fā)生在特定時(shí)間或者重復(fù)的時(shí)間間隔。
Run loop 模式是所有要監(jiān)視的輸入源和定時(shí)源以及要通知的 run loop 注冊(cè)觀察 者的集合。
可以將 Run loop 觀察者和以下事件關(guān)聯(lián):
Run loop 入口
Run loop 何時(shí)處理一個(gè)定時(shí)器
Run loop 何時(shí)處理一個(gè)輸入源
Run loop 何時(shí)進(jìn)入睡眠狀態(tài)
Run loop 何時(shí)被喚醒,但在喚醒之前要處理的事件
Run loop 終止
每次運(yùn)行 Run loop,你線程的 Run loop 對(duì)會(huì)自動(dòng)處理之前未處理的消息,并通知相關(guān)的觀察者。具體的順序如下:
1. 通知觀察者 Run loop 已經(jīng)啟動(dòng)。
2. 通知觀察者任何即將要開始的定時(shí)器。
3. 通知觀察者任何即將啟動(dòng)的非基于端口的源。
4. 啟動(dòng)任何準(zhǔn)備好的非基于端口的源。
5. 如果基于端口的源準(zhǔn)備好并處于等待狀態(tài),立即啟動(dòng);并進(jìn)入步驟 9。
6. 通知觀察者線程進(jìn)入休眠。
7. 將線程置于休眠直到任一下面的事件發(fā)生:
某一事件到達(dá)基于端口的源;
定時(shí)器啟動(dòng);
Run loop 設(shè)置的時(shí)間已經(jīng)超時(shí);
Run loop 被顯式喚醒。
8. 通知觀察者線程將被喚醒。
9. 處理未處理的事件
如果用戶定義的定時(shí)器啟動(dòng),處理定時(shí)器事件并重啟 Run loop。進(jìn)入步驟 2。
如果輸入源啟動(dòng),傳遞相應(yīng)的消息。
如果 Run loop 被顯式喚醒而且時(shí)間還沒超時(shí),重啟 Run loop,進(jìn)入步驟 2。
10. 通知觀察者 Run loop 結(jié)束。
Run loop 在你要和線程有更多的交互時(shí)才需要,比如以下情況:
使用端口或自定義輸入源來(lái)和其他線程通信;
使用線程的定時(shí)器;
Cocoa 中使用任何performSelector...的方法;
使線程周期性工作。
二、舉例說(shuō)明Runloop的優(yōu)點(diǎn)。
一般情況下,當(dāng)我們使用NSRunLoop的時(shí)候,代碼如下所示:
do {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopModebeforeDate:[NSDate distantFuture]];
} while (!done);
在上面的代碼中,參數(shù)done為NO的時(shí)候,當(dāng)前runloop會(huì)一直接收處理其他輸入源,處理輸入源之后會(huì)再回到runloop中等待其他的輸入源;除非done為NO,否則當(dāng)前流程一直再runloop中。
如下面的代碼片段所示,有三個(gè)按鈕,分別對(duì)應(yīng)如下三個(gè)action消息,buttonNormalThreadTestPressed,buttonRunloopPressed,buttonTestPressed。
buttonNormalThreadTestPressed:?jiǎn)?dòng)一個(gè)線程,在while循環(huán)中等待線程執(zhí)行完再接著往下運(yùn)行。
buttonRunloopPressed:?jiǎn)?dòng)一個(gè)線程,使用runloop,等待線程執(zhí)行完再接著往下運(yùn)行。
buttonTestPressed:僅僅打印兩條日志,用來(lái)測(cè)試UI是否能立即響應(yīng)的。
在本測(cè)試中,待程序運(yùn)行后,做如下操作對(duì)比:
1、點(diǎn)擊buttonNormalThreadTestPressed,然后立刻點(diǎn)擊buttonTestPressed,查看日志輸出。
2、待1完成后,點(diǎn)擊buttonRunloopPressed,然后立刻點(diǎn)擊buttonTestPressed,查看日志輸出,跟1的日志做對(duì)比,即可以發(fā)現(xiàn)步驟2即使線程沒有完成,在runloop等待過程中,界面仍然能夠響應(yīng)。
BOOL threadProcess1Finished =NO;
-(void)threadProce1{
NSLog(@"Enter threadProce1.");
for (int i=0; i<5;i++) {
NSLog(@"InthreadProce1 count = %d.", i);
sleep(1);
}
threadProcess1Finished =YES;
NSLog(@"Exit threadProce1.");
}
BOOL threadProcess2Finished =NO;
-(void)threadProce2{
NSLog(@"Enter threadProce2.");
for (int i=0; i<5;i++) {
NSLog(@"InthreadProce2 count = %d.", i);
sleep(1);
}
threadProcess2Finished =YES;
NSLog(@"Exit threadProce2.");
}
- (IBAction)buttonNormalThreadTestPressed:(UIButton *)sender {
NSLog(@"EnterbuttonNormalThreadTestPressed");
threadProcess1Finished =NO;
NSLog(@"Start a new thread.");
[NSThreaddetachNewThreadSelector: @selector(threadProce1)
toTarget: self
withObject: nil];
// 通常等待線程處理完后再繼續(xù)操作的代碼如下面的形式。
// 在等待線程threadProce1結(jié)束之前,調(diào)用buttonTestPressed,界面沒有響應(yīng),直到threadProce1運(yùn)行完,才打印buttonTestPressed里面的日志。
while (!threadProcess1Finished) {
[NSThreadsleepForTimeInterval: 0.5];
}
NSLog(@"ExitbuttonNormalThreadTestPressed");
}
- (IBAction)buttonRunloopPressed:(id)sender {
NSLog(@"Enter buttonRunloopPressed");
threadProcess2Finished =NO;
NSLog(@"Start a new thread.");
[NSThreaddetachNewThreadSelector: @selector(threadProce2)
toTarget: self
withObject: nil];
// 使用runloop,情況就不一樣了。
// 在等待線程threadProce2結(jié)束之前,調(diào)用buttonTestPressed,界面立馬響應(yīng),并打印buttonTestPressed里面的日志。
// 這就是runloop的神奇所在
while (!threadProcess2Finished) {
NSLog(@"Begin runloop");
[[NSRunLoopcurrentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate: [NSDate distantFuture]];
NSLog(@"End runloop.");
}
NSLog(@"Exit buttonRunloopPressed");
}
- (IBAction)buttonTestPressed:(id)sender{
NSLog(@"Enter buttonTestPressed");
NSLog(@"Exit buttonTestPressed");
}
日志信息如下:
2013-04-07 14:25:22.829 Runloop[657:11303] EnterbuttonNormalThreadTestPressed
2013-04-07 14:25:22.830 Runloop[657:11303] Start a new thread.
2013-04-07 14:25:22.831 Runloop[657:1250f] Enter threadProce1.
2013-04-07 14:25:22.832 Runloop[657:1250f] In threadProce1 count = 0.
2013-04-07 14:25:23.833 Runloop[657:1250f] In threadProce1 count = 1.
2013-04-07 14:25:24.834 Runloop[657:1250f] In threadProce1 count = 2.
2013-04-07 14:25:25.835 Runloop[657:1250f] In threadProce1 count = 3.
2013-04-07 14:25:26.837 Runloop[657:1250f] In threadProce1 count = 4.
2013-04-07 14:25:27.839 Runloop[657:1250f] Exit threadProce1.
2013-04-07 14:25:27.840 Runloop[657:11303]Exit buttonNormalThreadTestPressed
2013-04-07 14:25:27.841 Runloop[657:11303]Enter buttonTestPressed
2013-04-07 14:25:27.842 Runloop[657:11303] Exit buttonTestPressed
2013-04-07 14:25:27.843 Runloop[657:11303] Enter buttonTestPressed
2013-04-07 14:25:27.844 Runloop[657:11303] Exit buttonTestPressed
2013-04-07 14:43:41.790 Runloop[657:11303] Enter buttonRunloopPressed
2013-04-07 14:43:41.790 Runloop[657:11303] Start a new thread.
2013-04-07 14:43:41.791 Runloop[657:11303] Begin runloop
2013-04-07 14:43:41.791 Runloop[657:14f0b] Enter threadProce2.
2013-04-07 14:43:41.792 Runloop[657:14f0b] In threadProce2 count = 0.
2013-04-07 14:43:42.542 Runloop[657:11303] End runloop.
2013-04-07 14:43:42.543 Runloop[657:11303] Begin runloop
2013-04-07 14:43:42.694 Runloop[657:11303]Enter buttonTestPressed
2013-04-07 14:43:42.694 Runloop[657:11303]Exit buttonTestPressed
2013-04-07 14:43:42.695 Runloop[657:11303] End runloop.
2013-04-07 14:43:42.696 Runloop[657:11303] Begin runloop
2013-04-07 14:43:42.793 Runloop[657:14f0b] In threadProce2 count = 1.
2013-04-07 14:43:43.326 Runloop[657:11303] End runloop.
2013-04-07 14:43:43.327 Runloop[657:11303] Begin runloop
2013-04-07 14:43:43.438 Runloop[657:11303]Enter buttonTestPressed
2013-04-07 14:43:43.438 Runloop[657:11303]Exit buttonTestPressed
2013-04-07 14:43:43.439 Runloop[657:11303] End runloop.
2013-04-07 14:43:43.440 Runloop[657:11303] Begin runloop
2013-04-07 14:43:43.795 Runloop[657:14f0b] In threadProce2 count = 2.
2013-04-07 14:43:44.797 Runloop[657:14f0b] In threadProce2 count = 3.
2013-04-07 14:43:45.798 Runloop[657:14f0b] In threadProce2 count = 4.
2013-04-07 14:43:46.800 Runloop[657:14f0b] Exit threadProce2.
三、Runloop簡(jiǎn)單實(shí)例:
- (void)viewDidLoad
{
[superviewDidLoad];
// Doany additional setup after loading the view, typically from a nib.
[NSThreaddetachNewThreadSelector: @selector(newThreadProcess)
toTarget: self
withObject: nil];
}
- (void)newThreadProcess
{
@autoreleasepool {
////獲得當(dāng)前thread的Runloop
NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];
//設(shè)置Run loop observer的運(yùn)行環(huán)境
CFRunLoopObserverContext context = {0,self,NULL,NULL,NULL};
//創(chuàng)建Run loop observer對(duì)象
//第一個(gè)參數(shù)用于分配observer對(duì)象的內(nèi)存
//第二個(gè)參數(shù)用以設(shè)置observer所要關(guān)注的事件,詳見回調(diào)函數(shù)myRunLoopObserver中注釋
//第三個(gè)參數(shù)用于標(biāo)識(shí)該observer是在第一次進(jìn)入runloop時(shí)執(zhí)行還是每次進(jìn)入run loop處理時(shí)均執(zhí)行
//第四個(gè)參數(shù)用于設(shè)置該observer的優(yōu)先級(jí)
//第五個(gè)參數(shù)用于設(shè)置該observer的回調(diào)函數(shù)
//第六個(gè)參數(shù)用于設(shè)置該observer的運(yùn)行環(huán)境
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault,kCFRunLoopAllActivities, YES, 0, &myRunLoopObserver, &context);
if(observer)
{
//將Cocoa的NSRunLoop類型轉(zhuǎn)換成CoreFoundation的CFRunLoopRef類型
CFRunLoopRef cfRunLoop = [myRunLoop getCFRunLoop];
//將新建的observer加入到當(dāng)前thread的runloop
CFRunLoopAddObserver(cfRunLoop, observer, kCFRunLoopDefaultMode);
}
//
[NSTimerscheduledTimerWithTimeInterval: 1
target: self
selector:@selector(timerProcess)
userInfo: nil
repeats: YES];
NSInteger loopCount = 2;
do{
//啟動(dòng)當(dāng)前thread的loop直到所指定的時(shí)間到達(dá),在loop運(yùn)行時(shí),runloop會(huì)處理所有來(lái)自與該run loop聯(lián)系的inputsource的數(shù)據(jù)
//對(duì)于本例與當(dāng)前run loop聯(lián)系的inputsource只有一個(gè)Timer類型的source。
//該Timer每隔1秒發(fā)送觸發(fā)事件給runloop,run loop檢測(cè)到該事件時(shí)會(huì)調(diào)用相應(yīng)的處理方法。
//由于在run loop添加了observer且設(shè)置observer對(duì)所有的runloop行為都感興趣。
//當(dāng)調(diào)用runUnitDate方法時(shí),observer檢測(cè)到runloop啟動(dòng)并進(jìn)入循環(huán),observer會(huì)調(diào)用其回調(diào)函數(shù),第二個(gè)參數(shù)所傳遞的行為是kCFRunLoopEntry。
//observer檢測(cè)到runloop的其它行為并調(diào)用回調(diào)函數(shù)的操作與上面的描述相類似。
[myRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:5.0]];
//當(dāng)run loop的運(yùn)行時(shí)間到達(dá)時(shí),會(huì)退出當(dāng)前的runloop。observer同樣會(huì)檢測(cè)到runloop的退出行為并調(diào)用其回調(diào)函數(shù),第二個(gè)參數(shù)所傳遞的行為是kCFRunLoopExit。
loopCount--;
}while (loopCount);
}
}
void myRunLoopObserver(CFRunLoopObserverRef observer,CFRunLoopActivity activity,void *info)
{
switch (activity) {
//The entrance of the run loop, beforeentering the event processing loop.
//This activity occurs once for each callto CFRunLoopRun and CFRunLoopRunInMode
case kCFRunLoopEntry:
NSLog(@"run loop entry");
break;
//Inside the event processing loop beforeany timers are processed
case kCFRunLoopBeforeTimers:
NSLog(@"run loop before timers");
break;
//Inside the event processing loop beforeany sources are processed
case kCFRunLoopBeforeSources:
NSLog(@"run loop before sources");
break;
//Inside the event processing loop beforethe run loop sleeps, waiting for a source or timer to fire.
//This activity does not occur ifCFRunLoopRunInMode is called with a timeout of 0 seconds.
//It also does not occur in a particulariteration of the event processing loop if a version 0 source fires
case kCFRunLoopBeforeWaiting:
NSLog(@"run loop before waiting");
break;
//Inside the event processing loop afterthe run loop wakes up, but before processing the event that woke it up.
//This activity occurs only if the run loopdid in fact go to sleep during the current loop
case kCFRunLoopAfterWaiting:
NSLog(@"run loop after waiting");
break;
//The exit of the run loop, after exitingthe event processing loop.
//This activity occurs once for each callto CFRunLoopRun and CFRunLoopRunInMode
case kCFRunLoopExit:
NSLog(@"run loop exit");
break;
/*
A combination of all the precedingstages
case kCFRunLoopAllActivities:
break;
*/
default:
break;
}
}
- (void)timerProcess{
for (int i=0; i<5; i++) {
NSLog(@"In timerProcess count = %d.", i);
sleep(1);
}
}
調(diào)試打印信息如下:
2012-12-18 09:51:14.174 Texta[645:14807] run loop entry
2012-12-18 09:51:14.175 Texta[645:14807] run loop before timers
2012-12-18 09:51:14.176 Texta[645:14807] run loop before sources
2012-12-18 09:51:14.177 Texta[645:14807] run loop before waiting
2012-12-18 09:51:15.174 Texta[645:14807] run loop after waiting
2012-12-18 09:51:15.176 Texta[645:14807] In timerProcess count = 0.
2012-12-18 09:51:16.178 Texta[645:14807] In timerProcess count = 1.
2012-12-18 09:51:17.181 Texta[645:14807] In timerProcess count = 2.
2012-12-18 09:51:18.183 Texta[645:14807] In timerProcess count = 3.
2012-12-18 09:51:19.185 Texta[645:14807] In timerProcess count = 4.
2012-12-18 09:51:20.187 Texta[645:14807] run loop exit
2012-12-18 09:51:20.189 Texta[645:14807] run loop entry
2012-12-18 09:51:20.190 Texta[645:14807] run loop before timers
2012-12-18 09:51:20.191 Texta[645:14807] run loop before sources
2012-12-18 09:51:20.191 Texta[645:14807] run loop before waiting
2012-12-18 09:51:21.174 Texta[645:14807] run loop after waiting
2012-12-18 09:51:21.176 Texta[645:14807] In timerProcess count = 0.
2012-12-18 09:51:22.178 Texta[645:14807] In timerProcess count = 1.
2012-12-18 09:51:23.181 Texta[645:14807] In timerProcess count = 2.
2012-12-18 09:51:24.183 Texta[645:14807] In timerProcess count = 3.
2012-12-18 09:51:25.185 Texta[645:14807] In timerProcess count = 4.
2012-12-18 09:51:26.187 Texta[645:14807] run loop exit
四、Runloop可以阻塞線程,等待其他線程執(zhí)行后再執(zhí)行。
比如:
BOOL StopFlag =NO;
- (void)viewDidLoad
{
[superviewDidLoad];
// Doany additional setup after loading the view, typically from a nib.
StopFlag =NO;
NSLog(@"Start a new thread.");
[NSThreaddetachNewThreadSelector: @selector(newThreadProc)
toTarget:self
withObject: nil];
while (!StopFlag) {
NSLog(@"Beginrunloop");
[[NSRunLoopcurrentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate: [NSDate distantFuture]];
NSLog(@"Endrunloop.");
}
NSLog(@"OK");
}
-(void)newThreadProc{
NSLog(@"Enter newThreadProc.");
for (int i=0; i<10; i++) {
NSLog(@"InnewThreadProc count = %d.", i);
sleep(1);
}
StopFlag =YES;
NSLog(@"Exit newThreadProc.");
}
}
調(diào)試打印信息如下:
2012-12-18 08:50:34.220 Runloop[374:11303] Start a new thread.
2012-12-18 08:50:34.222 Runloop[374:11303] Begin runloop
2012-12-18 08:50:34.222 Runloop[374:14b03] Enter newThreadProc.
2012-12-18 08:50:34.223 Runloop[374:14b03] In newThreadProc count = 0.
2012-12-18 08:50:35.225 Runloop[374:14b03] In newThreadProc count = 1.
2012-12-18 08:50:36.228 Runloop[374:14b03] In newThreadProc count = 2.
2012-12-18 08:50:37.230 Runloop[374:14b03] In newThreadProc count = 3.
2012-12-18 08:50:38.233 Runloop[374:14b03] In newThreadProc count = 4.
2012-12-18 08:50:39.235 Runloop[374:14b03] In newThreadProc count = 5.
2012-12-18 08:50:40.237 Runloop[374:14b03] In newThreadProc count = 6.
2012-12-18 08:50:41.240 Runloop[374:14b03] In newThreadProc count = 7.
2012-12-18 08:50:42.242 Runloop[374:14b03] In newThreadProc count = 8.
2012-12-18 08:50:43.245 Runloop[374:14b03] In newThreadProc count = 9.
2012-12-18 08:50:44.247 Runloop[374:14b03] Exit newThreadProc.
2012-12-18 08:51:00.000 Runloop[374:11303] End runloop.
2012-12-18 08:51:00.001 Runloop[374:11303] OK
從調(diào)試打印信息可以看到,while循環(huán)后執(zhí)行的語(yǔ)句會(huì)在很長(zhǎng)時(shí)間后才被執(zhí)行。因?yàn)?,改變變量StopFlag的值,runloop對(duì)象根本不知道,runloop在這個(gè)時(shí)候未被喚醒。有其他事件在某個(gè)時(shí)點(diǎn)喚醒了主線程,這才結(jié)束了while循環(huán),但延緩的時(shí)長(zhǎng)總是不定的。。
將代碼稍微修改一下:
[[NSRunLoopcurrentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate: [NSDatedateWithTimeIntervalSinceNow: 1]];
縮短runloop的休眠時(shí)間,看起來(lái)解決了上面出現(xiàn)的問題。
但這樣會(huì)導(dǎo)致runloop被經(jīng)常性的喚醒,違背了runloop的設(shè)計(jì)初衷。runloop的目的就死讓你的線程在有工作的時(shí)候忙于工作,而沒工作的時(shí)候處于休眠狀態(tài)。
最后,看下下面正確的寫法:
BOOL StopFlag =NO;
- (void)viewDidLoad
{
[superviewDidLoad];
// Doany additional setup after loading the view, typically from a nib.
StopFlag =NO;
NSLog(@"Start a new thread.");
[NSThreaddetachNewThreadSelector: @selector(newThreadProc)
toTarget: self
withObject: nil];
while (!StopFlag) {
NSLog(@"Beginrunloop");
[[NSRunLoopcurrentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate: [NSDatedistantFuture]];
NSLog(@"Endrunloop.");
}
NSLog(@"OK");
}
-(void)newThreadProc{
NSLog(@"Enter newThreadProc.");
for (int i=0; i<10; i++) {
NSLog(@"InnewThreadProc count = %d.", i);
sleep(1);
}
[selfperformSelectorOnMainThread: @selector(setEnd)
withObject: nil
waitUntilDone: NO];
NSLog(@"Exit newThreadProc.");
}
-(void)setEnd{
StopFlag = YES;
}
調(diào)試打印信息如下:
2012-12-18 09:05:17.161 Runloop[410:11303] Start a new thread.
2012-12-18 09:05:17.163 Runloop[410:14a03] Enter newThreadProc.
2012-12-18 09:05:17.164 Runloop[410:14a03] In newThreadProc count = 0.
2012-12-18 09:05:17.165 Runloop[410:11303] Begin runloop
2012-12-18 09:05:18.166 Runloop[410:14a03] In newThreadProc count = 1.
2012-12-18 09:05:19.168 Runloop[410:14a03] In newThreadProc count = 2.
2012-12-18 09:05:20.171 Runloop[410:14a03] In newThreadProc count = 3.
2012-12-18 09:05:21.173 Runloop[410:14a03] In newThreadProc count = 4.
2012-12-18 09:05:22.175 Runloop[410:14a03] In newThreadProc count = 5.
2012-12-18 09:05:23.178 Runloop[410:14a03] In newThreadProc count = 6.
2012-12-18 09:05:24.180 Runloop[410:14a03] In newThreadProc count = 7.
2012-12-18 09:05:25.182 Runloop[410:14a03] In newThreadProc count = 8.
2012-12-18 09:05:26.185 Runloop[410:14a03] In newThreadProc count = 9.
2012-12-18 09:05:27.188 Runloop[410:14a03] Exit newThreadProc.
2012-12-18 09:05:27.188 Runloop[410:11303] End runloop.
2012-12-18 09:05:27.189 Runloop[410:11303] OK
把直接設(shè)置變量,改為向主線程發(fā)送消息,喚醒runloop,延時(shí)問題解決。