1. 程式人生 > >NSRunLoop概述和原理[轉]

NSRunLoop概述和原理[轉]

1.什麼是NSRunLoop?
我們會經常看到這樣的程式碼:

- (IBAction)start:(id)sender
{
pageStillLoading = YES;
[NSThread detachNewThreadSelector:@selector(loadPageInBackground:)toTarget:self withObject:nil];
[progress setHidden:NO];
while (pageStillLoading) {
[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
[progress setHidden:YES];
}


這段程式碼很神奇的,因為他會“暫停”程式碼執行,而且程式執行不會因為這裡有一個while迴圈而受到影響。在[progress setHidden:NO]執行之後,整個函式想暫停了一樣停在迴圈裡面,等loadPageInBackground裡面的操作都完成了以後才讓[progress setHidden:YES]執行。這樣做就顯得簡介,而且邏輯很清晰。如果你不這樣做,你就需要在loadPageInBackground裡面表示load完成的地方呼叫[progress setHidden:YES],顯得程式碼不緊湊而且容易出錯。
那麼具體什麼是NSRunLoop呢?其實NSRunLoop的本質是一個訊息機制的處理模式。如果你對vc++程式設計有一定了解,在windows中,有一系列很重要的函式SendMessage,PostMessage,GetMessage,這些都是有關訊息傳遞處理的API。但是在你進入到Cocoa的程式設計世界裡面,我不知道你是不是走的太快太匆忙而忽視了這個很重要的問題,Cocoa裡面就沒有提及到任何關於訊息處理的API,開發者從來也沒有自己去關心過訊息的傳遞過程,好像一切都是那麼自然,像大自然一樣自然?在Cocoa裡面你再也不用去自己定義WM_COMMAD_XXX這樣的巨集來標識某個訊息,也不用在switch-case裡面去對特定的訊息做特別的處理。難道是Cocoa裡面就沒有了訊息機制?答案是否定的,只是Apple在設計訊息處理的時候採用了一個更加高明的模式,那就是RunLoop。

2. NSRunLoop工作原理
接下來看一下NSRunLoop具體的工作原理,首先是官方文件提供的說法,看圖:

[img]http://dl.iteye.com/upload/attachment/529881/22e63a66-c25d-3f07-a775-0f6b81a42a86.png[/img]

通過所有的“訊息”都被新增到了NSRunLoop中去,而在這裡這些訊息並分為“input source”和“Timer source” 並在迴圈中檢查是不是有事件需要發生,如果需要那麼就呼叫相應的函式處理。為了更清晰的解釋,我們來對比VC++和iOS訊息處理過程。

VC++中在一切初始化都完成之後程式就開始這樣一個迴圈了(程式碼是從戶sir mfc程式設計課程的slides中擷取):



int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow){
...
while (GetMessage(&msg, NULL, 0, 0)){
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg)){
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}


可以看到在GetMessage之後就去分發處理訊息了,而iOS中main函式中只是呼叫了UIApplicationMain,那麼我們可以介意猜出UIApplicationMain在初始化完成之後就會進入這樣一個情形:

int UIApplicationMain(...){
...
while(running){
[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
...
}


所以在UIApplicationMain中也是同樣在不斷處理runloop才是的程式沒有退出。剛才的我說了NSRunLoop是一種更加高明的訊息處理模式,他就高明在對訊息處理過程進行了更好的抽象和封裝,這樣才能是的你不用處理一些很瑣碎很低層次的具體訊息的處理,在NSRunLoop中每一個訊息就被打包在input source或者是timer source中了,當需要處理的時候就直接呼叫其中包含的相應物件的處理函數了。所以對外部的開發人員來講,你感受到的就是,把source/timer加入到runloop中,然後在適當的時候類似於[receiver action]這樣的事情發生了。甚至很多時候,你都沒有感受到整個過程前半部分,你只是感覺到了你的某個物件的某個函式呼叫了。比如在UIView被觸控時會用touchesBegan/touchesMoved等等函式被呼叫,也許你會想,“該死的,我都不知道在那裡被告知有觸控訊息,這些處理函式就被呼叫了!?”所以,訊息是有的,只是runloop已經幫你做了!為了證明我的觀點,我截取了一張debug touchesBegan的call stack,有圖有真相:


[img]http://dl.iteye.com/upload/attachment/529883/58303cd1-0f6f-3f82-ad93-ff75a18c89b5.png[/img]