Run Loop,感覺神一般的存在纸兔,這么久了惰瓜,也沒搞懂個(gè)所以然,希望這次能夠了解些皮毛:嚎蟆F榉弧!
這里是官方地址洲拇!
Run loop是線程運(yùn)行的基礎(chǔ)奈揍,run loop的目的是為了保證讓你空閑的線程忙碌起來,或者在沒有事情可做的時(shí)候赋续,讓線程保持睡眠男翰。
Run loop不是完全的原子的。需要我們在合適的時(shí)機(jī)去啟動(dòng)線程和處理事件纽乱。每一個(gè)線程都有一個(gè)run loop對象蛾绎,但是只有主線程的run loop是啟動(dòng)的,其他線程的都需要手動(dòng)啟動(dòng)鸦列。
一租冠、解剖Run Loop
run loop和它的字面意思很像。它表示一個(gè)循環(huán)薯嗤,讓您的線程進(jìn)入運(yùn)行事件的處理程序和響應(yīng)傳入的事件顽爹。您的代碼提供了控制語句去實(shí)現(xiàn)真實(shí)的run loop,換句話說骆姐,您的代碼里有類似于while或者for循環(huán)之類的去驅(qū)動(dòng)run loop镜粤。
run loop的事件源有兩種:
- 輸入源,分發(fā)異步事件诲锹,事件來源于其他線程或者其他程序
- 定時(shí)器繁仁,分發(fā)同步事件,事件由預(yù)定時(shí)間或者循環(huán)重復(fù)的事件觸發(fā)
下圖是runloop的結(jié)構(gòu)圖:
在該圖中归园,輸入源分發(fā)異步事件到相應(yīng)的(Port對應(yīng)handlePort黄虱、Custom對應(yīng)customSrc:等等)事件處理程序里并觸發(fā)
runUntilDate:
方法去結(jié)束runloop;定時(shí)器分發(fā)事件到相應(yīng)的事件處理程序里但是不會(huì)觸發(fā)runUntilDate:
去結(jié)束runloop(庸诱?捻浦?晤揣?)
除了處理輸入的事件,runloop還會(huì)在runloop的不同時(shí)間段發(fā)送通知朱灿,我們可以注冊相關(guān)的runloop通知來做一些額外的事情昧识。您應(yīng)該使用Core Foundation去注冊runloop的觀察者(NSNotification是Foundation下的)。
在接下來的章節(jié)中將會(huì)對runloop做進(jìn)一步的說明盗扒。
1跪楞、Run Loop的Modes
runloop的mode是輸入源和定時(shí)器源的集合和runloop的觀察者接收通知的集合。每次您運(yùn)行runloop侣灶,您都會(huì)指定一個(gè)mode(無論是明確地還是含蓄地)甸祭。在runloop運(yùn)行的過程中,只有事件源模式和該runloop指定模式匹配的事件源才會(huì)被監(jiān)控和分發(fā)它的事件褥影。(相似的池户,只有觀察者的模式和runloop的模式匹配的才能接收到通知)模式不匹配的只能處在等待狀態(tài)。
2凡怎、輸入源
輸入源異步分發(fā)事件到你的線程校焦。事件來源取決于輸入源的類型⊥车梗基于端口輸入源監(jiān)控你的程序的Mach端口寨典。自定義輸入源監(jiān)控自定義的事件。對于runloop來說檐薯,它不關(guān)心你的輸入源是哪種類型凝赛,系統(tǒng)默認(rèn)都實(shí)現(xiàn)了這兩種輸入源的事件的處理程序。這兩個(gè)事件的唯一的不同之處是它們?nèi)绾伟l(fā)出信號的坛缕∧沽裕基于端口輸入源由其內(nèi)核表明其類型,自定義事件必須手動(dòng)表明其類型赚楚。
當(dāng)你創(chuàng)建了一個(gè)輸入源毙沾,你要做好準(zhǔn)備,讓它在一個(gè)或者多個(gè)模式的runloop下正常運(yùn)行宠页。runloop一直在監(jiān)控著輸入源的mode左胞,一旦發(fā)生了模式變化,就會(huì)發(fā)生事件掛起举户,一直到mode匹配了才會(huì)再次觸發(fā)烤宙。
(1) 基于端口的源
蘋果Cocoa和Core Foundation框架提供了和端口相關(guān)的接口去創(chuàng)建端口對象。
比如俭嘁,在Cocoa框架里躺枕,你不能直接去創(chuàng)建一個(gè)輸入源的事件,你只能使用NSPort來簡單的創(chuàng)建一個(gè)端口對象并添加到runloop中拐云。該端口對象負(fù)責(zé)創(chuàng)建和配置輸入源需要的東西罢猪。
在Core Foundation框架下,你必須手動(dòng)創(chuàng)建端口和runloop叉瘩。在這兩種情況下膳帕,你需要使用相應(yīng)的端口方法(CFMachPortRef, CFMessagePortRef, 和 CFSocketRef
)去創(chuàng)建合適的端口對象。
具體的使用在后面薇缅!
(2) 自定義輸入源
你需要使用類CFRunLoopSourceRef
去創(chuàng)建一個(gè)自定義輸入源危彩,可以使用一些回調(diào)函數(shù)去處理事件,因?yàn)樵诓煌臅r(shí)間段泳桦,會(huì)調(diào)用這些回調(diào)函數(shù)去注冊事件源恬砂、事件和從runloop移除該輸入源。
除了定義接收到自定義事件后的一些行為蓬痒,你必須定義事件的分發(fā)機(jī)制。輸入源的該部分運(yùn)行在一個(gè)單獨(dú)的線程上漆羔,負(fù)責(zé)為輸入源提供它的數(shù)據(jù)梧奢,并在數(shù)據(jù)準(zhǔn)備好處理時(shí)發(fā)出信號。事件的交付機(jī)制由你決定演痒,但是不要太過于復(fù)雜亲轨。
具體的使用在后面!
(3) Cocoa可執(zhí)行選擇源(Selector Sources)
除了基于端口的輸入源鸟顺,Cocoa定義了一個(gè)自定義的輸入源惦蚊,允許在任何線程上運(yùn)行。和基于端口的輸入源一樣讯嫂,執(zhí)行 selector請求是在目標(biāo)線程上序列化的蹦锋,從而減輕了在一個(gè)線程上運(yùn)行多個(gè)方法時(shí)可能出現(xiàn)的許多同步問題。和基于端口的輸入源不同的是欧芽,執(zhí)行 selector完畢后會(huì)自動(dòng)從runloop移除莉掂。
當(dāng)在其他線程上執(zhí)行selector時(shí),目標(biāo)線程必須有一個(gè)可活躍的runloop千扔。對于你創(chuàng)建的線程憎妙,在代碼里需要明確的去啟動(dòng)runloop。因?yàn)樵谥骶€程里是自動(dòng)啟動(dòng)的曲楚,所以在程序執(zhí)行完applicationDidFinishLaunching:
方法后厘唾,你就可以在主線程上處理事情。
runloop在每次循環(huán)時(shí)都會(huì)處理所有的在隊(duì)列中的selector龙誊,而不是只處理一次抚垃。
下面是幾個(gè)自帶的selector:
-
performSelectorOnMainThread:withObject:waitUntilDone:和performSelectorOnMainThread:withObject:waitUntilDone:modes:
在程序的主線程上執(zhí)行,如果錯(cuò)過了當(dāng)前一次runloop循環(huán),會(huì)在下次循環(huán)中被立即執(zhí)行讯柔;其中waitUntilDone的參數(shù)表示添加到runloop中時(shí)是否阻塞當(dāng)前線程(主線程)抡蛙,yes表示阻塞,no表示不阻塞魂迄。過程如下:
- (void)viewDidLoad {
[super viewDidLoad];
self.edgesForExtendedLayout = UIRectEdgeNone;
self.view.backgroundColor = [UIColor whiteColor];
self.title = @"Run Loop";
[self executePerfomSelectorOnMainThread];
}
- (void)executePerfomSelectorOnMainThread {
[self performSelectorOnMainThreadWithWait];
[self performSelectorOnMainThreadWithNoWait];
}
- (void)performSelectorOnMainThreadWithWait {
[self performSelectorOnMainThread:@selector(logEventForWait:) withObject:@"1" waitUntilDone:YES];
NSLog(@"I am here for Wait");
}
- (void)performSelectorOnMainThreadWithNoWait {
[self performSelectorOnMainThread:@selector(logEventForNoWait:) withObject:@"1" waitUntilDone:NO];
NSLog(@"I am here for no Wait");
}
- (void)logEventForWait:(NSString *)infoStr {
NSLog(@"%@", infoStr);
sleep(2);
}
- (void)logEventForNoWait:(NSString *)infoStr {
NSLog(@"%@", infoStr);
sleep(2);
}
結(jié)果如下:這點(diǎn)和NSNotification很像
performSelector:onThread:withObject:waitUntilDone:和performSelector:onThread:withObject:waitUntilDone:modes:
在任何自定義的線程上執(zhí)行performSelector:withObject:afterDelay:和performSelector:withObject:afterDelay:inModes:
在當(dāng)前線程上執(zhí)行粗截,需要等到下一個(gè)runloop的循環(huán),它的執(zhí)行是阻塞當(dāng)前線程執(zhí)行的捣炬,但是添加是不阻塞的熊昌。另外,它的延遲運(yùn)行時(shí)間也不能做到按時(shí)出發(fā)湿酸,只能盡可能以最靠近設(shè)置的延遲觸發(fā)婿屹。這里會(huì)啟動(dòng)一個(gè)定時(shí)器。
如下:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self executePerfomSelectorOnCurrentThread];
[self executePerfomSelectorOnMainThread];
}
- (void)executePerfomSelectorOnCurrentThread {
[self performSelector:@selector(logEventForPerfomSelectorOnCurrentThreadForSleep:) withObject:@"1-1" afterDelay:1];
NSLog(@"after delay 1-1");
[self performSelector:@selector(logEventForPerfomSelectorOnCurrentThread:) withObject:@"1" afterDelay:1];
[self performSelector:@selector(logEventForPerfomSelectorOnCurrentThread:) withObject:@"0" afterDelay:0];
}
- (void)logEventForPerfomSelectorOnCurrentThread:(NSString *)infoStr {
NSLog(@"%s--%@--info:%@",__func__,[NSThread currentThread],infoStr);
}
- (void)logEventForPerfomSelectorOnCurrentThreadForSleep:(NSString *)infoStr {
NSLog(@"before:%s--%@--info:%@",__func__,[NSThread currentThread],infoStr);
sleep(2);
NSLog(@"after:%s--%@--info:%@",__func__,[NSThread currentThread],infoStr);
}
結(jié)果如下:1推溃、可以發(fā)現(xiàn)昂利,上面在添加的時(shí)候,不會(huì)阻塞當(dāng)前線程的執(zhí)行
2铁坎、它的運(yùn)行不是準(zhǔn)時(shí)的蜂奸,比如上面的兩個(gè)都是延遲1秒,但是第一個(gè)(1-1)沒有執(zhí)行結(jié)束硬萍,那么另一個(gè)就不會(huì)進(jìn)行
3扩所、這里還調(diào)用了方法performSelectorOnMainThread:withObject:waitUntilDone:和performSelectorOnMainThread:withObject:waitUntilDone:modes:
,該方法的優(yōu)先級要高很多朴乖,雖然它是后面添加的祖屏,但是它最先執(zhí)行
4、如果添加一個(gè)1秒的睡眠买羞,又會(huì)不一樣
[self performSelector:@selector(logEventForPerfomSelectorOnCurrentThreadForSleep:) withObject:@"1-1" afterDelay:1];
NSLog(@"after delay 1-1");
[self performSelector:@selector(logEventForPerfomSelectorOnCurrentThread:) withObject:@"1" afterDelay:1];
sleep(1);
[self performSelector:@selector(logEventForPerfomSelectorOnCurrentThread:) withObject:@"0" afterDelay:0];
}
-
cancelPreviousPerformRequestsWithTarget:和cancelPreviousPerformRequestsWithTarget:selector:object:
用來暫停上面的帶有延遲的方法袁勺,但是只移除當(dāng)前runloop中的,不回移除其他runloop中的哩都。
3魁兼、時(shí)間源(定時(shí)器)
時(shí)間源會(huì)在你預(yù)設(shè)的未來的某個(gè)時(shí)間同步的把事件分發(fā)到你的線程上。定時(shí)器是通知線程自己去做一些事情的很好的例子漠嵌。
盡管它是基于定時(shí)器的通知咐汞,但是它的并不是驅(qū)動(dòng)事件運(yùn)行的機(jī)制。時(shí)間源的運(yùn)行機(jī)制和事件源的機(jī)制是一樣的:需要模式mode匹配儒鹿;如果當(dāng)前runloop正在執(zhí)行任務(wù)化撕,它需要等待。
你可以一次或者多次注冊時(shí)間源去處理事情约炎。一個(gè)重復(fù)的計(jì)時(shí)器會(huì)自動(dòng)根據(jù)預(yù)定的發(fā)射時(shí)間自動(dòng)調(diào)整植阴,也就是說真正的觸發(fā)時(shí)間并不是你設(shè)置的時(shí)間蟹瘾。
4、Run Loop的觀察者
和在合適的時(shí)機(jī)觸發(fā)異步或者同步事件發(fā)生的輸入源相比掠手,runloop的觀察者能在一些特定的時(shí)機(jī)被觸發(fā)憾朴。你可以利用runloop的這個(gè)特性去處理事件或者在線程睡眠前作一些準(zhǔn)備工作∨绺耄可以將以下的事件通過runloop的觀察者和實(shí)際情況聯(lián)系起來:
- runloop的入口
- runloop將處理計(jì)時(shí)器(時(shí)間源)timer
- runloop將處理輸入源
- runloop將進(jìn)入睡眠
- runloop將被喚醒
- runloop退出
你可以使用Core Foundation添加runloop的觀察者众雷。你可以通過創(chuàng)建CFRunLoopObserverRef
的實(shí)例來創(chuàng)建和添加runloop的觀察者。它會(huì)對你自定義的回調(diào)函數(shù)或者它感興趣的活動(dòng)進(jìn)行跟蹤做祝。
和時(shí)間源(定時(shí)器)相似砾省,runloop的觀察者可以使用一次或者重復(fù)使用多次,只觀察一次的觀察者會(huì)在它運(yùn)行完后被移除混槐,但是可重復(fù)的runloop觀察者不會(huì)被移除编兄。當(dāng)然runloop的觀察者可被執(zhí)行幾次,是由你決定的声登。
5狠鸳、Run Loop事件執(zhí)行的順序
每次運(yùn)行線程,runloop執(zhí)行一些還在等待處理的事件和給觀察者發(fā)送通知悯嗓。它所做的事情的執(zhí)行順序如下所示:
1碰煌、 告訴觀察者,要開始進(jìn)入runloop了
2绅作、 告訴觀察者,所有的時(shí)間源(定時(shí)器)都準(zhǔn)備好了處理到來的事件
3蛾派、 告訴觀察者俄认,所有的輸入源除了基于端口的輸入源都準(zhǔn)備好了去處理到來的事件
4、 執(zhí)行任何已經(jīng)做好準(zhǔn)備的輸入源(不包括基于端口的輸入源)
5洪乍、 如果有基于端口的輸入源已經(jīng)準(zhǔn)備好了并在等待觸發(fā)眯杏,立馬執(zhí)行,并進(jìn)入第9步
6、告訴觀察者線程將要睡眠了
7壳澳、讓線程進(jìn)入睡眠,一直到出現(xiàn)下面的幾種情況里的一種巷波,線程才被重新喚醒:
a萎津、一個(gè)基于端口的輸入源事件到達(dá)了
b、時(shí)間源(定時(shí)器)啟動(dòng)
c抹镊、為runloop設(shè)置的超時(shí)時(shí)間過期了
d锉屈、明確的喚醒runloop
8、告訴觀察者垮耳,線程被喚醒了颈渊,但是runloop沒啟動(dòng)
9遂黍、處理等待處理的事件:
a、如果定時(shí)器啟動(dòng)了俊嗽,則處理時(shí)間源事件并重啟runloop雾家,進(jìn)入步驟2
b、如果有輸入源被觸發(fā)绍豁,則分發(fā)事件
c芯咧、如果runloop被明確喚醒并且沒有超時(shí),進(jìn)入步驟2
10妹田、告訴觀察者唬党,runloop退出了
因?yàn)橥ㄖ窃诙〞r(shí)器和輸入源被實(shí)際觸發(fā)前發(fā)送的,所以在通知的發(fā)送和事件的真實(shí)執(zhí)行之間會(huì)有一個(gè)時(shí)間間隙鬼佣。如果在這些事件間的時(shí)間很重要驶拱,可以使用sleep通知和awake通知去讓彼此關(guān)聯(lián)起來。
因?yàn)槎〞r(shí)器和其他的周期性的事件是在運(yùn)行runloop的時(shí)候分發(fā)的晶衷,因此如果繞過該runloop就會(huì)打亂那些事件的分發(fā)蹬挤。
一個(gè)runloop時(shí)可以通過runloop的對象被直接喚醒的,其他事件也可能導(dǎo)致runloop被喚醒像云。(如上面的第7步)
二境肾、什么時(shí)候該使用Run Loop
一般在你自己創(chuàng)建了一個(gè)線程的時(shí)候需要顯式的啟動(dòng)runloop,因?yàn)槌绦蛑械闹骶€程是默認(rèn)啟動(dòng)的锹漱,所以不需要也不希望你去顯示的啟動(dòng)箭养。
對于自己創(chuàng)建的線程,你需要自己決定是否需要runloop哥牍,如果需要毕泌,那么創(chuàng)建它并啟動(dòng)它。然而并不是所有你創(chuàng)建的線程的runloop都需要啟動(dòng)嗅辣。例如撼泛,如果您使用線程來執(zhí)行一些長時(shí)間運(yùn)行和預(yù)定義的任務(wù),則不需要啟動(dòng)runloop澡谭。runloop時(shí)為了那些需要和線程有很多交互的場景愿题,比如下面的幾種情況:
- 使用端口或者自定義輸入源和其他線程交互
- 在線程上使用定時(shí)器
- 使用任何類似于
performSelector…
方法的場景 - 保持線程執(zhí)行定期任務(wù)
如果你決定去使用runloop,那么配置和設(shè)置很簡單蛙奖。和所有的線程一樣潘酗,你應(yīng)該有一個(gè)計(jì)劃在合適的時(shí)機(jī)去退出線程,退出一個(gè)線程要比強(qiáng)制終止一個(gè)線程更好雁仲。