Runloop

來源: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.


timer方法不會(huì)走


正確寫法
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 說明iOS中的RunLoop使用場景1.保持線程的存活,而不是線性的執(zhí)行完任務(wù)就退出了<1>不開啟RunLoop的...
    野生塔塔醬閱讀 6,921評(píng)論 15 109
  • 1 Runloop機(jī)制原理 深入理解RunLoop http://www.cocoachina.com/ios/2...
    Kevin_Junbaozi閱讀 4,239評(píng)論 4 30
  • 1.不開啟RunLoop的線程在遇到一些耗時(shí)操作時(shí),為了避免主線程阻塞導(dǎo)致界面卡頓,影響用戶體驗(yàn),往往我們會(huì)把這些...
    shinedada閱讀 416評(píng)論 0 2
  • iOS刨根問底-深入理解RunLoop 2017-05-08 10:35 by KenshinCui 概述 Run...
    mengjz閱讀 1,640評(píng)論 1 10
  • Runloop 是和線程緊密相關(guān)的一個(gè)基礎(chǔ)組件,是很多線程有關(guān)功能的幕后功臣。盡管在平常使用中幾乎不太會(huì)直接用到,...
    jackyshan閱讀 10,009評(píng)論 10 75

友情鏈接更多精彩內(nèi)容