|
iOS學習筆記12—Runloop 一、Runloop簡介: Run loops 是線程相關的的基礎框架的一部分。一個 run loop 就是一個事件處理 的循環(huán),用來不停的調度工作以及處理輸入事件。 使用 run loop的目的是讓你的線程在有工作的時候忙于工作,而沒工作的時候處于休眠狀態(tài)。 Runloop還可以在loop在循環(huán)中的同時響應其他輸入源,比如界面控件的按鈕,手勢等。
Run loop 接收輸入事件來自兩種不同的來源: 輸入源(input source)和定時源 (timer source)。 輸入源傳遞異步事件,通常消息來自于其他線程或程序。輸入源的種類:基于端口的輸入源和自定義輸入源。 定時源則傳遞同步事件,發(fā)生在特定時間或者重復的時間間隔。
Run loop 模式是所有要監(jiān)視的輸入源和定時源以及要通知的 run loop 注冊觀察 者的集合。 可以將 Run loop 觀察者和以下事件關聯(lián): Run loop 入口 Run loop 何時處理一個定時器 Run loop 何時處理一個輸入源 Run loop 何時進入睡眠狀態(tài) Run loop 何時被喚醒,但在喚醒之前要處理的事件 Run loop 終止
每次運行 Run loop,你線程的 Run loop 對會自動處理之前未處理的消息,并通知相關的觀察者。具體的順序如下: 1. 通知觀察者 Run loop 已經啟動。 2. 通知觀察者任何即將要開始的定時器。 3. 通知觀察者任何即將啟動的非基于端口的源。 4. 啟動任何準備好的非基于端口的源。 5. 如果基于端口的源準備好并處于等待狀態(tài),立即啟動;并進入步驟 9。 6. 通知觀察者線程進入休眠。 7. 將線程置于休眠直到任一下面的事件發(fā)生: 某一事件到達基于端口的源; 定時器啟動; Run loop 設置的時間已經超時; Run loop 被顯式喚醒。 8. 通知觀察者線程將被喚醒。 9. 處理未處理的事件 如果用戶定義的定時器啟動,處理定時器事件并重啟 Run loop。進入步驟 2。 如果輸入源啟動,傳遞相應的消息。 如果 Run loop 被顯式喚醒而且時間還沒超時,重啟 Run loop,進入步驟 2。 10. 通知觀察者 Run loop 結束。
Run loop 在你要和線程有更多的交互時才需要,比如以下情況: 使用端口或自定義輸入源來和其他線程通信; 使用線程的定時器; Cocoa 中使用任何performSelector...的方法; 使線程周期性工作。
二、舉例說明Runloop的優(yōu)點。 一般情況下,當我們使用NSRunLoop的時候,代碼如下所示: do { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopModebeforeDate:[NSDate distantFuture]]; } while (!done); 在上面的代碼中,參數done為NO的時候,當前runloop會一直接收處理其他輸入源,處理輸入源之后會再回到runloop中等待其他的輸入源;除非done為NO,否則當前流程一直再runloop中。 如下面的代碼片段所示,有三個按鈕,分別對應如下三個action消息,buttonNormalThreadTestPressed,buttonRunloopPressed,buttonTestPressed。 buttonNormalThreadTestPressed:啟動一個線程,在while循環(huán)中等待線程執(zhí)行完再接著往下運行。 buttonRunloopPressed:啟動一個線程,使用runloop,等待線程執(zhí)行完再接著往下運行。 buttonTestPressed:僅僅打印兩條日志,用來測試UI是否能立即響應的。 在本測試中,待程序運行后,做如下操作對比: 1、點擊buttonNormalThreadTestPressed,然后立刻點擊buttonTestPressed,查看日志輸出。 2、待1完成后,點擊buttonRunloopPressed,然后立刻點擊buttonTestPressed,查看日志輸出,跟1的日志做對比,即可以發(fā)現(xiàn)步驟2即使線程沒有完成,在runloop等待過程中,界面仍然能夠響應。
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結束之前,調用buttonTestPressed,界面沒有響應,直到threadProce1運行完,才打印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結束之前,調用buttonTestPressed,界面立馬響應,并打印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簡單實例: - (void)viewDidLoad { [superviewDidLoad]; // Doany additional setup after loading the view, typically from a nib.
[NSThreaddetachNewThreadSelector: @selector(newThreadProcess) toTarget: self withObject: nil]; }
- (void)newThreadProcess { @autoreleasepool { ////獲得當前thread的Runloop NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];
//設置Run loop observer的運行環(huán)境 CFRunLoopObserverContext context = {0,self,NULL,NULL,NULL};
//創(chuàng)建Run loop observer對象 //第一個參數用于分配observer對象的內存 //第二個參數用以設置observer所要關注的事件,詳見回調函數myRunLoopObserver中注釋 //第三個參數用于標識該observer是在第一次進入runloop時執(zhí)行還是每次進入run loop處理時均執(zhí)行 //第四個參數用于設置該observer的優(yōu)先級 //第五個參數用于設置該observer的回調函數 //第六個參數用于設置該observer的運行環(huán)境 CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault,kCFRunLoopAllActivities, YES, 0, &myRunLoopObserver, &context); if(observer) { //將Cocoa的NSRunLoop類型轉換成CoreFoundation的CFRunLoopRef類型 CFRunLoopRef cfRunLoop = [myRunLoop getCFRunLoop];
//將新建的observer加入到當前thread的runloop CFRunLoopAddObserver(cfRunLoop, observer, kCFRunLoopDefaultMode); }
// [NSTimerscheduledTimerWithTimeInterval: 1 target: self selector:@selector(timerProcess) userInfo: nil repeats: YES]; NSInteger loopCount = 2; do{ //啟動當前thread的loop直到所指定的時間到達,在loop運行時,runloop會處理所有來自與該run loop聯(lián)系的inputsource的數據 //對于本例與當前run loop聯(lián)系的inputsource只有一個Timer類型的source。 //該Timer每隔1秒發(fā)送觸發(fā)事件給runloop,run loop檢測到該事件時會調用相應的處理方法。
//由于在run loop添加了observer且設置observer對所有的runloop行為都感興趣。 //當調用runUnitDate方法時,observer檢測到runloop啟動并進入循環(huán),observer會調用其回調函數,第二個參數所傳遞的行為是kCFRunLoopEntry。 //observer檢測到runloop的其它行為并調用回調函數的操作與上面的描述相類似。 [myRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:5.0]]; //當run loop的運行時間到達時,會退出當前的runloop。observer同樣會檢測到runloop的退出行為并調用其回調函數,第二個參數所傳遞的行為是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); } }
調試打印信息如下: 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."); } } 調試打印信息如下: 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 從調試打印信息可以看到,while循環(huán)后執(zhí)行的語句會在很長時間后才被執(zhí)行。因為,改變變量StopFlag的值,runloop對象根本不知道,runloop在這個時候未被喚醒。有其他事件在某個時點喚醒了主線程,這才結束了while循環(huán),但延緩的時長總是不定的。。
將代碼稍微修改一下: [[NSRunLoopcurrentRunLoop] runMode:NSDefaultRunLoopMode beforeDate: [NSDatedateWithTimeIntervalSinceNow: 1]]; 縮短runloop的休眠時間,看起來解決了上面出現(xiàn)的問題。 但這樣會導致runloop被經常性的喚醒,違背了runloop的設計初衷。runloop的目的就死讓你的線程在有工作的時候忙于工作,而沒工作的時候處于休眠狀態(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; }
調試打印信息如下: 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 把直接設置變量,改為向主線程發(fā)送消息,喚醒runloop,延時問題解決。
|
|
|
來自: 最初九月雪 > 《NSRunLoop》