什么情況下使用runloop不恭?
runloop好比就是跑圈,就是一個(gè)線程一直在做某一件事情财饥。
一般主線程會(huì)自動(dòng)運(yùn)行runloop,我們一般情況下不用管换吧。而在子線程中,我們需要手動(dòng)去運(yùn)行它钥星。你可以把它想象成一個(gè)循環(huán)式散,一般情況下我們沒有必要去啟動(dòng)線程的runloop,例如:在需要一個(gè)單獨(dú)的線程長時(shí)間的去檢測某一個(gè)事件打颤,具體看需求而定了暴拄。如果沒有這個(gè)循環(huán),子線程完成任務(wù)后编饺,這個(gè)線程就結(jié)束了乖篷。所以這個(gè)時(shí)候我們就要運(yùn)行一個(gè)runloop,用于處理種種事件,而讓它不結(jié)束透且。而沒有事件發(fā)生的時(shí)候撕蔼, 會(huì)處于休眠狀態(tài)豁鲤,以節(jié)省電量。
那么一般在什么情況下用到呢:
1.需要使用Port(基于端口)或者自定義(事件源)Input Source與其他線程進(jìn)行通訊鲸沮。
2.需要在線程中使用Timer琳骡。
3.需要在線程上使用performSelector*****方法。
4.需要讓線程執(zhí)行周期性的工作讼溺。
例:常駐線程
創(chuàng)建一個(gè)線程來處理耗時(shí)且頻繁的操作楣号,例如即時(shí)聊天音頻的壓縮,或者經(jīng)常下載怒坯,避免頻繁開啟線程以便提高性能, AFNetWorking就是如此炫狱。
[[NSThreadcurrentThread] setName:@"AFNetworking"];
NSRunLoop*runLoop = [NSRunLoopcurrentRunLoop];
?[runLoop addPort:[NSMachPortport] forMode:NSDefaultRunLoopMode];?
?[runLoop run];
RunLoop 對(duì)外的接口
在 CoreFoundation 里面關(guān)于 RunLoop 有5個(gè)類:
CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef
其中 CFRunLoopModeRef 類并沒有對(duì)外暴露,只是通過 CFRunLoopRef 的接口進(jìn)行了封裝剔猿。他們的關(guān)系如下:
一個(gè) RunLoop 包含若干個(gè) Mode视译,每個(gè) Mode 又包含若干個(gè) Source/Timer/Observer。每次調(diào)用 RunLoop 的主函數(shù)時(shí)归敬,只能指定其中一個(gè) Mode酷含,這個(gè)Mode被稱作 CurrentMode。如果需要切換 Mode汪茧,只能退出 Loop第美,再重新指定一個(gè) Mode 進(jìn)入。這樣做主要是為了分隔開不同組的 Source/Timer/Observer陆爽,讓其互不影響什往。
CFRunLoopSourceRef是事件產(chǎn)生的地方。Source有兩個(gè)版本:Source0 和 Source1慌闭。
Source0 只包含了一個(gè)回調(diào)(函數(shù)指針)别威,它并不能主動(dòng)觸發(fā)事件。使用時(shí)驴剔,你需要先調(diào)用 CFRunLoopSourceSignal(source)省古,將這個(gè) Source 標(biāo)記為待處理,然后手動(dòng)調(diào)用 CFRunLoopWakeUp(runloop) 來喚醒 RunLoop丧失,讓其處理這個(gè)事件豺妓。
Source1 包含了一個(gè) mach_port 和一個(gè)回調(diào)(函數(shù)指針),被用于通過內(nèi)核和其他線程相互發(fā)送消息布讹。這種 Source 能主動(dòng)喚醒 RunLoop 的線程琳拭,其原理在下面會(huì)講到。
CFRunLoopTimerRef是基于時(shí)間的觸發(fā)器描验,它和 NSTimer 是toll-free bridged 的白嘁,可以混用。其包含一個(gè)時(shí)間長度和一個(gè)回調(diào)(函數(shù)指針)膘流。當(dāng)其加入到 RunLoop 時(shí)絮缅,RunLoop會(huì)注冊對(duì)應(yīng)的時(shí)間點(diǎn)鲁沥,當(dāng)時(shí)間點(diǎn)到時(shí),RunLoop會(huì)被喚醒以執(zhí)行那個(gè)回調(diào)耕魄。
CFRunLoopObserverRef是觀察者画恰,每個(gè) Observer 都包含了一個(gè)回調(diào)(函數(shù)指針),當(dāng) RunLoop 的狀態(tài)發(fā)生變化時(shí)吸奴,觀察者就能通過回調(diào)接受到這個(gè)變化允扇。
typedef?CF_OPTIONS(CFOptionFlags,?CFRunLoopActivity)?{
kCFRunLoopEntry?????????=?(1UL?<<?0),//?即將進(jìn)入Loop
kCFRunLoopBeforeTimers??=?(1UL?<<?1),//?即將處理?Timer
kCFRunLoopBeforeSources?=?(1UL?<<?2),//?即將處理?Source
kCFRunLoopBeforeWaiting?=?(1UL?<<?5),//?即將進(jìn)入休眠
kCFRunLoopAfterWaiting??=?(1UL?<<?6),//?剛從休眠中喚醒
kCFRunLoopExit??????????=?(1UL?<<?7),//?即將退出Loop
};
Source/Timer/Observer 被統(tǒng)稱為 mode item,一個(gè) item 可以被同時(shí)加入多個(gè) mode奄抽。但一個(gè) item 被重復(fù)加入同一個(gè) mode 時(shí)是不會(huì)有效果的蔼两。如果一個(gè) mode 中一個(gè) item 都沒有甩鳄,則 RunLoop 會(huì)直接退出逞度,不進(jìn)入循環(huán)。
RunLoop各種模式(CFRunLoopModeRef)
每個(gè)run loop可運(yùn)行在不同的模式下,一個(gè)run loop mode是一個(gè)集合妙啃,其中包含其監(jiān)聽的若干輸入事件源档泽,定時(shí)器烦粒,以及在事件發(fā)生時(shí)需要通知的run loop observers穆碎。運(yùn)行在一種mode下的run loop只會(huì)處理其run loop mode中包含的輸入源事件误褪,定時(shí)器事件酱塔,以及通知run loop mode中包含的observers理盆。
CFRunLoopModeRef 代表 RunLoop 的運(yùn)行模式
一個(gè) RunLoop 包含若干個(gè) Mode改鲫,每個(gè)Mode 又包含若干個(gè) Source/Timer筛璧、Observer
每次 RunLoop 啟動(dòng)時(shí)卧波,只能制定其中一個(gè) Mode铭拧,這個(gè) Mode 被稱作 CurrentMode
如果需要切換 Mode赃蛛,只能退出 Loop,再重新制定一個(gè) Mode 進(jìn)入
這樣做主要是為了分割開不同組的 Source/Timer/Observer搀菩,讓其互不影響
Cocoa中的預(yù)定義模式有:
Default模式
定義:NSDefaultRunLoopMode(Cocoa)kCFRunLoopDefaultMode(Core Foundation)
描述:默認(rèn)模式中幾乎包含了所有輸入源(NSConnection除外),一般情況下應(yīng)使用此模式呕臂。
Connection模式
定義:NSConnectionReplyMode(Cocoa)
描述:處理NSConnection對(duì)象相關(guān)事件,系統(tǒng)內(nèi)部使用肪跋,用戶基本不會(huì)使用歧蒋。
Modal模式
定義:NSModalPanelRunLoopMode(Cocoa)
描述:處理modal panels事件。
Event tracking模式
定義:UITrackingRunLoopMode(iOS) NSEventTrackingRunLoopMode(cocoa)
描述:在拖動(dòng)loop或其他user interface tracking loops時(shí)處于此種模式下州既,在此模式下會(huì)限制輸入事件的處理谜洽。例如,當(dāng)手指按住UITableView拖動(dòng)時(shí)就會(huì)處于此模式吴叶。
Common模式
定義:NSRunLoopCommonModes(Cocoa)kCFRunLoopCommonModes(Core Foundation)
描述:這是一個(gè)偽模式褥琐,其為一組run loop mode的集合,將輸入源加入此模式意味著在Common Modes中包含的所有模式下都可以處理晤郑。在Cocoa應(yīng)用程序中敌呈,默認(rèn)情況下Common Modes包含default modes,modal modes,event Tracking modes.可使用CFRunLoopAddCommonMode方法想Common Modes中添加自定義modes贸宏。
獲取當(dāng)前線程的run loop mode
NSString* runLoopMode = [[NSRunLoop currentRunLoop] currentMode];
RunLooop與Source
正如其名所示,是線程進(jìn)入和被線程用來響應(yīng)事件以及調(diào)用事件處理函數(shù)的地方磕洪。需要在代碼中使用控制語句實(shí)現(xiàn)run loop的循環(huán)吭练,也就是說,需要代碼提供while 或者 for循環(huán)來驅(qū)動(dòng)run loop析显。
在這個(gè)循環(huán)中鲫咽,使用一個(gè)Runloop對(duì)象[NSRunloop currentRunloop]執(zhí)行接收消息,調(diào)用對(duì)應(yīng)的處理函數(shù)谷异。
Runloop接收兩種源事件:input sources和timer sources分尸。
input sources傳遞異步事件,通常是來自其他線程和不同的程序中的消息歹嘹;
timer sources(定時(shí)器)傳遞同步事件(重復(fù)執(zhí)行或者在特定時(shí)間上觸發(fā))箩绍。
除了處理input sources,Runloop 也會(huì)產(chǎn)生一些關(guān)于本身行為的notificaiton尺上。注冊成為Runloop的observer材蛛,可以接收到這些notification,做一些額外的處理怎抛。(使用CoreFoundation來成為runloop的observer)卑吭。
定時(shí)源在預(yù)設(shè)的時(shí)間點(diǎn)同步方式傳遞消息,這些消息都會(huì)發(fā)生在特定時(shí)間或者重復(fù)的時(shí)間間隔马绝。定時(shí)源則直接傳遞消息給處理例程豆赏,不會(huì)立即退出run loop。
需要注意的是富稻,盡管定時(shí)器可以產(chǎn)生基于時(shí)間的通知掷邦,但它并不是實(shí)時(shí)機(jī)制。和輸入源一樣唉窃,定時(shí)器也和你的run loop的特定模式相關(guān)耙饰。如果定時(shí)器所在的模式當(dāng)前未被run loop監(jiān)視,那么定時(shí)器將不會(huì)開始直到run loop運(yùn)行在相應(yīng)的模式下纹份。類似的苟跪,如果定時(shí)器在run loop處理某一事件期間開始,定時(shí)器會(huì)一直等待直到下次run loop開始相應(yīng)的處理程序蔓涧。如果run loop不再運(yùn)行件已,那定時(shí)器也將永遠(yuǎn)不啟動(dòng)。
創(chuàng)建定時(shí)器源有兩種方法元暴,
方法一:
NSTimer *timer = [NSTimerscheduledTimerWithTimeInterval:4.0?target:self selector:@selector(backgroundThreadFire:) userInfo:nil ?repeats:YES];
[[NSRunLoop currentRunLoop]addTimer:timerforMode:NSDefaultRunLoopMode];
方法二:
[NSTimerscheduledTimerWithTimeInterval:10 ?target:self ?selector:@selector(backgroundThreadFire:)
userInfo:nil ?repeats:YES];
RunLooop與NSTimer(CFRunLoopTimerRef)
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
// 定時(shí)器只運(yùn)行在NSDefaultRunLoopMode下篷扩,一旦RunLoop進(jìn)入其他模式,這個(gè)定時(shí)器就不會(huì)工作
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
// 定時(shí)器只運(yùn)行在UITrackingRunLoopMode下(拖拽ScrollView會(huì)來到這個(gè)模式)茉盏,一旦RunLoop進(jìn)入其他模式鉴未,這個(gè)定時(shí)器就不會(huì)工作
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
// 標(biāo)記為NSRunLoopCommonModes的模式:會(huì)運(yùn)行在UITrackingRunLoopMode或NSDefaultRunLoopMode模式下(注意只能運(yùn)行在一種模式下枢冤,“或”)
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
----------
// 調(diào)用了scheduledTimer返回的定時(shí)器,已經(jīng)自動(dòng)被添加到當(dāng)前runLoop中铜秆,而且是NSDefaultRunLoopMode
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
RunLoop的事件隊(duì)列
每次運(yùn)行run loop淹真,你線程的run loop對(duì)會(huì)自動(dòng)處理之前未處理的消息,并通知相關(guān)的觀察者连茧。具體的順序如下:
因?yàn)槎〞r(shí)器和輸入源的觀察者是在相應(yīng)的事件發(fā)生之前傳遞消息核蘸,所以通知的時(shí)間和實(shí)際事件發(fā)生的時(shí)間之間可能存在誤差。如果需要精確時(shí)間控制啸驯,你可以使用休眠和喚醒通知來幫助你校對(duì)實(shí)際發(fā)生事件的時(shí)間客扎。
因?yàn)楫?dāng)你運(yùn)行run loop時(shí)定時(shí)器和其它周期性事件經(jīng)常需要被傳遞,撤銷run loop也會(huì)終止消息傳遞罚斗。典型的例子就是鼠標(biāo)路徑追蹤徙鱼。因?yàn)槟愕拇a直接獲取到消息而不是經(jīng)由程序傳遞,因此活躍的定時(shí)器不會(huì)開始直到鼠標(biāo)追蹤結(jié)束并將控制權(quán)交給程序惰聂。
Run loop可以由run loop對(duì)象顯式喚醒疆偿。其它消息也可以喚醒run loop咱筛。例如搓幌,添加新的非基于端口的源會(huì)喚醒run loop從而可以立即處理輸入源而不需要等待其他事件發(fā)生后再處理。
從這個(gè)事件隊(duì)列中可以看出:
①如果是事件到達(dá)迅箩,消息會(huì)被傳遞給相應(yīng)的處理程序來處理溉愁,?runloop處理完當(dāng)次事件后,run loop會(huì)退出饲趋,而不管之前預(yù)定的時(shí)間到了沒有拐揭。你可以重新啟動(dòng)run loop來等待下一事件。
②如果線程中有需要處理的源奕塑,但是響應(yīng)的事件沒有到來的時(shí)候堂污,線程就會(huì)休眠等待相應(yīng)事件的發(fā)生。這就是為什么run loop可以做到讓線程有工作的時(shí)候忙于工作龄砰,而沒工作的時(shí)候處于休眠狀態(tài)盟猖。
RunLoop觀察者
源是在合適的同步或異步事件發(fā)生時(shí)觸發(fā),而run loop觀察者則是在run loop本身運(yùn)行的特定時(shí)候觸發(fā)换棚。你可以使用run loop觀察者來為處理某一特定事件或是進(jìn)入休眠的線程做準(zhǔn)備式镐。你可以將run loop觀察者和以下事件關(guān)聯(lián):
1.? Runloop入口
2.? Runloop何時(shí)處理一個(gè)定時(shí)器
3.? Runloop何時(shí)處理一個(gè)輸入源
4.? Runloop何時(shí)進(jìn)入睡眠狀態(tài)
5.? Runloop何時(shí)被喚醒,但在喚醒之前要處理的事件
6.? Runloop終止
和定時(shí)器類似固蚤,在創(chuàng)建的時(shí)候你可以指定run loop觀察者可以只用一次或循環(huán)使用娘汞。若只用一次,那么在它啟動(dòng)后夕玩,會(huì)把它自己從run loop里面移除你弦,而循環(huán)的觀察者則不會(huì)惊豺。定義觀察者并把它添加到run loop,只能使用Core Fundation禽作。下面的例子演示了如何創(chuàng)建run loop的觀察者:
- (void)addObserverToCurrentRunloop
{
// The application uses garbage collection, so noautorelease pool is needed.
NSRunLoop*myRunLoop = [NSRunLoop currentRunLoop];
// Create a run loop observer and attach it to the runloop.
CFRunLoopObserverContextcontext = {0,self,NULL,NULL,NULL};
CFRunLoopObserverRef??? observer =CFRunLoopObserverCreate(kCFAllocatorDefault,
kCFRunLoopBeforeTimers,YES,0, &myRunLoopObserver, &context);
if(observer)
{
CFRunLoopRefcfLoop = [myRunLoopgetCFRunLoop];
CFRunLoopAddObserver(cfLoop, observer, kCFRunLoopDefaultMode);
}
}
其中扮叨,kCFRunLoopBeforeTimers表示選擇監(jiān)聽定時(shí)器觸發(fā)前處理事件,后面的YES表示循環(huán)監(jiān)聽领迈。
Runloop工作的特點(diǎn):
1> 當(dāng)有事件發(fā)生時(shí)彻磁,Runloop會(huì)根據(jù)具體的事件類型通知應(yīng)用程序作出響應(yīng);
2> 當(dāng)沒有事件發(fā)生時(shí)狸捅,Runloop會(huì)進(jìn)入休眠狀態(tài)衷蜓,從而達(dá)到省電的目的;
3> 當(dāng)事件再次發(fā)生時(shí)尘喝,Runloop會(huì)被重新喚醒磁浇,處理事件。
runloop與線程阻塞:
舉個(gè)例子:
定義一個(gè)NSTimer來隔一會(huì)調(diào)用某個(gè)方法? 但這時(shí)你在拖動(dòng)textVIew不放手? 主線程就被占用了朽褪。 timer的監(jiān)聽方法就不調(diào)用? 直到你松手 置吓,,這時(shí)吧nstimer加到 runloop里? 就相當(dāng)于告訴主循環(huán) 騰出點(diǎn)時(shí)間來給timer? 缔赠,再拖動(dòng)textView就不會(huì)因主線程被占用而不調(diào)用了衍锚。
runloop也可以阻塞線程,等待其他線程執(zhí)行后再執(zhí)行嗤堰。
@implementation ViewController{
BOOL end;
}
– (void)viewDidLoad
{
[super viewDidLoad];
NSLog(@”start new thread …”);
// 創(chuàng)建一個(gè)新的線程去執(zhí)行runOnNewThread方法戴质。
[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);
//向主線程發(fā)送消息(讓主線程執(zhí)行setEnd方法,改變end值)
[self performSelectorOnMainThread:@selector(setEnd) withObject:nil waitUntilDone:NO];
NSLog(@”end.”);
}
-(void)setEnd{
end=YES;
}
runloop 的API理解:
+ (NSRunLoop*)currentRunLoop;//獲得當(dāng)前線程的run loop
+ (NSRunLoop *)mainRunLoop;//獲得主線程的run loop
- (void)run;//進(jìn)入處理事件循環(huán)踢匣,如果沒有事件則立刻返回告匠。注意:主線程上調(diào)用這個(gè)方法會(huì)導(dǎo)致無法返回(進(jìn)入無限循環(huán),雖然不會(huì)阻塞主線程)离唬,因?yàn)橹骶€程一般總是會(huì)有事件處理后专。
- (void)runUntilDate:(NSDate*)limitDate;//同run方法,增加超時(shí)參數(shù)limitDate输莺,避免進(jìn)入無限循環(huán)戚哎。使用在UI線程(亦即主線程)上,可以達(dá)到暫停的效果模闲。
- (BOOL)runMode:(NSString*)mode beforeDate:(NSDate*)limitDate;//等待消息處理建瘫,好比在PC終端窗口上等待鍵盤輸入。一旦有合適事件(mode相當(dāng)于定義了事件的類型)被處理了尸折,則立刻返回啰脚;類同run方法,如果沒有事件處理也立刻返回;有否事件處理由返回布爾值判斷橄浓。同樣limitDate為超時(shí)參數(shù)粒梦。
- (void)acceptInputForMode:(NSString*)mode beforeDate:(NSDate*)limitDate;//似乎和runMode:差不多(測試過是這種結(jié)果,但確定是否有其它特殊情況下的不同)荸实,沒有BOOL返回值匀们。
下面羅列調(diào)用主線程的run loop的各種方式,讀者可以加深理解:
[[NSRunLoop mainRunLoop] run];//主線程永遠(yuǎn)等待准给,但讓出主線程時(shí)間片
[[NSRunLoopmainRunLoop]runUntilDate:[NSDatedistantFuture]];//等同上面調(diào)用
[[NSRunLoopmainRunLoop]runUntilDate:[NSDatedate]];//立即返回
[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:10.0]];//主線程等待泄朴,但讓出主線程時(shí)間片,然后過10秒后返回
[[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate: [NSDate distantFuture]];//主線程等待露氮,但讓出主線程時(shí)間片祖灰;有事件到達(dá)就返回,比如點(diǎn)擊UI等畔规。
[[NSRunLoopmainRunLoop]runMode:NSDefaultRunLoopModebeforeDate: [NSDatedate]];//立即返回
[[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate: [NSDate dateWithTimeIntervalSinceNow:10.0]];//主線程等待局扶,但讓出主線程時(shí)間片;有事件到達(dá)就返回叁扫,如果沒有則過10秒返回三妈。
參考:http://blog.csdn.net/mideveloper/article/details/46310067