iOS RunLoop 初識


今天突然才之間才意識到NSTimer這樣的運行方式,是在多線程中實現的循環還是在主線程中去實現的呢。當然不可能是在主線程中的while那么簡單,那樣什么都干不了,簡單看了下NSTimer是以同步方式運行的。時間到了,消息發出后,ontimer的函數是在主線程上調用的。

我們會經常看到這樣的代碼:

  1. - (IBAction)start:(id)sender  
  2. {  
  3. pageStillLoading = YES;  
  4. [NSThread detachNewThreadSelector:@selector(loadPageInBackground:)toTarget:self withObject:nil];  
  5. [progress setHidden:NO];  
  6. while (pageStillLoading) {  
  7. [NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];  
  8. }  
  9. [progress setHidden:YES];  
  10. }  
復制代碼

這段代碼很神奇的,因為他會“暫停”代碼運行,而且程序運行不會因為這里有一個while循環而受到影響。在[progress setHidden:NO]執行之后,整個函數想暫停了一樣停在循環里面,等loadPageInBackground里面的操作都完成了以后才讓[progress setHidden:YES]運行。這樣做就顯得簡介,而且邏輯很清晰。如果你不這樣做,你就需要在loadPageInBackground里面表示load完成的地方調用[progress setHidden:YES],顯得代碼不緊湊而且容易出錯。

 

        Run loop就像它的名字一樣,是你thread中的一個循環並對收到的事件進行處理。你的代碼提供控制語句用來對run loop進行執行——換句話說:你的代碼提供while或for循環來驅動run loop。在你的循環中,你使用run loop對象來“運行”事件處理代碼。事件處理代碼主要進行接收事件並調用事件處理函數。

        Run loop從兩個不同的事件源中接收消息。Input sources(CFRunLoopSource)投遞異步消息,通常來自於另一個thread或另一個應用程序。Timer sources(CFRunLoopTimer)當在計划的時間或重復的時間間隔內投遞同步消息。兩種事件源都使用應用程序指定的處理方式對到達的事件進行處理。下圖展示了run loop和不同的事件源結構。

如果我們要寫多線程的程序,可能就需要自己來管理Run Loop。
下面說一下樓主提出的方法中的參數:
RunMode: NSDefaultRunLoopMode,可以把這個理解為一個”過濾器“,我們可以只對自己關心的事件進行監視。一般NSDefaultRunLoopMode是最常用的。

啟動run loop的方法就是lz寫的這個,它的說明如下:
Runs the loop once, blocking for input in the specified mode until a given date.
啟動run loop一次,在特定的run loop mode下等待輸入。

如果沒有附加input source或是timer,或是過limitDate,run loop就會退出,並且方法返回NO。

下來是Run Loop的使用場合:
1. 使用port或是自定義的input source來和其他線程進行通信
2. 在線程(非主線程)中使用timer
3. 使用 performSelector…系列(如performSelectorOnThread, …)
4. 使用線程執行周期性工作

run loop不需要創建,在線程中只需要調用[NSRunLoop currentRunLoop]就可以得到
假設我們想要等待某個異步方法的回調。比如connection。如果我們的線程中沒有啟動run loop,是不會有效果的(因為線程已經運行完畢,正常退出了)。

 

 

何時使用 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 在你要和線程有更多的交互時才需要,比如以下情況:

 使用端口或自定義輸入源來和其他線程通信  使用線程的定時器  Cocoa 中使用任何 performSelector...的方法  使線程周期性工作

 

如果你決定在程序中使用 run loop,那么它的配置和啟動都很簡單。和所有線程 編程一樣,你需要計划好在輔助線程退出線程的情形。讓線程自然退出往往比強制關 閉它更好。關於更多介紹如何配置和退出一個 run loop,參閱”使用 Run Loop 對象” 的介紹。

   上代碼:

 

使用runloop阻塞線程的正確寫法

Runloop可以阻塞線程,等待其他線程執行后再執行。

比如:

@implementation ViewController{
    BOOL end;
}

– (void)viewDidLoad
{
    [super viewDidLoad]; 
    NSLog(@”start new thread …”);
    [NSThread detachNewThreadSelector:@selector(runOnNewThread) toTarget:self withObject:nil];    
    while (!end) {
        NSLog(@”runloop…”);
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        NSLog(@”runloop end.”);
    }
    NSLog(@”ok.”);
}
-(void)runOnNewThread{
     NSLog(@”run for new thread …”);
    sleep(1);
    end=YES;
    NSLog(@”end.”);
}

但是這樣做,運行時會發現,while循環后執行的語句會在很長時間后才被執行。

那是不是可以這樣:

[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];

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

不過這樣也又問題,runloop對象被經常性的喚醒,這違背了runloop的設計初衷。runloop的作用就是要減少cpu做無謂的空轉,cpu可在空閑的時候休眠,以節約電量。

那么怎么做呢?正確的寫法是:

-(void)runOnNewThread{

     NSLog(@”run for new thread …”);
    sleep(1);
    [self performSelectorOnMainThread:@selector(setEnd) withObject:nil waitUntilDone:NO];
    NSLog(@”end.”);
}
-(void)setEnd{
    end=YES;
}

見黑體斜體字部分,要將直接設置變量,改為向主線程發送消息,執行方法。問題得到解決。

這里要說一下,造成while循環后語句延緩執行的原因是,runloop未被喚醒。因為,改變變量的值,runloop對象根本不知道。延緩的時長總是不定的,這是因為,有其他事件在某個時點喚醒了主線程,這才結束了while循環。那么,向主線程發送消息,將喚醒runloop,因此問題就解決了。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM