RunLoop

RunLoop的核心,主要是涉及到用戶態(tài)和內(nèi)核態(tài)的切換(mach_msg())。

基本作用

保持程序運(yùn)行(main()的UIApplicationMain函數(shù)中會(huì)啟動(dòng)主線程的Runloop)

處理事件(觸摸、Timer)

節(jié)省CPU資源,提高性能(切換到內(nèi)核態(tài),休眠線程,等待事件/消息)


CFRunloopRef 對(duì)象

typedef struct __CFRunloop *CFRunLoopRef;

__CFRunloop屬性:pthread-與線程一一對(duì)應(yīng);commonModes-模式名稱的字符串集合;commonModeItems-通用模式下的事件源:Source01、Timer、Observer(Items一個(gè)都沒有則直接退出Runloop);currentMode-當(dāng)前模式;modes-Runloop中所有mode(default,tracking,common等)

Runloop對(duì)象的獲取

_CFRunLoopGetMain & _CFRunLoopGetCurrent

__CFRunLoopGet(pthread_t thread) { OSSpinLockLock(&lock) 自旋鎖;?

第一次進(jìn)入,初始化loopsDic,并為主線程創(chuàng)建一個(gè)Runloop(_CFRunLoopCreate());?

CFDictionaryGetValue(loopsDic,thread),根據(jù)thread獲取CFRunloopRef;

取不到就創(chuàng)建一個(gè),CFDictionarySetValue,并注冊(cè)回調(diào)銷毀線程時(shí)順便銷毀對(duì)應(yīng)的Runloop;

OSSpinUnLock(&lock)自旋鎖結(jié)束;}


CFRunLoopModeRef 模式

一個(gè)Runloop包含幾個(gè)mode(但每次只能啟動(dòng)一個(gè),所以互不影響),一個(gè)mode包含name,source0/1,timer、observer等若干事件源和監(jiān)聽,mode中沒有事件源Runloop會(huì)馬上退出。

tracking下不去處理default中的source,做到了屏蔽效果,保證了trackingmode下,滾動(dòng)的順暢。

CommonModes:所有mode都可以標(biāo)記為Common,每當(dāng)Runloop內(nèi)容發(fā)生變化時(shí),自動(dòng)將_commonModeItems同步到所有帶有標(biāo)記的Mode下(預(yù)置的default和track已被標(biāo)記為common)。

例:把timer加入CommonModeItems自動(dòng)更新同步到default和track,解決滾動(dòng)時(shí)NSTimer不回調(diào)的問題。

管理mode只有兩個(gè)方法:addCommonMode(只能增加不能刪除)和RunInMode。


mode中的事件源和活動(dòng)狀態(tài)

CFRunLoopSourceRef 輸入源(結(jié)構(gòu)體union中的version0/1分別對(duì)應(yīng)Source0/1)

Source0,只有一個(gè)回調(diào)指針(添加0到Runloop不會(huì)自動(dòng)喚醒線程需手動(dòng)wakeup,如觸摸事件?、performSeletor: thread等);

Source1,有一個(gè)mach-port和一個(gè)回調(diào)指針(基于Port的線程通信、系統(tǒng)事件捕捉)

CFRunLoopTimerRef 定時(shí)器源

和NSTimer是toll-free-bridge橋接的,包含一個(gè)時(shí)間長(zhǎng)度和一個(gè)回調(diào),加入到Runloop時(shí)會(huì)注冊(cè)對(duì)應(yīng)的時(shí)間點(diǎn),到點(diǎn)喚醒;performSeletor: afterDelay(本質(zhì)也是創(chuàng)建一個(gè)NSTimer加到Runloop中)。

CFRunLoopObserverRef 活動(dòng)狀態(tài)

__CFRunLoopObserver結(jié)構(gòu)體中的_activities(CFOptionFlags,6種狀態(tài)如下)保存了當(dāng)前mode的活動(dòng)狀態(tài),狀態(tài)發(fā)生改變時(shí),通過結(jié)構(gòu)體中的_callout回調(diào)(CFRunLoopObserverCallBack)通知狀態(tài)的觀察者。

Entry 即將進(jìn)入

BeforeTimers //即將處理Timers

BeforeSource //即將處理Sources

BeforeWaiting //即將入休眠(UI刷新和AutoreleasePool執(zhí)行的觸發(fā)狀態(tài))

AfterWaiting //剛從休眠中喚醒

Exit //即將退出

mode管理item的接口有6個(gè),add和remove各3個(gè)分別對(duì)應(yīng)以上3個(gè)事件源。CFRunLoopAddSource/Timer/Obsever(rl, item, modeName),如果modeName沒有則會(huì)創(chuàng)建一個(gè)。


RunLoop的內(nèi)部邏輯:事件循環(huán)機(jī)制

主線程Runloop的啟動(dòng)過程??通過設(shè)置斷點(diǎn),LLDB中輸入指令bt查看調(diào)用棧,發(fā)現(xiàn)UIApplicationMain函數(shù)中調(diào)用了CFRunLoopSpecific(RunLoop的入口)。

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

→ currentMode = __CFRunLoopFindMode(rl, modeName, false)(找本次mode,找不到或mode中沒有任何事件則直接Finished)

→ __CFRunLoopDoObservers(rl, currentModel, Entry)(即將進(jìn)入的通知)

→ result = __CFRunLoopRun(rl, currentModel, seconds, returnAfterSourceHandled, previousMode)

→ __CFRunLoopDoObservers(rl, currentMode, Exit)(即將退出的通知)

__CFRunLoopRun(rl, mode, seconds, handler, previousMode)的實(shí)現(xiàn)

→ __CFRunLoopDoObservers(rl, rlm, BeforeTimers)、(rl, rlm, BeforeSources); __CFRunLoopDoBlocks(rl, rlm);(發(fā)出即將處理Tmers,Sources的通知,響應(yīng)Blocks)

→ __CFRunLoopDoSources0,__CFRunLoopDoBlocks(處理Sources0,并響應(yīng)blocks)

→ 判斷如果有Source1則goto handle_msg(處理source1的邏輯)

→ __CFRunLoopDoObservers(rl, rlm, BeforeWaiting)(發(fā)出即將進(jìn)入休眠的通知)→ __CFRunLoopSetSleeping(rl);?

→ __CFRunLoopServiceMachPort(正式進(jìn)入休眠,并等待消息來喚醒線程)

注:休眠時(shí),其中調(diào)用了mach_msg函數(shù)從用戶態(tài)切換到了內(nèi)核態(tài),等待消息,避免CUP資源占用。有消息處理時(shí),立刻喚醒線程,調(diào)用mach_msg切回用戶態(tài)。

handle_msg的實(shí)現(xiàn):

→ int retVal = 0,do-while循環(huán)

→ __CFRunLoopDoTimers,__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__,__CFRunLoopDoSource1(被誰(shuí)喚醒就處理誰(shuí))

→ __CFRunLoopDoBlocks(處理Block)

→ __CFRunLoopModeIsEmpty ? retVal = kCFRunLoopRunFinished;(根據(jù)判斷條件,標(biāo)記kCFRunLoopRunHandledSource=4、TimedOut=3、Stopped=2、Finished=1等返回值,return retVal (!=0))

總結(jié):__CFRunLoopRun方法內(nèi)部的邏輯,先發(fā)出即將處理事件源Timer/Sources的通知,doBlocks,處理Source0和Source1(如果有,跳轉(zhuǎn)handle_msg),發(fā)出即將進(jìn)入休眠的通知,machPort轉(zhuǎn)移控制權(quán)到內(nèi)核態(tài),并sleep進(jìn)入休眠。最后Afterwaiting從休眠中喚醒的通知,開始新一輪。

Runloop與線程

1、與線程一一對(duì)應(yīng),保存在全局的Dict中,線程(pthread)為key,runloop為value;

2、runloop為線程保活否則線程執(zhí)行完任務(wù)就會(huì)退出(主線程執(zhí)行完main()程序也會(huì)退出);

3、runloop創(chuàng)建時(shí)機(jī):線程剛創(chuàng)建時(shí)沒有,第一次CFDictionaryGetValue獲取它的時(shí)候創(chuàng)建(主線程在UIApplicationMain函數(shù)中通過[NSRunLoop currentRunLoop]自動(dòng)創(chuàng)建,子線程默認(rèn)沒有開啟runloop);

4、銷毀時(shí)機(jī):線程結(jié)束時(shí)銷毀。創(chuàng)建一個(gè)線程(子線程)并執(zhí)行Test任務(wù),[thread start]后線程會(huì)被銷毀;runloop注冊(cè)時(shí)會(huì)同時(shí)注冊(cè)銷毀的回調(diào),當(dāng)線程被銷毀時(shí)執(zhí)行。

5、常駐子線程:為子線程創(chuàng)建Runloop(addPort+addSource添加事件來維持循環(huán)→[run] / [runMode beforeDate]啟動(dòng),注意,run和runUntilDate會(huì)永久運(yùn)行,stop也無法停止),runMode:beforeDate可以用stop退出:在performSelector: onThread中,執(zhí)行CFRunLoopStop(CFRunLoopGetCurrent());。

添加Source事件源

CFMessagePortRef localPort = CFMessagePortCreateLocal(nil, CFSTR("com.example.app.port.server"), Callback, nil, nil);

CFRunLoopSourceRef runLoopSource = CFMessagePortCreateRunLoopSource(nil, localPort, 0);

CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopCommonModes);

CF方法:創(chuàng)建port,創(chuàng)建source,addSource到currentRunLoop

NS方法:[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode]; // 子線程,別忘了手動(dòng)run/runMode

或使用信號(hào)量的方式

dispatch_semaphore_t sem;

sem = dispatch_semaphore_create(0);

dispatch_semaphore_signal發(fā)送信號(hào);

while(true)循環(huán)內(nèi)dispatch_semaphore_wait(sem, -1)等待信號(hào),并執(zhí)行:

dispatch_block_t block = [actions firstObject]; if (block) { [actions removeObject: block]; block(); }

6、GCD:dispatch_async(main)時(shí),libDispatch會(huì)向主線程的RunLoop發(fā)消息,RunLoop會(huì)被喚醒并從消息中取得要執(zhí)行的任務(wù)的block,并在回調(diào)中執(zhí)行這個(gè)block。

注:此邏輯只限于dispatch到主線程,其他線程仍是libDispatch自己處理的。


Runloop與NSTimer

NSTimer 其實(shí)就是 CFRunLoopTimerRef,他們之間是toll-free bridged(等價(jià)橋接)的。

原理:在重復(fù)的時(shí)間點(diǎn)注冊(cè)事件,RunLoop為了節(jié)省資源不會(huì)非常準(zhǔn)確的回調(diào)(Tolerance寬容度,允許的時(shí)間誤差)。

1、子線程上使用定時(shí)器:[[NSRunLoop currentRunLoop] run],手動(dòng)啟動(dòng)子線程Runloop(invalidate時(shí)也要在子線程);

2、解決NSTimer在滾動(dòng)時(shí)停止工作的問題:NSTimer默認(rèn)加入DefaultMode,而滾動(dòng)時(shí)的TrackingMode下,不會(huì)處理DefaultMode的消息,所以應(yīng)設(shè)置為CommonMode,同步到所有mode包括Default和Tracking。

3、performSelector:afterDelay會(huì)內(nèi)部創(chuàng)建一個(gè)Timer并加到當(dāng)前線程的runloop中;onThread也會(huì)內(nèi)部創(chuàng)建一個(gè)Timer并加到線程中。兩種方法在當(dāng)前線程沒有啟動(dòng)Runloop時(shí)都會(huì)失效(如在未創(chuàng)建和啟動(dòng)Runloop的子線程中調(diào)用,回到1)

4、NSTimer的不準(zhǔn)時(shí):當(dāng)RunLoop的任務(wù)繁重時(shí),每次循環(huán)都會(huì)查看是否到達(dá)Timer設(shè)置的時(shí)間點(diǎn),當(dāng)循環(huán)時(shí)間過長(zhǎng)時(shí)會(huì)導(dǎo)致超過Timer設(shè)置的時(shí)間,則不會(huì)延后執(zhí)行回調(diào)而是等待下一個(gè)時(shí)間點(diǎn)。可以用GCD的定時(shí)器,不依賴Runloop直接與系統(tǒng)內(nèi)核掛鉤(dispatch_source_create→dispatch_source_set_timer→dispatch_source_set_event_handler→dispatch_resume)

注1:scheduledTimerWithTimeInterval自動(dòng)添加到默認(rèn)的mode下;timerWithTimeInterval不會(huì)添加到runloop中,需要手動(dòng)添加并指定common模式,以避免滾動(dòng)mode下停止工作的問題。

注2:NSTimer、CADisplayLink、dispatch_source_t的區(qū)別和優(yōu)劣。

注3:CADisplayLink,和屏幕刷新率一致的定時(shí)器(內(nèi)部實(shí)際是操作了一個(gè)Source),也需要添加到RunLoop,如果兩次刷新之間有耗時(shí)任務(wù),則會(huì)造成頁(yè)面丟幀卡頓。


AutoreleasePool

主線程的RunLoop中注冊(cè)了兩個(gè)Observer:1、監(jiān)聽kCFRunLoopEntry,調(diào)用objc_autoreleasePoolPush創(chuàng)建自動(dòng)釋放池,優(yōu)先級(jí)較高,保證在所有其他回調(diào)前;2、監(jiān)聽了kCFRunLoopBeforeWaiting,進(jìn)入休眠前先調(diào)pop釋放舊池再調(diào)push創(chuàng)建新池、和kCFRunLoopBeforeExit,調(diào)用pop釋放自動(dòng)釋放池優(yōu)先級(jí)低,保證在其他所有回調(diào)后。

主線程的代碼一般是在事件/Timer回調(diào)內(nèi)的,所以會(huì)被自動(dòng)創(chuàng)建的autoreleasePool環(huán)繞,不會(huì)出現(xiàn)內(nèi)存泄漏。但有些情況下比如for循環(huán)中創(chuàng)建大量臨時(shí)對(duì)象,需要手動(dòng)添加@autoreleasePool在下一次RunLoop前就處理這些對(duì)象。注:enumerateObjectsUsingBlock的容器循環(huán)內(nèi),默認(rèn)添加了pool。


事件響應(yīng)和手勢(shì)識(shí)別

IOKit捕捉到事件后處理成IOHIDEvent通過mach port發(fā)送給SpringBoard,再通過mach port發(fā)送給當(dāng)前前臺(tái)運(yùn)行的app,觸發(fā)了app主線程RunLoop的Source1監(jiān)聽并觸發(fā)Source0的回調(diào),拿到了事件后,在回調(diào)內(nèi)部封裝為UIEvent(手勢(shì)的封裝和識(shí)別也在這一步),然后調(diào)用UIApplication的sendEvent傳遞給UIWindow,開始了事件的傳遞鏈,尋找最佳響應(yīng)者。

事件的Source0回調(diào)中,處理事件時(shí)如果識(shí)別成Gesture手勢(shì),則標(biāo)記手勢(shì)對(duì)象為待處理,監(jiān)聽到BeforeWating事件時(shí),會(huì)獲取所有標(biāo)記待處理的手勢(shì)執(zhí)行手勢(shì)回調(diào),每當(dāng)手勢(shì)變化時(shí),都會(huì)進(jìn)行update。


UI的繪制和刷新

更改frame、UI層級(jí),或手動(dòng)調(diào)用setNeedsLayout/Display后,UIView/CALayer將被標(biāo)記為待處理并提交到全局容器中,在BeforeWaiting和Exit回調(diào)中執(zhí)行一個(gè)callback函數(shù),方法會(huì)遍歷所有待處理的UI并執(zhí)行繪制和調(diào)整,更新頁(yè)面。


網(wǎng)絡(luò)請(qǐng)求

網(wǎng)絡(luò)請(qǐng)求接口的四層封裝

1、CFSocket:最底層的接口,只負(fù)責(zé)Socket通信

2、CFNetwork:基于CFSocket的封裝

3、NSURLConnection:基于CFNetwork的封裝,面向?qū)ο螅?b>AFNetworking在這層

4、NSURLSession:iOS7新增,表面和3并列,底層實(shí)際還是用到了3部分功能,AFNetwork2,Alamofire在這層

NSURLConnection的工作原理:[connection start],start函數(shù)會(huì)獲取CurrentRunLoop,并在Default里CFRunLoopAddSource加4個(gè)Source0(參見RunLoop與線程中的5、創(chuàng)建常駐線程)。CFMultiplexerSource負(fù)責(zé)各種請(qǐng)求的回調(diào),CFHTTPCookieStorage負(fù)責(zé)處理Cookie。

網(wǎng)絡(luò)開始傳輸時(shí),NSURLConnection創(chuàng)建了com.apple.CFSocket.private和com.apple.NSURLConnectionLoader兩個(gè)新線程,CFSocket處理底層Socket連接發(fā)個(gè)Source1事件,NSURLConnectionLoader接收這個(gè)Source1(基于mach Port),并通過之前添加的Source0通知到上層delegate,喚醒回調(diào)線程的RunLoop,執(zhí)行實(shí)際的回調(diào)。

AFNetwork

AFURLConnectionOperation:基于NSURLConnection,單獨(dú)創(chuàng)建了一個(gè)線程并啟動(dòng)了一個(gè)RunLoop,以便在后臺(tái)線程接收Delegate回調(diào)。


相關(guān)問題

RunLoop的基本作用

主線程RunLoop保持程序運(yùn)行;處理事件/Timer;通過調(diào)用mach_msg轉(zhuǎn)移線程控制權(quán)切換內(nèi)核態(tài),進(jìn)入休眠,節(jié)省CPU資源。


Runloop對(duì)象的數(shù)據(jù)結(jié)構(gòu)

一個(gè)RunLoop對(duì)象有多個(gè)mode,如defaultMode、trackMode和commonMode標(biāo)記,每個(gè)mode可以處理多個(gè)source和Timer,回調(diào)多個(gè)Observer。


整個(gè)RunLoop機(jī)制的運(yùn)行過程,哪些事件源可以喚醒RunLoop

Entry通知,RunLoop運(yùn)行(source和timer通知,處理blocks,處理source0.1,然后handle_msg內(nèi)執(zhí)行dowhile循環(huán),被誰(shuí)喚醒處理timer,GCD,Source1),Exit通知


獲取RunLoop的兩個(gè)方法和實(shí)現(xiàn);管理Mode的兩個(gè)方法和用處;管理ModeItem的6個(gè)方法和用處CFRunLoopGetMain/Current:從線程-RunLoop的字典中獲取,獲取不到就創(chuàng)建一個(gè),并注冊(cè)跟隨線程銷毀的回調(diào)。主線程會(huì)在UIApplicationMain中獲取主線程RunLoop默認(rèn)開啟。

addCommonMode/runInMode方法:添加自定義mode和將事件或Timer放入指定mode。

CFRunLoopAddSource/Timer/Obsever(rl, item, modeName),如果modeName沒有則會(huì)創(chuàng)建一個(gè)。


RunLoop和線程的關(guān)系及生命周期,主線程創(chuàng)建RunLoop的過程,如何實(shí)現(xiàn)一個(gè)常駐子線程(線程保活)

與線程一一對(duì)應(yīng);主線程RunLoop會(huì)在Main函數(shù)時(shí)通過currentRunLoop創(chuàng)建并開啟;

在子線程內(nèi)addPort / addSource維持事件循環(huán),run開啟后常駐? 或? 使用信號(hào)量的方式:while(true)循環(huán)內(nèi)執(zhí)行dispatch_semaphore_wait(sem, -1)等待信號(hào),收到信號(hào)執(zhí)行block。


NSTimer的原理和劣勢(shì);有哪些添加NSTimer的方法,分別有什么問題;能在子線程上使用NSTimer嗎;PerformSelector的原理

NSTimer和CFRunLoopTimerRef是toll-bridge的,注冊(cè)時(shí)間點(diǎn)每次RunLoop時(shí)檢查,為了性能有一定的寬容度,有耗時(shí)任務(wù)時(shí)將不會(huì)準(zhǔn)時(shí)回調(diào);scheduled開頭的timer添加方法會(huì)加到defaultMode下,track時(shí)不回調(diào),而timerWithTimeInterval不會(huì)加到RunLoop中,需要手動(dòng)添加[[NSRunLoop mainRunLoop] addTimer: timer forMode:NSRunLoopCommonModes];。PerformSelector的onThread和afterDelay都是添加一個(gè)timer到runLoop中,子線程中不會(huì)默認(rèn)啟動(dòng)。

主線程中是如何通過RunLoop添加AutoreleasePool的,子線程呢

監(jiān)聽Entry調(diào)用Push創(chuàng)建,BeforeWaiting的回調(diào)中Pop并再push一個(gè)新的,于是主線程代碼中的對(duì)象被pool包圍,不會(huì)內(nèi)存泄露。子線程中,iOS7前需要手動(dòng)開啟,后來會(huì)通過自動(dòng)創(chuàng)建hotPoolPaged把對(duì)象加入page。


RunLoop和UI刷新的關(guān)系(繪制計(jì)算和提交渲染的時(shí)機(jī))

被標(biāo)記需要刷新的UI會(huì)在BeforeWaiting/Exit的回調(diào)中,CPU計(jì)算處理提交GPU渲染。


RunLoop是如何接收系統(tǒng)事件(識(shí)別手勢(shì)),并開啟傳遞鏈的

IOKit,SpringBoard,分發(fā)給APP,通過mach port觸發(fā)主線程Source1并執(zhí)行Source0回調(diào),內(nèi)部處理成UIEvent,調(diào)用UIApplication的sendEvent發(fā)給UIWindow開始事件的傳遞。

?著作權(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),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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