網(wǎng)上有很多關(guān)于runLoop的文章,但是看過(guò)了就忘記了,為了加深印象,不妨自己動(dòng)手寫(xiě)寫(xiě),很多理論都是網(wǎng)上學(xué)習(xí)到的,即便寫(xiě)完這篇記錄,我也不是很理解runLoop。
一:線(xiàn)程與runLoop
看過(guò)面試題的人都知道runLoop,簡(jiǎn)單的理解就是跑圈,runLoop其實(shí)和線(xiàn)程是一一對(duì)應(yīng)的,我們都知道主線(xiàn)程(UI線(xiàn)程),為什么主線(xiàn)程不會(huì)像子線(xiàn)程一樣,執(zhí)行完一段代碼就被銷(xiāo)毀掉,因?yàn)樵谥骶€(xiàn)程下有一個(gè)runLoop,這個(gè)runLoop不斷循環(huán)接收各種事件,保證主線(xiàn)程不會(huì)因?yàn)闊o(wú)事兒可做而被銷(xiāo)毀
那么子線(xiàn)程就沒(méi)有runLoop嗎?
默認(rèn)情況下,子線(xiàn)程沒(méi)有創(chuàng)建runLoop,但是可以手動(dòng)創(chuàng)建,當(dāng)我們?cè)谧泳€(xiàn)程中獲取當(dāng)前線(xiàn)程的runLoop時(shí),就會(huì)自動(dòng)創(chuàng)建一個(gè)runLopp,系統(tǒng)內(nèi)部創(chuàng)建的代碼如下,了解即可
// 拿到當(dāng)前Runloop 調(diào)用_CFRunLoopGet0
CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
return _CFRunLoopGet0(pthread_self());
}
// 查看_CFRunLoopGet0方法內(nèi)部
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
__CFLock(&loopsLock);
if (!__CFRunLoops) {
__CFUnlock(&loopsLock);
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
// 根據(jù)傳入的主線(xiàn)程獲取主線(xiàn)程對(duì)應(yīng)的RunLoop
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
// 保存主線(xiàn)程 將主線(xiàn)程-key和RunLoop-Value保存到字典中
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
CFRelease(mainLoop);
__CFLock(&loopsLock);
}
// 從字典里面拿,將線(xiàn)程作為key從字典里獲取一個(gè)loop
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFUnlock(&loopsLock);
// 如果loop為空,則創(chuàng)建一個(gè)新的loop,所以runloop會(huì)在第一次獲取的時(shí)候創(chuàng)建
if (!loop) {
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFLock(&loopsLock);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
// 創(chuàng)建好之后,以線(xiàn)程為key runloop為value,一對(duì)一存儲(chǔ)在字典中,下次獲取的時(shí)候,則直接返回字典內(nèi)的runloop
if (!loop) {
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
// don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
__CFUnlock(&loopsLock);
CFRelease(newLoop);
}
if (pthread_equal(t, pthread_self())) {
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
}
}
return loop;
}
二:看一下主線(xiàn)程的runLoop是怎樣工作的
1:runLoop的活動(dòng)周期
public static var entry: CFRunLoopActivity { get }
public static var beforeTimers: CFRunLoopActivity { get }
public static var beforeSources: CFRunLoopActivity { get }
public static var beforeWaiting: CFRunLoopActivity { get }
public static var afterWaiting: CFRunLoopActivity { get }
public static var exit: CFRunLoopActivity { get }
但大體來(lái)說(shuō)有兩種狀態(tài),工作和睡覺(jué),當(dāng)我們觸發(fā)一些事件,這些事件包括source0、source1、timer其實(shí)我也不太清楚具體哪些事件
source0應(yīng)該是各種點(diǎn)擊事件、函數(shù)調(diào)用之內(nèi)的,比如我們點(diǎn)擊按鈕

source1貌似是線(xiàn)程之間的通信,可以添加個(gè)斷點(diǎn)了解下


timer是定時(shí)任務(wù),我們啟動(dòng)一個(gè)定時(shí)器

這時(shí)候runLoop就是開(kāi)始工作,當(dāng)事件處理完成,runLoop就會(huì)去睡覺(jué),等待下一個(gè)電話(huà)把它叫醒。
我們可以監(jiān)聽(tīng)一下主線(xiàn)程下的runLoop的狀態(tài)
// 1: 給主線(xiàn)程的runloop添加監(jiān)聽(tīng)
func mainObserver() {
let observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, CFOptionFlags.max, true, 0) { (observer, activity) in
switch activity {
case CFRunLoopActivity.entry:
print("即將進(jìn)入runloop")
break
case CFRunLoopActivity.beforeTimers:
print("即將處理timer")
break
case CFRunLoopActivity.beforeSources:
print("即將處理input Sources")
break
case CFRunLoopActivity.beforeWaiting:
print("即將睡眠")
break
case CFRunLoopActivity.afterWaiting:
print("從睡眠中喚醒,處理完喚醒源之前")
break
case CFRunLoopActivity.exit:
print("退出")
break
default:
print("other")
break
}
}
// 添加監(jiān)聽(tīng)到當(dāng)前的runloop中
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, CFRunLoopMode.defaultMode)
// 主線(xiàn)程的runloop已經(jīng)運(yùn)行了
// CFRunLoopRun()
}
然后我們就可以看到很多狀態(tài),大致在這個(gè)周期內(nèi),不斷循環(huán),永遠(yuǎn)不會(huì)切換到CFRunLoopActivity.exit狀態(tài)
從睡眠中喚醒,處理完喚醒源之前
即將處理timer
即將處理input Sources
即將處理timer
即將處理input Sources
即將睡眠
從睡眠中喚醒,處理完喚醒源之前
即將處理timer
即將處理input Sources
即將處理timer
即將處理input Sources
即將睡眠
我們?cè)贠C項(xiàng)目的main.m中可以修改一下代碼
int main(int argc, char * argv[]) {
@autoreleasepool {
int x = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
NSLog(@"打印 %d", x);
return x;
}
}
這里添加的打印不會(huì)被執(zhí)行,我們看看UIApplicationMain函數(shù)里面會(huì)執(zhí)行什么
從圖中可以看到,主線(xiàn)程中會(huì)啟動(dòng)runLoop,而主線(xiàn)程的runLoop會(huì)保證主線(xiàn)程不被銷(xiāo)毀,也就是從UIApplicationMain函數(shù)進(jìn)入,主線(xiàn)程會(huì)一直運(yùn)行下去,不會(huì)調(diào)用return返回一個(gè)值
三:看一下子線(xiàn)程的runLoop
其實(shí)主線(xiàn)程的runLoop我們幾乎不可用,也干不了什么事兒,主要是了解一下
但是我們可以在子線(xiàn)程中去使用runLoop干一些事兒
??在子線(xiàn)程中獲取了當(dāng)前線(xiàn)程的runLoop,其實(shí)為我們創(chuàng)建了一個(gè)子線(xiàn)程的runLoop,如果我們注釋掉
// RunLoop.current.add(Port.init(), forMode: RunLoop.Mode.default)
// RunLoop.current.run(until: Date.init(timeIntervalSinceNow: 10))
這兩行代碼,下面的程序不會(huì)打印任何信息,因?yàn)閞unLoop中沒(méi)有監(jiān)聽(tīng)任何輸入源,直接就結(jié)束了
// 2: 給子線(xiàn)程的runLoop添加監(jiān)聽(tīng)
func subObserver() {
// 全局隊(duì)列獲取一個(gè)子線(xiàn)程
subThread = Thread.init(block: {
let observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, CFOptionFlags.max, true, 0) { (observer, activity) in
switch activity {
case CFRunLoopActivity.entry:
print("子線(xiàn)程即將進(jìn)入runloop")
break
case CFRunLoopActivity.beforeTimers:
print("子線(xiàn)程即將處理timer")
break
case CFRunLoopActivity.beforeSources:
print("子線(xiàn)程即將處理input Sources")
break
case CFRunLoopActivity.beforeWaiting:
print("子線(xiàn)程即將睡眠")
break
case CFRunLoopActivity.afterWaiting:
print("子線(xiàn)程從睡眠中喚醒,處理完喚醒源之前")
break
case CFRunLoopActivity.exit:
print("子線(xiàn)程退出")
break
default:
print("子線(xiàn)程other")
break
}
}
self.subRunLoop = RunLoop.current
// 添加監(jiān)聽(tīng)到當(dāng)前的runloop中
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, CFRunLoopMode.defaultMode)
// RunLoop.current.add(Port.init(), forMode: RunLoop.Mode.default)
// RunLoop.current.run(until: Date.init(timeIntervalSinceNow: 10))
CFRunLoopRun()
})
subThread?.start()
}
此時(shí),我們來(lái)試試,指定一個(gè)方法到subThread線(xiàn)程上去執(zhí)行
@IBAction func subLoopAction(_ sender: Any) {
// waitUntilDone: true會(huì)阻塞主線(xiàn)程
perform(#selector(subLoop), on: subThread!, with: nil, waitUntilDone: false)
}
@objc func subLoop() {
for i in 0...10 {
sleep(1)
print("sub \(i)")
}
}
程序會(huì)出現(xiàn)崩潰,因?yàn)榇藭r(shí)的subThread子線(xiàn)程已經(jīng)被銷(xiāo)毀掉了,為了讓子線(xiàn)程不會(huì)被快速銷(xiāo)毀,我們就需要讓子線(xiàn)程的runLoop運(yùn)行下去
我們可以給runLoop添加一個(gè)輸入源,比如timer,或者監(jiān)聽(tīng)一個(gè)port
RunLoop.current.add(Port.init(), forMode: RunLoop.Mode.default)
這樣子線(xiàn)程就不會(huì)被銷(xiāo)毀,然后我們?cè)龠\(yùn)行試試看

好了,我們已經(jīng)可以讓子線(xiàn)程能夠保持下去了,AFNetWorking里面就用到了runLoop去保存子線(xiàn)程不被銷(xiāo)毀
我們也可以讓runLoop在限定的時(shí)間內(nèi)運(yùn)行,超過(guò)這個(gè)時(shí)間,子線(xiàn)程就會(huì)被銷(xiāo)毀
RunLoop.current.run(until: Date.init(timeIntervalSinceNow: 10))
讓runLoop在接下來(lái)的10秒內(nèi)有效,修改上面的代碼
func subObserver() {
// 全局隊(duì)列獲取一個(gè)子線(xiàn)程
subThread = Thread.init(block: {
let observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, CFOptionFlags.max, true, 0) { (observer, activity) in
switch activity {
case CFRunLoopActivity.entry:
print("子線(xiàn)程即將進(jìn)入runloop")
break
case CFRunLoopActivity.beforeTimers:
print("子線(xiàn)程即將處理timer")
break
case CFRunLoopActivity.beforeSources:
print("子線(xiàn)程即將處理input Sources")
break
case CFRunLoopActivity.beforeWaiting:
print("子線(xiàn)程即將睡眠")
break
case CFRunLoopActivity.afterWaiting:
print("子線(xiàn)程從睡眠中喚醒,處理完喚醒源之前")
break
case CFRunLoopActivity.exit:
print("子線(xiàn)程退出")
break
default:
print("子線(xiàn)程other")
break
}
}
self.subRunLoop = RunLoop.current
// 添加監(jiān)聽(tīng)到當(dāng)前的runloop中
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, CFRunLoopMode.defaultMode)
// RunLoop.current.add(Port.init(), forMode: RunLoop.Mode.default)
// RunLoop.current.run(mode: RunLoop.Mode.default, before: Date.init(timeIntervalSinceNow: 10))
Timer.scheduledTimer(withTimeInterval: 1, repeats: true, block: { (_) in
print("xxxxx")
})
// 需要放到任務(wù)下面
RunLoop.current.run(until: Date.init(timeIntervalSinceNow: 5))
// CFRunLoopRun()
})
subThread?.start()
}

5秒鐘后子線(xiàn)程銷(xiāo)毀,內(nèi)部的定時(shí)任務(wù)也不會(huì)繼續(xù)執(zhí)行,也可以添加延時(shí)執(zhí)行任務(wù)
self.perform(#selector(self.subLoop), with: nil, afterDelay: 12)
超過(guò)5秒,也不會(huì)執(zhí)行
四:runLoop和autoreleasepool
關(guān)于autoreleasepool和runLoop網(wǎng)上也有很多文章進(jìn)行了講解,這里我只是簡(jiǎn)單的把自己的理解寫(xiě)出來(lái),如果有錯(cuò),歡迎指正
先看看系統(tǒng)下的autoreleasepool是在什么時(shí)候開(kāi)啟的,打一個(gè)斷點(diǎn)

我們啟動(dòng)程序,會(huì)立即停在斷點(diǎn)處,因?yàn)槲覀兌贾涝趍ain.m中就有使用到autoreleasepool


這時(shí)候,我們可以通過(guò)之前添加的主線(xiàn)程runLoop狀態(tài)監(jiān)聽(tīng)發(fā)現(xiàn)當(dāng)runLoop狀態(tài)切換到beforeWaiting時(shí),斷點(diǎn)就會(huì)斷下
通過(guò)上面的圖,可以發(fā)現(xiàn),runLoop狀態(tài)改變會(huì)執(zhí)行回調(diào)方法,CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION,這個(gè)回調(diào)后有兩個(gè)關(guān)于autoreleasepool的方法名


這里有一個(gè)pop()方法,和push()的方法
由此我們可以大概的推測(cè),runLoop在進(jìn)入睡眠之前,會(huì)將當(dāng)前創(chuàng)建的釋放池進(jìn)行銷(xiāo)毀,隨即立馬創(chuàng)建一個(gè)新的釋放池,等待下一個(gè)周期到來(lái)釋放
然后我們回到main.m中,修改一下代碼
int main(int argc, char * argv[]) {
@autoreleasepool {
}
}
使用編譯命令clang -rewrite-objc main.m,將main.m文件編譯成main.cpp文件
我們可以看到一個(gè)結(jié)構(gòu)體__AtAutoreleasePool,這個(gè)結(jié)構(gòu)體中有 objc_autoreleasePoolPush()方法,這個(gè)方法返回一個(gè)pool對(duì)象atautoreleasepoolobj,objc_autoreleasePoolPop(atautoreleasepoolobj)方法,接收一個(gè)pool對(duì)象為參數(shù)。
關(guān)于autoreleasepool的使用場(chǎng)景,網(wǎng)上很多文章都有介紹,這里摘抄一下
1.寫(xiě)基于命令行的的程序時(shí),就是沒(méi)有UI框架,如AppKit等Cocoa框架時(shí)
2.寫(xiě)循環(huán),循環(huán)里面包含了大量臨時(shí)創(chuàng)建的對(duì)象
3.創(chuàng)建了新的線(xiàn)程(非Cocoa程序創(chuàng)建線(xiàn)程時(shí)才需要,所以平時(shí)我們創(chuàng)建子線(xiàn)程的時(shí)候并沒(méi)有特意的去添加autoreleasepool)
4.長(zhǎng)時(shí)間在后臺(tái)運(yùn)行的任務(wù)
給for循環(huán)中添加autoreleasepool,釋放每次循環(huán)創(chuàng)建的臨時(shí)對(duì)象,對(duì)于這個(gè)autoreleasepool的釋放則是根據(jù)pool的作用域,超過(guò)其作用域就會(huì)釋放
for _ in 0...10000 {
autoreleasepool {
let view1: UIView? = UIView.init()
print(view1.debugDescription)
}
}
另外,更詳細(xì)關(guān)于__AtAutoreleasePool是如果創(chuàng)建,如果將對(duì)象添加到pool中,以及如何釋放的pool中的對(duì)象的,大家可以看看其他文章,參考文章
http://www.itdecent.cn/p/50bdd8438857
http://www.itdecent.cn/p/b875065074f2
http://www.itdecent.cn/p/733447ca44ae