runloop

Runloop 框架

1、Runloop簡介: 異步

Run loops 是線程相關的的基礎框架的一部分。一個 run loop 就是一個事件處理 的循環,用來不停的調度工做以及處理輸入事件。 ide

使用 run loop的目的是讓你的線程在有工做的時候忙於工做,而沒工做的時候處於休眠狀態。 函數

Runloop還能夠在loop在循環中的同時響應其餘輸入源,好比界面控件的按鈕,手勢等。 oop

 

Run loop 接收輸入事件來自兩種不一樣的來源: 測試

輸入源(input source)和定時源 (timer source)。 spa

輸入源傳遞異步事件,一般消息來自於其餘線程或程序。輸入源的種類:基於端口的輸入源和自定義輸入源。 線程

定時源則傳遞同步事件,發生在特定時間或者重複的時間間隔。 設計

 

Run loop 模式是全部要監視的輸入源和定時源以及要通知的 run loop 註冊觀察 者的集合。 調試

能夠將 Run loop 觀察者和如下事件關聯:

Run loop 入口

Run loop 什麼時候處理一個定時器

Run loop 什麼時候處理一個輸入源

Run loop 什麼時候進入睡眠狀態

Run loop 什麼時候被喚醒,但在喚醒以前要處理的事件

Run loop 終止

 

每次運行 Run loop,你線程的 Run loop 對會自動處理以前未處理的消息,並通知相關的觀察者。具體的順序以下:

1. 通知觀察者 Run loop 已經啓動。

2. 通知觀察者任何即將要開始的定時器。

3. 通知觀察者任何即將啓動的非基於端口的源。

4. 啓動任何準備好的非基於端口的源。

5. 若是基於端口的源準備好並處於等待狀態,當即啓動;並進入步驟 9。

6. 通知觀察者線程進入休眠。

7. 將線程置於休眠直到任一下面的事件發生:

   某一事件到達基於端口的源;

   定時器啓動;

   Run loop 設置的時間已經超時;

   Run loop 被顯式喚醒。

8. 通知觀察者線程將被喚醒。

9. 處理未處理的事件

   若是用戶定義的定時器啓動,處理定時器事件並重啓 Run loop。進入步驟 2。

   若是輸入源啓動,傳遞相應的消息。

   若是 Run loop 被顯式喚醒並且時間還沒超時,重啓 Run loop,進入步驟 2。

10. 通知觀察者 Run loop 結束。

 

Run loop 在你要和線程有更多的交互時才須要,好比如下狀況:

使用端口或自定義輸入源來和其餘線程通訊;

使用線程的定時器;

Cocoa 中使用任何performSelector...的方法;

使線程週期性工做。

 

2、舉例說明Runloop的優勢。

通常狀況下,當咱們使用NSRunLoop的時候,代碼以下所示:

do {

    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopModebeforeDate:[NSDate distantFuture]];

} while (!done);

在上面的代碼中,參數done爲NO的時候,當前runloop會一直接收處理其餘輸入源,處理輸入源以後會再回到runloop中等待其餘的輸入源;除非done爲NO,不然當前流程一直再runloop中。

以下面的代碼片斷所示,有三個按鈕,分別對應以下三個action消息,buttonNormalThreadTestPressed,buttonRunloopPressed,buttonTestPressed。

buttonNormalThreadTestPressed:啓動一個線程,在while循環中等待線程執行完再接着往下運行。

buttonRunloopPressed:啓動一個線程,使用runloop,等待線程執行完再接着往下運行。

buttonTestPressed:僅僅打印兩條日誌,用來測試UI是否能當即響應的。

在本測試中,待程序運行後,作以下操做對比:

一、點擊buttonNormalThreadTestPressed,而後馬上點擊buttonTestPressed,查看日誌輸出。

二、待1完成後,點擊buttonRunloopPressed,而後馬上點擊buttonTestPressed,查看日誌輸出,跟1的日誌作對比,便可以發現步驟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)

                             toTargetself

                           withObjectnil];

   

    // 一般等待線程處理完後再繼續操做的代碼以下面的形式。

    // 在等待線程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)

                             toTargetself

                           withObjectnil];

   

    // 使用runloop,狀況就不同了。

    // 在等待線程threadProce2結束以前,調用buttonTestPressed,界面立馬響應,並打印buttonTestPressed裏面的日誌。

    // 這就是runloop的神奇所在

    while (!threadProcess2Finished) {

        NSLog(@"Begin runloop");

        [[NSRunLoopcurrentRunLooprunMode: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.

 

 

3、Runloop簡單實例:

- (void)viewDidLoad

{

    [superviewDidLoad];

    // Doany additional setup after loading the view, typically from a nib.

   

    [NSThreaddetachNewThreadSelector@selector(newThreadProcess)

                             toTargetself

                           withObjectnil];

}

 

 

- (void)newThreadProcess

{

    @autoreleasepool {

        ////得到當前threadRunloop

        NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];

       

        //設置Run loop observer的運行環境 

        CFRunLoopObserverContext context = {0,self,NULL,NULL,NULL};

       

        //建立Run loop observer對象 

        //第一個參數用於分配observer對象的內存 

        //第二個參數用以設置observer所要關注的事件,詳見回調函數myRunLoopObserver中註釋 

        //第三個參數用於標識該observer是在第一次進入runloop時執行仍是每次進入run loop處理時均執行 

        //第四個參數用於設置該observer的優先級 

        //第五個參數用於設置該observer的回調函數 

        //第六個參數用於設置該observer的運行環境 

        CFRunLoopObserverRef observer =CFRunLoopObserverCreate(kCFAllocatorDefault,kCFRunLoopAllActivitiesYES0, &myRunLoopObserver, &context);

        if(observer)

        {

            //CocoaNSRunLoop類型轉換成CoreFoundationCFRunLoopRef類型 

            CFRunLoopRef cfRunLoop = [myRunLoop getCFRunLoop];

           

            //將新建的observer加入到當前threadrunloop 

            CFRunLoopAddObserver(cfRunLoop, observer, kCFRunLoopDefaultMode);

        }

       

        //

        [NSTimerscheduledTimerWithTimeInterval1

                                        targetself

                                       selector:@selector(timerProcess)

                                      userInfonil

                                       repeatsYES];

        NSInteger loopCount = 2;

        do{

            //啓動當前threadloop直到所指定的時間到達,在loop運行時,runloop會處理全部來自與該run loop聯繫的inputsource的數據 

            //對於本例與當前run loop聯繫的inputsource只有一個Timer類型的source 

            //Timer每隔1秒發送觸發事件給runlooprun loop檢測到該事件時會調用相應的處理方法。 

           

            //因爲在run loop添加了observer且設置observer對全部的runloop行爲都感興趣。  

            //當調用runUnitDate方法時,observer檢測到runloop啓動並進入循環,observer會調用其回調函數,第二個參數所傳遞的行爲是kCFRunLoopEntry 

            //observer檢測到runloop的其它行爲並調用回調函數的操做與上面的描述相相似。 

            [myRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:5.0]];

            //run loop的運行時間到達時,會退出當前的runloopobserver一樣會檢測到runloop的退出行爲並調用其回調函數,第二個參數所傳遞的行爲是kCFRunLoopExit

            loopCount--;

        }while (loopCount);

    }

}

 

void myRunLoopObserver(CFRunLoopObserverRef observer,CFRunLoopActivityactivity,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

 

4、Runloop能夠阻塞線程,等待其餘線程執行後再執行。

好比:

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

                           withObjectnil];

    while (!StopFlag) {

        NSLog(@"Beginrunloop");

        [[NSRunLoopcurrentRunLooprunMode: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循環後執行的語句會在很長時間後才被執行。由於,改變變量StopFlag的值,runloop對象根本不知道,runloop在這個時候未被喚醒。有其餘事件在某個時點喚醒了主線程,這才結束了while循環,但延緩的時長老是不定的。。

 

將代碼稍微修改一下:

[[NSRunLoopcurrentRunLooprunMode:NSDefaultRunLoopMode

                                 beforeDate[NSDatedateWithTimeIntervalSinceNow1]];

縮短runloop的休眠時間,看起來解決了上面出現的問題。

但這樣會致使runloop被常常性的喚醒,違背了runloop的設計初衷。runloop的目的就死讓你的線程在有工做的時候忙於工做,而沒工做的時候處於休眠狀態。

 

最後,看下下面正確的寫法:

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)

                             toTargetself

                           withObjectnil];

    while (!StopFlag) {

        NSLog(@"Beginrunloop");

        [[NSRunLoopcurrentRunLooprunMode: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)

                          withObjectnil

                        waitUntilDoneNO];

   

    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

把直接設置變量,改成向主線程發送消息,喚醒runloop,延時問題解決。

相關文章
相關標籤/搜索