RunLoop

CFRunLoop

  • 這篇文章是在看了sunnyxx大神的線下分享后整理的學(xué)習(xí)筆記廷雅,感謝sunnyxx大神的分享,學(xué)習(xí)路上再接再厲。
  • sunnyxx大神的自動(dòng)算高工具UITableView-FDTemplateLayoutCell堵漱,里面有RunLoop的使用技巧。
    https://github.com/forkingdog/UITableView-FDTemplateLayoutCell

概念

  • 事件循環(huán)

  • 每個(gè)線程都有一個(gè)RunLoop對(duì)象涣仿,但是只有主線程的RunLoop是開(kāi)啟的勤庐。子線程中的RunLoop默認(rèn)是不被創(chuàng)建的,在子線程中當(dāng)我們調(diào)用NSRunLoop *runloop = [NSRunLoop currentRunLoop];獲取RunLoop對(duì)象的時(shí)候好港,就會(huì)創(chuàng)建RunLoop

  • 一個(gè)線程可以開(kāi)啟多個(gè)RunLoop愉镰,只不過(guò)都是嵌套在最大的RunLoop中

  • 作用

  • 使程序一直運(yùn)行并接收用戶的輸入

  • 決定程序在何時(shí)處理哪些事件

  • 調(diào)用解耦(主調(diào)方產(chǎn)生很多事件,不用等到被調(diào)方處理完事件之后钧汹,才能執(zhí)行其他操作)

  • 節(jié)省CPU時(shí)間(當(dāng)程序啟動(dòng)后丈探,什么都沒(méi)有執(zhí)行的話,就不用讓CPU來(lái)消耗資源來(lái)執(zhí)行崭孤,直接進(jìn)入睡眠狀態(tài))

模擬RunLoop


int main(int argc, char * argv[]) {

while (程序在運(yùn)行中) {

runloop睡覺(jué)呢

起床了类嗤,有事干了(喚醒runloop)

runloop干活中

}

return 0;

}

構(gòu)成元素

  • 每一個(gè)RunLoop都包含若干個(gè)CFRunLoopMode

  • 在同一時(shí)間,只能在一種Mode下面執(zhí)行

  • 當(dāng)需要切換Mode的時(shí)候辨宠,就必須退出當(dāng)前的RunLoop。重新啟動(dòng)一個(gè)

  • 系統(tǒng)默認(rèn)的有以下5種模式

  1. CFRunLoopDefaultMode: 這個(gè)是默認(rèn) Mode货裹,也是空閑狀態(tài)嗤形。主線程通常在這個(gè) Mode 下運(yùn)行的。

  2. UITrackingRunLoopMode: ScrollView滾動(dòng)時(shí)候的模式弧圆。

  3. UIInitializationRunLoopMode: 在剛啟動(dòng)程序時(shí)進(jìn)入的第一個(gè) Mode赋兵,啟動(dòng)完成后就不再使用。

  4. GSEventReceiveRunLoopMode: 接受系統(tǒng)事件的內(nèi)部的Mode搔预,這個(gè)Mode由GraphicsServices調(diào)用在CFRunLoopRunSpecific前面霹期。通常用不到。

  5. CFRunLoopCommonModes: 這是一個(gè)數(shù)組拯田,包括了第1和第2種模式历造。

  • CFRunLoopMode的應(yīng)用舉例

當(dāng)我們?cè)谧鰣D片輪播器的時(shí)候,如果使用的是kCFRunLoopDefaultMode那么當(dāng)ScrollView滾動(dòng)的時(shí)候,RunLoop模式就會(huì)切換為UITrackingRunLoopMode吭产,這時(shí)候NSTimer就沒(méi)法執(zhí)行侣监,這時(shí)候我們可以使用kCFRunLoopCommonModes,就可以解決這個(gè)問(wèn)題臣淤。

  • CFRunLoopMode又包含若干個(gè)CFRunLoopSource\ CFRunLoopTimer\ CFRunLoopObserver

  • CFRunLoopSource

  • RunLoop的數(shù)據(jù)源抽象類(類似于OC中的protocol)

  • RunLoop定義了兩個(gè)版本的source:Source0 和 Source1

  1. Source0:處理的是App內(nèi)部的事件橄霉、App自己負(fù)責(zé)管理,如按鈕點(diǎn)擊事件等邑蒋。

  2. Source1:由RunLoop和內(nèi)核管理姓蜂,Mach Port驅(qū)動(dòng),如CFMachPort医吊、CFMessagePort

  • CFRunLoopTimer的封裝有(只是舉例幾個(gè))

+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;


+ (NSTimer *)scheduledTimerWithTimeInterval:    (NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;


- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay


+ (CADisplayLink *)displayLinkWithTarget:(id)target selector:(SEL)sel;

  • CFRunLoopObserver

  • 作用:告知外界RunLoop狀態(tài)的更改

  • 有以下?tīng)顟B(tài)


typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {

// 進(jìn)入RunLoop開(kāi)始跑了

kCFRunLoopEntry = (1UL << 0),

// 將要執(zhí)行timer了

kCFRunLoopBeforeTimers = (1UL << 1),

// 將要執(zhí)行Source了

kCFRunLoopBeforeSources = (1UL << 2),

// 將要進(jìn)入睡眠

kCFRunLoopBeforeWaiting = (1UL << 5),

// 被喚醒

kCFRunLoopAfterWaiting = (1UL << 6),

// 退出

kCFRunLoopExit = (1UL << 7),

// 全部的狀態(tài)

kCFRunLoopAllActivities = 0x0FFFFFFFU

}

  • CFRunLoopObserver的應(yīng)用舉例
  1. CFRunLoopObserver與Autorelease Pool

CFRunLoopObserver 監(jiān)視到kCFRunLoopEntry(將要進(jìn)入Loop)的時(shí)候覆糟,會(huì)調(diào)用_objc_autoreleasePoolPush() 創(chuàng)建自動(dòng)釋放池。

CFRunLoopObserver 監(jiān)視到kCFRunLoopBeforeWaiting(將要進(jìn)入休眠) 時(shí)調(diào)用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 釋放舊的池并創(chuàng)建新池遮咖;kCFRunLoopExit(即將退出Loop) 時(shí)會(huì)調(diào)用 _objc_autoreleasePoolPop() 來(lái)釋放自動(dòng)釋放池滩字。

  1. 重繪視圖

蘋(píng)果為了保證界面的流暢性,(1)不會(huì)重繪屬性(frame等)沒(méi)有改變的視圖(2)只發(fā)送一次drawRect:消息御吞。

當(dāng)相關(guān)的視圖對(duì)象接收到設(shè)置屬性的消息的時(shí)候麦箍,就會(huì)將自己標(biāo)記為要重繪。RunLoop會(huì)收集所有等待重繪制的視圖陶珠,蘋(píng)果會(huì)注冊(cè)一個(gè)CFRunLoopObserver來(lái)監(jiān)聽(tīng)kCFRunLoopBeforeWaiting事件挟裂,當(dāng)事件觸發(fā)的時(shí)候,就會(huì)對(duì)所有等待重繪的視圖對(duì)象發(fā)送drawRect:消息揍诽。

RunLoop的掛起和喚醒

  • 當(dāng)RunLoop處于空閑狀態(tài)或者點(diǎn)擊了暫停的時(shí)候,RunLoop就被掛起诀蓉,具體步驟

(1) 指定用于再次喚醒的端口(mach_port)

(2) 調(diào)用mach_msg監(jiān)聽(tīng)喚醒端口。內(nèi)核調(diào)用mach_msg_trap 讓RunLoop處于mach_msg_trap狀態(tài)暑脆,RunLoop就會(huì)掛起渠啤,等待激活。就像一段代碼中有scanf函數(shù)添吗,必須要接收一個(gè)輸入一樣沥曹,不輸入就不會(huì)繼續(xù)往下執(zhí)行。這里要區(qū)別于sleep碟联〖嗣溃或者像是Notification,當(dāng)有post的時(shí)候,才會(huì)被喚醒鲤孵。

(3)由另一線程(或者另一個(gè)進(jìn)程中的某個(gè)線程)向內(nèi)核發(fā)送這個(gè)端口的msg后壶栋,trap狀態(tài)就會(huì)被喚醒,RunLoop就繼續(xù)工作

RunLoop的實(shí)現(xiàn)

// 底層的實(shí)現(xiàn)函數(shù)

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled){

// 配置RunLoop的Mode

SetupCFRunLoopMode()

// 通知 Observers 將要進(jìn)入 Loop

__CFRunLoopDoObservers(kCFRunLoopEntry);

// 通過(guò)GCD設(shè)置RunLoop的超時(shí)時(shí)間

SetupThisRunLoopRunTimeoutTimer();

// RunLoop開(kāi)始處理事件  do while 循環(huán)

do {

// 通知 Observers 將執(zhí)行timer

__CFRunLoopDoObservers(kCFRunLoopBeforeTimers);

// 通知 Observers 將執(zhí)行Source0

__CFRunLoopDoObservers(kCFRunLoopBeforeSources);

// 執(zhí)行blocks

__CFRunLoopDoBlocks();

// 執(zhí)行Source0

__CFRunLoopDoSource0();

// 問(wèn) GCD 主線程有沒(méi)有需要執(zhí)行的東西

CheckIfExistMessagesInMainDispatchQueue();

// 通知 Observers 將進(jìn)入睡眠

__CFRunLoopDoObservers(kCFRunLoopBeforeWaiting);

/* 指定 喚醒端口

監(jiān)聽(tīng) mach_msg 會(huì)停在這里

進(jìn)入 mach_msg_trap 狀態(tài)

睡眠中...

*/

var wakeUpPort = SleepAndWaitForWakingUpPorts();

// 接收到 消息  通知Observers RunLoop被喚醒了

__CFRunLoopDoObservers(kCFRunLoopAfterWaiting);

// 處理事件

if (wakeUpPort == timerPort) {

// 喚醒端口是 timerPort 執(zhí)行timer回調(diào) /* DOES CALLOUT */

__CFRunLoopDoTimers();

} else if (wakeUpPort == mainDispatchQueuePort) {

// 喚醒端口 執(zhí)行mainQueue里面的調(diào)用

__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__()

} else {

// 喚醒端口 執(zhí)行Source1回調(diào)

__CFRunLoopDoSource1();

}

// 執(zhí)行 blocks

__CFRunLoopDoBlocks()普监;

// 當(dāng)事件處理完了贵试、被強(qiáng)制停止了琉兜、超時(shí)了、Mode是空的時(shí)候就會(huì)退出 循環(huán)

} while (!stop && isStopped !timeout && !ModeIsEmpty );

// 通知 Observers 將退出Loop

__CFRunLoopDoObservers(kCFRunLoopExit);

}

其中var wakeUpPort = SleepAndWaitForWakingUpPorts();這句偽代碼可以看作是RunLoop的核心锡移。內(nèi)部實(shí)現(xiàn)簡(jiǎn)化為這樣:先調(diào)用__CFRunLoopServiceMachPort() ——> 里面會(huì)調(diào)用mach_msg()函數(shù) 然后會(huì)卡在這里呕童,等待接收消息來(lái)喚醒RunLoop。直到下面的某個(gè)條件被觸發(fā)才被喚醒:

  1. time_out 超時(shí)時(shí)間到了

  2. 有一個(gè)Source事件

  3. timer的時(shí)間到了

RunLoop 調(diào)用mach_msg()函數(shù)去接收消息淆珊,如果沒(méi)有其他 mach_port 發(fā)送消息過(guò)來(lái)夺饲,內(nèi)核就會(huì)將線程置于等待狀態(tài),直到接收到msg施符。就好比我們?cè)谝粋€(gè)函數(shù)中往声,調(diào)用了scanf()函數(shù)來(lái)接收輸入一樣,只有收到了輸入信息戳吝,代碼才能繼續(xù)向下執(zhí)行浩销,否則會(huì)一直卡在那里。

  • GCD 和 RunLoop

在RunLoop的內(nèi)部實(shí)現(xiàn)中听哭,用到了很多GCD的東西慢洋。比如剛剛開(kāi)始run的時(shí)候,通過(guò)DISPATCH_SOURCE_TYPE_TIMER該類型的dispatch_source 設(shè)置了RunLoop的超時(shí)時(shí)間陆盘。還可以在上面RunLoop實(shí)現(xiàn)的偽代碼中看到__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__() 普筹,只要是dispatch到main_queue的,CoreFoundation 都會(huì)調(diào)用這個(gè)函數(shù)隘马,之后太防,libdispatch.dylib 就會(huì)執(zhí)行回調(diào)。

  • RunLoop實(shí)踐

  • AFNetworking中RunLoop的創(chuàng)建

    在AFN中當(dāng)使用 NSURLConnection 去執(zhí)行網(wǎng)絡(luò)操作的時(shí)候酸员,會(huì)遇到還沒(méi)有收到服務(wù)器的回調(diào)蜒车,線程就已經(jīng)退出了。為了解決這一問(wèn)題幔嗦,作者使用到了RunLoop酿愧。下面是AFN中的一段代碼:


+ (void)networkRequestThreadEntryPoint:(id)__unused object {

@autoreleasepool {

[[NSThread currentThread] setName:@"AFNetworking"];

NSRunLoop *runLoop = [NSRunLoop currentRunLoop];

[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];

[runLoop run];

}

}

+ (NSThread *)networkRequestThread {

static NSThread *_networkRequestThread = nil;

static dispatch_once_t oncePredicate;

dispatch_once(&oncePredicate, ^{

_networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];

[_networkRequestThread start];

});

return _networkRequestThread;

}

上面這段代碼在AFURLConnectionOperation.m中的 162 行。

這是創(chuàng)建一個(gè)常駐服務(wù)線程的好方法崭添。比如寓娩,當(dāng)我們的程序要提供語(yǔ)音服務(wù)的時(shí)候,就可以創(chuàng)建一個(gè)專門(mén)為語(yǔ)音功能服務(wù)的線程呼渣,當(dāng)需要語(yǔ)音服務(wù)的時(shí)候,這個(gè)線程就可以來(lái)執(zhí)行寞埠。

  • 一個(gè)TableView延遲加載圖片的新思路

當(dāng)cell上有需要從網(wǎng)絡(luò)獲取的圖片的時(shí)候屁置,我們滾動(dòng)tableView,異步線程會(huì)去加載圖片仁连,加載完成后主線程就會(huì)設(shè)置cell的圖片蓝角,這個(gè)時(shí)候就會(huì)出現(xiàn)卡的現(xiàn)象阱穗。一般的解決方案是調(diào)用tableView的代理方法,判斷tableView是否正在滑動(dòng)使鹅,如果在滑動(dòng)揪阶,就不設(shè)置圖片,等停止滑動(dòng)后再去設(shè)置cell的圖片患朱。用Runloop能更簡(jiǎn)單的解決這個(gè)問(wèn)題鲁僚。我們可以根據(jù)RunLoop不同Mode下,執(zhí)行不同的事件來(lái)解決這個(gè)問(wèn)題思路如下:當(dāng)設(shè)置圖片的時(shí)候裁厅,讓其在 CFRunLoopDefaultMode 下進(jìn)行冰沙。當(dāng)滾動(dòng)tableView的時(shí)候,RunLoop是在 UITrackingRunLoopMode 這個(gè)Mode下执虹,就不會(huì)設(shè)置圖片拓挥,當(dāng)停止的時(shí)候,就會(huì)設(shè)置圖片袋励。


UIImage *downloadedImage = ...;

[self.avatarImageView performSelector:@selector(setImage:)

withObject:downloadedImage

afterDelay:0

inModes:@[NSDefaultRunLoopMode]];

  • 讓Crash的App回光返照

App崩潰的發(fā)生分兩種情況:

(1) program received signal:SIGABRT SIGABRT 一般是過(guò)度release 或者 發(fā)送 unrecogized selector導(dǎo)致侥啤。

(2) EXC_BAD_ACCESS 是訪問(wèn)已被釋放的內(nèi)存導(dǎo)致,野指針錯(cuò)誤。

由 SIGABRT 引起的Crash 是系統(tǒng)發(fā)這個(gè)signal給App茬故,程序收到這個(gè)signal后盖灸,就會(huì)把主線程的RunLoop殺死,程序就Crash了 該例只針對(duì) SIGABRT引起的Crash有效均牢。

  • Signal: 是Unix糠雨、類Unix等操作系統(tǒng)中進(jìn)程間通訊的一種方式,用來(lái)通知一個(gè)事件發(fā)生徘跪。當(dāng)一個(gè)singal發(fā)送給進(jìn)程甘邀,操作系統(tǒng)就會(huì)中斷進(jìn)程的正常控制流程垮庐,如果在進(jìn)程中定義了信號(hào)的處理函數(shù)松邪,那么這個(gè)函數(shù)就會(huì)被執(zhí)行,因此我們可以注冊(cè)signal哨查,并指定收到signal后要執(zhí)行的函數(shù)

為了讓App回光返照逗抑,我們需要來(lái)捕獲 libsystem_sim_c.dylib 調(diào)用 abort() 函數(shù)發(fā)出的程序終止信號(hào),然后讓其執(zhí)行我們定義的處理signal的方法寒亥。在方法中邮府,我們需要開(kāi)啟一個(gè)RunLoop,保持主線程不退出。


// 創(chuàng)建RunLoop

CFRunLoopRef runLoop = CFRunLoopGetCurrent();

// 設(shè)置Mode

NSArray *allModes = CFBridgingRelease(CFRunLoopCopyAllModes(runLoop));

// 彈窗告知 程序掛了

UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"程序崩潰了" message:@"崩潰信息" delegate:nil cancelButtonTitle:@"取消" otherButtonTitles:nil];

[alertView show];

while (1) {

for (NSString *mode in allModes) {

// 快速的切換 Mode  就能處理滾動(dòng)溉奕、點(diǎn)擊等事件

CFRunLoopRunInMode((CFStringRef)mode, 0.001, false);

}

}

備注

有哪些地方理解的不對(duì)褂傀,希望大神們能夠指出,感激不盡加勤。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末仙辟,一起剝皮案震驚了整個(gè)濱河市同波,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌叠国,老刑警劉巖未檩,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異粟焊,居然都是意外死亡冤狡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)吆玖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)筒溃,“玉大人,你說(shuō)我怎么就攤上這事沾乘×保” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵翅阵,是天一觀的道長(zhǎng)歪玲。 經(jīng)常有香客問(wèn)我,道長(zhǎng)掷匠,這世上最難降的妖魔是什么滥崩? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮讹语,結(jié)果婚禮上钙皮,老公的妹妹穿的比我還像新娘。我一直安慰自己顽决,他們只是感情好短条,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著才菠,像睡著了一般茸时。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上赋访,一...
    開(kāi)封第一講書(shū)人閱讀 49,036評(píng)論 1 285
  • 那天可都,我揣著相機(jī)與錄音,去河邊找鬼蚓耽。 笑死渠牲,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的步悠。 我是一名探鬼主播嘱兼,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼贤徒!你這毒婦竟也來(lái)了芹壕?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤接奈,失蹤者是張志新(化名)和其女友劉穎踢涌,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體序宦,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡睁壁,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了互捌。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片潘明。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖秕噪,靈堂內(nèi)的尸體忽然破棺而出钳降,到底是詐尸還是另有隱情,我是刑警寧澤腌巾,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布遂填,位于F島的核電站,受9級(jí)特大地震影響澈蝙,放射性物質(zhì)發(fā)生泄漏吓坚。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一灯荧、第九天 我趴在偏房一處隱蔽的房頂上張望礁击。 院中可真熱鬧,春花似錦逗载、人聲如沸哆窿。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)更耻。三九已至,卻和暖如春捏膨,著一層夾襖步出監(jiān)牢的瞬間秧均,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工号涯, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留目胡,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓链快,卻偏偏與公主長(zhǎng)得像誉己,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子域蜗,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容