來源:iOS 模塊注解—「Runtime面試、工作」看我就 ?? 了 ^_^. - 簡書
iOS多線程--徹底學(xué)會(huì)多線程之『RunLoop』 | 不羈閣 | Walking Boy's Blog
RunLoop
RunLoop實(shí)際上是一個(gè)對(duì)象,這個(gè)對(duì)象在循環(huán)中用來處理程序運(yùn)行過程中出現(xiàn)的各種事件(比如說觸摸事件、UI刷新事件、定時(shí)器事件、Selector事件),從而保持程序的持續(xù)運(yùn)行;而且在沒有事件處理的時(shí)候,會(huì)進(jìn)入睡眠模式,從而節(jié)省CPU資源,提高程序性能。
RunLoop在循環(huán)中會(huì)不斷檢測,通過Input sources(輸入源)和Timer sources(定時(shí)源)兩種來源等待接受事件;然后對(duì)接受到的事件通知線程進(jìn)行處理,并在沒有事件的時(shí)候進(jìn)行休息。

CFRunLoopSourceRef
CFRunLoopSourceRef是事件源(RunLoop模型圖中提到過),CFRunLoopSourceRef有兩種分類方法。
第一種按照官方文檔來分類(就像RunLoop模型圖中那樣):
Port-Based Sources(基于端口)
Custom Input Sources(自定義)
Cocoa Perform Selector Sources
第二種按照函數(shù)調(diào)用棧來分類:
Source0 :非基于Port
Source1:基于Port,通過內(nèi)核和其他線程通信,接收、分發(fā)系統(tǒng)事件
?CFRunLoopObserverRef
CFRunLoopObserverRef是觀察者,用來監(jiān)聽RunLoop的狀態(tài)改變
CFRunLoopObserverRef可以監(jiān)聽的狀態(tài)改變有以下幾種:
typedefCF_OPTIONS(CFOptionFlags,CFRunLoopActivity) {??
? kCFRunLoopEntry = (1UL <<0),// 即將進(jìn)入Loop:1
kCFRunLoopBeforeTimers = (1UL <<1),// 即將處理Timer:2?
?kCFRunLoopBeforeSources = (1UL <<2),// 即將處理Source:4
kCFRunLoopBeforeWaiting = (1UL <<5),// 即將進(jìn)入休眠:32
kCFRunLoopAfterWaiting = (1UL <<6),// 即將從休眠中喚醒:64
kCFRunLoopExit = (1UL <<7),// 即將從Loop中退出:128
kCFRunLoopAllActivities =0x0FFFFFFFU// 監(jiān)聽全部狀態(tài)改變?
?};
后臺(tái)常駐線程(很常用)
我們?cè)陂_發(fā)應(yīng)用程序的過程中,如果后臺(tái)操作特別頻繁,經(jīng)常會(huì)在子線程做一些耗時(shí)操作(下載文件、后臺(tái)播放音樂等),我們最好能讓這條線程永遠(yuǎn)常駐內(nèi)存。
那么怎么做呢?
添加一條用于常駐內(nèi)存的強(qiáng)引用的子線程,在該線程的RunLoop下添加一個(gè)Sources,開啟RunLoop。
- (void)viewDidLoad { [super viewDidLoad];
// 創(chuàng)建線程,并調(diào)用run1方法執(zhí)行任務(wù)
self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(run1) object:nil];
// 開啟線程 [self.thread start];
}
- (void) run1{
// 這里寫任務(wù) NSLog(@"----run1-----");
// 添加下邊兩句代碼,就可以開啟RunLoop,之后self.thread就變成了常駐線程,可隨時(shí)添加任務(wù),并交于RunLoop處理
[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode]; [[NSRunLoop currentRunLoop] run];
// 測試是否開啟了RunLoop,如果開啟RunLoop,則來不了這里,因?yàn)镽unLoop開啟了循環(huán)。 NSLog(@"未開啟RunLoop");
} ?
第二種方法
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
// 利用performSelector,在self.thread的線程中調(diào)用run2方法執(zhí)行任務(wù)
[self performSelector:@selector(run2) onThread:self.thread withObject:nil waitUntilDone:NO];
}
- (void) run2{ NSLog(@"----run2------");} ?
RunLoop總結(jié):RunLoop的應(yīng)用場景(四)App卡頓監(jiān)測 - 簡書
主線程的RunLoop是在應(yīng)用啟動(dòng)時(shí)自動(dòng)開啟的,也沒有超時(shí)時(shí)間,所以正常情況下,主線程的RunLoop 只會(huì)在 2---9 之間無限循環(huán)下去。
那么,我們只需要在主線程的RunLoop中添加一個(gè)observer,檢測從kCFRunLoopBeforeSources到kCFRunLoopBeforeWaiting花費(fèi)的時(shí)間 是否過長。如果花費(fèi)的時(shí)間大于某一個(gè)闕值,我們就認(rèn)為有卡頓,并把當(dāng)前的線程堆棧轉(zhuǎn)儲(chǔ)到文件中,并在以后某個(gè)合適的時(shí)間,將卡頓信息文件上傳到服務(wù)器。
RunLoop總結(jié):RunLoop的應(yīng)用場景(五)阻止App崩潰一次 - 簡書
iOS應(yīng)用崩潰,常見的崩潰信息有EXC_BAD_ACCESS、SIGABRT XXXXXXX,而這里分為兩種情況,一種是未被捕獲的異常,我們只需要添加一個(gè)回調(diào)函數(shù),并在應(yīng)用啟動(dòng)時(shí)調(diào)用一個(gè) API即可;另一種是直接發(fā)送的SIGABRT XXXXXXX,這里我們也需要監(jiān)聽各種信號(hào),然后添加回調(diào)函數(shù)。
針對(duì)情況一,其實(shí)我們都見過。我們?cè)谑占疉pp崩潰信息時(shí),需要添加一個(gè)函數(shù)NSSetUncaughtExceptionHandler(&HandleException),參數(shù) 是一個(gè)回調(diào)函數(shù),在回調(diào)函數(shù)里獲取到異常的原因,當(dāng)前的堆棧信息等保存到 dump文件,然后供下次打開App時(shí)上傳到服務(wù)器。
其實(shí),我們?cè)贖andleException回調(diào)函數(shù)中,可以獲取到當(dāng)前的RunLoop,然后獲取該RunLoop中的所有Mode,手動(dòng)運(yùn)行一遍。
針對(duì)情況二,首先針對(duì)多種要捕獲的信號(hào),設(shè)置好回調(diào)函數(shù),然后也是在回調(diào)函數(shù)中獲取RunLoop,然后拿到所有的Mode,手動(dòng)運(yùn)行一遍。
CFRunLoopRefrunLoop =CFRunLoopGetCurrent();
CFArrayRefallModes =CFRunLoopCopyAllModes(runLoop);
while(!ignore) {
for(NSString*modein(__bridgeNSArray*)allModes) {
CFRunLoopRunInMode((CFStringRef)mode,0.001,false);?
?} }
CFRelease(allModes);
1
問題:runloop是來做什么的?runloop和線程有什么關(guān)系?主線程默認(rèn)開啟了runloop么?子線程呢?
解答:runloop: 從字面意思看:運(yùn)行循環(huán)、跑圈,其實(shí)它內(nèi)部就是do-while循環(huán),在這個(gè)循環(huán)內(nèi)部不斷地處理各種任務(wù)(比如Source、Timer、Observer)事件。runloop和線程的關(guān)系:一個(gè)線程對(duì)應(yīng)一個(gè)RunLoop,主線程的RunLoop默認(rèn)創(chuàng)建并啟動(dòng),子線程的RunLoop需手動(dòng)創(chuàng)建且手動(dòng)啟動(dòng)(調(diào)用run方法)。RunLoop只能選擇一個(gè)Mode啟動(dòng),如果當(dāng)前Mode中沒有任何Source(Sources0、Sources1
)、Timer,那么就直接退出RunLoop。
02
問題:runloop的mode是用來做什么的?有幾種mode?
解答:model:是runloop里面的運(yùn)行模式,不同的模式下的runloop處理的事件和消息有一定的差別。系統(tǒng)默認(rèn)注冊(cè)了5個(gè)Mode:(1)kCFRunLoopDefaultMode: App的默認(rèn) Mode,通常主線程是在這個(gè) Mode 下運(yùn)行的。(2)UITrackingRunLoopMode: 界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動(dòng),保證界面滑動(dòng)時(shí)不受其他 Mode 影響。(3)UIInitializationRunLoopMode: 在剛啟動(dòng) App 時(shí)第進(jìn)入的第一個(gè) Mode,啟動(dòng)完成后就不再使用。(4)GSEventReceiveRunLoopMode: 接受系統(tǒng)事件的內(nèi)部 Mode,通常用不到。(5)kCFRunLoopCommonModes: 這是一個(gè)占位的 Mode,沒有實(shí)際作用。注意iOS 對(duì)以上5中model進(jìn)行了封裝 NSDefaultRunLoopMode、NSRunLoopCommonModes
03
問題:為什么把NSTimer對(duì)象以NSDefaultRunLoopMode(kCFRunLoopDefaultMode)添加到主運(yùn)行循環(huán)以后,滑動(dòng)scrollview的時(shí)候NSTimer卻不動(dòng)了?
解答:
在尋找NSRunLoopCommonModes和NSDefaultRunLoopMode區(qū)別時(shí)發(fā)現(xiàn) - CSDN博客
當(dāng)實(shí)例化NSTimer對(duì)象的時(shí)候,通常會(huì)使用 scheduledTimerWithTimeInterval 方法。該方法會(huì)自動(dòng)為我們實(shí)例化的timer添加到當(dāng)前線程的RunLoop中,并且默認(rèn)模式是?NSDefaultRunLoopMode。但當(dāng)前線程是主線程時(shí),某些UI事件,比如ScrollView正在拖動(dòng),將會(huì)RunLoop切換成 NSEventTrackingRunLoopMode 模式,在這個(gè)模式下,默認(rèn)的?NSDefaultRunLoopMode 模式中注冊(cè)的事件是不會(huì)執(zhí)行的。也就是說,使用?scheduledTimerWithTimeInterval 方法添加到RunLoop中的Timer就不會(huì)執(zhí)行。
為了設(shè)置一個(gè)不被UI干擾的Timer,我們需要手動(dòng)創(chuàng)建一個(gè)Timer,然后使用RunLoop的 addTimer:forMode: 方法來把Timer按照指定的模式加入到RunLoop中。這里使用?NSRunLoopCommonModes 模式,這個(gè)模式相當(dāng)于?NSDefaultRunLoopMode 和?NSEventTrackingRunLoopMode 的結(jié)合。
1、如果是在主線程中運(yùn)行timer,想要timer在某界面有視圖滾動(dòng)時(shí),依然能正常運(yùn)轉(zhuǎn),那么將timer添加到RunLoop中時(shí),就需要設(shè)置mode 為NSRunLoopCommonModes。
2、如果是在子線程中運(yùn)行timer,那么將timer添加到RunLoop中后,Mode設(shè)置為NSDefaultRunLoopMode或NSRunLoopCommonModes均可,但是需要保證RunLoop在運(yùn)行,且其中有任務(wù)。
作者:Haley_Wong
鏈接:http://www.itdecent.cn/p/b2d431d6fa09
來源:簡書
簡書著作權(quán)歸作者所有,任何形式的轉(zhuǎn)載都請(qǐng)聯(lián)系作者獲得授權(quán)并注明出處。
04
問題:蘋果是如何實(shí)現(xiàn)Autorelease Pool的?
解答:Autorelease Pool作用:緩存池,可以避免我們經(jīng)常寫relase的一種方式。其實(shí)就是延遲release,將創(chuàng)建的對(duì)象,添加到最近的autoreleasePool中,等到autoreleasePool作用域結(jié)束的時(shí)候,會(huì)將里面所有的對(duì)象的引用計(jì)數(shù)器 - autorelease.

