iOS 記錄runLoop與線(xiàn)程,runLoop與autoreleasepool

網(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)擊按鈕


image.png

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


image.png

image.png

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

這時(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í)行什么
image.png

從圖中可以看到,主線(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)行試試看


image.png

好了,我們已經(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()
    }
image.png

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)

image.png

我們啟動(dòng)程序,會(huì)立即停在斷點(diǎn)處,因?yàn)槲覀兌贾涝趍ain.m中就有使用到autoreleasepool
image.png
我們跳過(guò)斷點(diǎn),讓程序正常運(yùn)行,然后放著不動(dòng)等待一段時(shí)間,或者點(diǎn)擊屏幕,我們發(fā)現(xiàn)程序自動(dòng)又?jǐn)嗟搅诉@個(gè)斷點(diǎn)
image.png

這時(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的方法名
image.png
image.png

這里有一個(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文件
image.png

我們可以看到一個(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

?著作權(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)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 原文地址:http://blog.ibireme.com/2015/05/18/runloop/ RunLoop ...
    大餅炒雞蛋閱讀 1,259評(píng)論 0 6
  • RunLoop 的概念 一般來(lái)講,一個(gè)線(xiàn)程一次只能執(zhí)行一個(gè)任務(wù),執(zhí)行完成后線(xiàn)程就會(huì)退出。如果我們需要一個(gè)機(jī)制,讓線(xiàn)...
    Mirsiter_魏閱讀 670評(píng)論 0 2
  • Runloop是iOS和OSX開(kāi)發(fā)中非?;A(chǔ)的一個(gè)概念,從概念開(kāi)始學(xué)習(xí)。 RunLoop的概念 -般說(shuō),一個(gè)線(xiàn)程一...
    小貓仔閱讀 1,105評(píng)論 0 1
  • RunLoop的定義與概念RunLoop的主要作用main函數(shù)中的RunLoopRunLoop與線(xiàn)程的關(guān)系RunL...
    __silhouette閱讀 1,070評(píng)論 0 6
  • 內(nèi)存管理一直是學(xué)習(xí) Objective-C 的重點(diǎn)和難點(diǎn)之一,在實(shí)際的軟件開(kāi)發(fā)工作中,經(jīng)常會(huì)遇見(jiàn)由于內(nèi)存原因而導(dǎo)致...
    高思陽(yáng)閱讀 3,985評(píng)論 1 4

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