iPhone應用開發中關於NSRunLoop的概述是本文要介紹的內容,NSRunLoop是一種更加高明的消息處理模式,他就高明在對消息處理過程進行了更好的抽象和封裝,這樣才能是的你不用處理一些很瑣碎很低層次的具體消息的處理,在NSRunLoop中每個消息就被打包在input source或者是timer source中了,來看詳細內容。
1.什麼是NSRunLoop
咱們會常常看到這樣的代碼:html
- (IBAction)start:(id)sender
c++
{
編程
pageStillLoading = YES;
windows
[NSThread detachNewThreadSelector:@selector(loadPageInBackground:)toTarget:self withObject:nil];
網絡
[progress setHidden:NO];
多線程
while (pageStillLoading) {
框架
[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
異步
}
socket
[progress setHidden:YES];
async
}
複製代碼
這段代碼很神奇的,由於他會「暫停」代碼運行,並且程序運行不會由於這裏有一個while循環而受到影響。在[progress setHidden:NO]執行以後,整個函數想暫停了同樣停在循環裏面,等loadPageInBackground裏面的操做都完成了之後才讓[progress setHidden:YES]運行。這樣作就顯得簡介,並且邏輯很清晰。若是你不這樣作,你就須要在loadPageInBackground裏面表示load完成的地方調用[progress setHidden:YES],顯得代碼不緊湊並且容易出錯。
[iGoogle有話說:應用程序框架主線程已經封裝了對NSRunLoop runMode:beforeDate:的調用;它和while循環構成了一個消息泵,不斷獲取和處理消息;可能你們會比較奇怪,既然主線程中已經封裝好了對NSRunLoop的調用,爲何這裏還能夠再次調用,這個就是它與Windows消息循環的區別,它能夠嵌套調用.當再次調用while+NSRunLoop時候程序並無中止執行,它還在不停提取消息/處理消息.這一點與Symbian中Active Scheduler的嵌套調用達到同步做用原理是同樣的.]
那麼具體什麼是NSRunLoop呢?其實NSRunLoop的本質是一個消息機制的處理模式。若是你對vc++編程有必定了解,在windows中,有一系列很重要的函數SendMessage,PostMessage,GetMessage,這些都是有關消息傳遞處理的API。
可是在你進入到Cocoa的編程世界裏面,我不知道你是否是走的太快太匆忙而忽視了這個很重要的問題,Cocoa裏面就沒有說起到任何關於消息處理的API,開發者歷來也沒有本身去關心過消息的傳遞過程,好像一切都是那麼天然,像大天然同樣天然?在Cocoa裏面你不再用去本身定義WM_COMMAD_XXX這樣的宏來標識某個消息,也不用在switch-case裏面去對特定的消息作特別的處理。難道是Cocoa裏面就沒有了消息機制?答案是否認的,只是Apple在設計消息處理的時候採用了一個更加高明的模式,那就是RunLoop。
2. NSRunLoop工做原理
接下來看一下NSRunLoop具體的工做原理,首先是官方文檔提供的說法,看圖:
經過全部的「消息」都被添加到了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,如圖:
利用NSRunLoop阻塞NSOperation線程
在使用NSOperationQueue簡化多線程開發中介紹了多線程的開發,我這裏主要介紹一下使用NSRunLoop阻塞線程。
主要使用在NStimer定時啓用的任務或者異步獲取數據的狀況如socket獲取網絡數據,要阻塞線程,直到獲取數據以後在釋放線程。
下面是線程中沒有使用NSRunLoop阻塞線程的代碼和執行效果:
線程類:
#import <Foundation/Foundation.h>
@interface MyTask : NSOperation {
}
@end
#import "MyTask.h"
@implementation MyTask
-(void)main
{
NSLog(@"開始線程=%@",self);
[NSTimer timerWithTimeInterval:2 target:self selector:@selector(hiandeTime:) userInfo:nil repeats:NO];
}
-(void)hiandeTime:(id)sender
{
NSLog(@"執行了NSTimer");
}
-(void)dealloc
{
NSLog(@"delloc mytask=%@",self);
[super dealloc];
}
@end
線程添加到隊列中:
- (void)viewDidLoad
{
[super viewDidLoad];
NSOperationQueue *queue=[[NSOperationQueue alloc] init];
MyTask *myTask=[[[MyTask alloc] init] autorelease];
[queue addOperation:myTask];
MyTask *myTask1=[[[MyTask alloc] init] autorelease];
[queue addOperation:myTask1];
MyTask *myTask2=[[[MyTask alloc] init] autorelease];
[queue addOperation:myTask2];
[queue release];
}
執行結果是:
2011-07-25 09:44:45.393 OperationDemo[20676:1803] 開始線程=<MyTask: 0x4b4dea0>
2011-07-25 09:44:45.393 OperationDemo[20676:5d03] 開始線程=<MyTask: 0x4b50db0>
2011-07-25 09:44:45.396 OperationDemo[20676:1803] 開始線程=<MyTask: 0x4b51070>
2011-07-25 09:44:45.404 OperationDemo[20676:6303] delloc mytask=<MyTask: 0x4b4dea0>
2011-07-25 09:44:45.404 OperationDemo[20676:5d03] delloc mytask=<MyTask: 0x4b50db0>
2011-07-25 09:44:45.405 OperationDemo[20676:6303] delloc mytask=<MyTask: 0x4b51070>
能夠看到,根本沒有執行NSTimer中的方法,線程就釋放掉了,咱們要執行
NSTimer中的方法,就要利用NSRunLoop阻塞線程。下面是修改後的代碼:
-(void)main執行結果以下:
{
NSLog(@"開始線程=%@",self);
NSTimer *timer=[NSTimer timerWithTimeInterval:2 target:self selector:@selector(hiandeTime) userInfo:nil repeats:NO];
[timer fire];
while (!didDisconnect) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}
2011-07-25 10:07:00.543 OperationDemo[21270:1803] 開始線程=<MyTask: 0x4d16380>咱們可使用NSRunLoop進行線程阻塞。
2011-07-25 10:07:00.543 OperationDemo[21270:5d03] 開始線程=<MyTask: 0x4d17790>
2011-07-25 10:07:00.550 OperationDemo[21270:6303] 開始線程=<MyTask: 0x4d17a50>
2011-07-25 10:07:00.550 OperationDemo[21270:1803] 執行了NSTimer
2011-07-25 10:07:00.551 OperationDemo[21270:5d03] 執行了NSTimer
2011-07-25 10:07:00.552 OperationDemo[21270:6303] 執行了NSTimer
2011-07-25 10:07:00.556 OperationDemo[21270:6503] delloc mytask=<MyTask: 0x4d16380>
2011-07-25 10:07:00.557 OperationDemo[21270:6303] delloc mytask=<MyTask: 0x4d17790>
2011-07-25 10:07:00.557 OperationDemo[21270:5d03] delloc mytask=<MyTask: 0x4d17a50>
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,所以問題就解決了。
NSRunLoop runMode:
NSDefaultRunLoopMode/NSRunLoopCommonModes
eg.
[[NSRunLoopcurrentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
from CFRunLoop Reference
CFRunLoop monitors sources of input to a task and dispatches control when they become ready for processing.
Examples of input source might include user input devices,network connection,periodic or timed-delay events,and asynchronous callbacks.
Three types of objects can be monitored by a run loop:
sources(CFRunLoopSource)
timers(CFRunLoopTimer)
observers(CFRunLoopObserver)
Each sources,times or observers added to a run loop must be associated with one or more run loop modes.
There is exactly one run loop per thread.
RunLoop 是事件循環,用於schedule work 和協調輸入事件.
RunLoop 接受2種不一樣的事件源,input sources (異步事件)和 timer sources(同步事件)
什麼時候使用RunLoop?
1.使用ports或自定義輸入源 與其餘線程通信
2.在線程中使用定時器
3.在程序中使用performSelector等方法
4.讓線程週期性執行某個任務