RunLoop源碼分析

一糜值、RunLoop的入口

通過(guò)再touchesBegan方法中添加斷點(diǎn),使用bt指令,可以顯示出方法調(diào)用棧

    frame #1: 0x00007fff480bf863 UIKitCore`forwardTouchMethod + 340
    frame #2: 0x00007fff480bf6fe UIKitCore`-[UIResponder touchesBegan:withEvent:] + 49
    frame #3: 0x00007fff480ce8de UIKitCore`-[UIWindow _sendTouchesForEvent:] + 1867
    frame #4: 0x00007fff480d04c6 UIKitCore`-[UIWindow sendEvent:] + 4596
    frame #5: 0x00007fff480ab53b UIKitCore`-[UIApplication sendEvent:] + 356
    frame #6: 0x00007fff4812c71a UIKitCore`__dispatchPreprocessedEventFromEventQueue + 6847
    frame #7: 0x00007fff4812f1e0 UIKitCore`__handleEventQueueInternal + 5980
    frame #8: 0x00007fff23bd4471 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
    frame #9: 0x00007fff23bd439c CoreFoundation`__CFRunLoopDoSource0 + 76
    frame #10: 0x00007fff23bd3b74 CoreFoundation`__CFRunLoopDoSources0 + 180
    frame #11: 0x00007fff23bce87f CoreFoundation`__CFRunLoopRun + 1263
    frame #12: 0x00007fff23bce066 CoreFoundation`CFRunLoopRunSpecific + 438
    frame #13: 0x00007fff384c0bb0 GraphicsServices`GSEventRunModal + 65
    frame #14: 0x00007fff48092d4d UIKitCore`UIApplicationMain + 1621
    frame #15: 0x0000000103c46fb4 runloop`main(argc=1, argv=0x00007ffeebfb8d00) at main.m:18:12
    frame #16: 0x00007fff5227ec25 libdyld.dylib`start + 1

從下到上的程序調(diào)用方法的過(guò)程橄抹,再UIApplicationMain之后調(diào)用了CFRunloop,其中CFRunLoopRunSpecific 方法是runloop的具體內(nèi)容蜜笤。

C語(yǔ)言的API CFRunLoopRef ,C語(yǔ)言的runloop 是開(kāi)源的濒蒋,下載地址 https://opensource.apple.com/tarballs/CF/
在源碼中,我們尋找CFRunLoopRunSpecific方法把兔,看看具體都執(zhí)行了哪些內(nèi)容

方法內(nèi)部有這樣一段代碼:

// 經(jīng)過(guò)精簡(jiǎn)的 CFRunLoopRunSpecific 函數(shù)代碼沪伙,其內(nèi)部會(huì)調(diào)用__CFRunLoopRun函數(shù)
/*
 * 指定mode運(yùn)行runloop
 * @param rl 當(dāng)前運(yùn)行的runloop
 * @param modeName 需要運(yùn)行的mode的name
 * @param seconds  runloop的超時(shí)時(shí)間
 * @param returnAfterSourceHandled 是否處理完事件就返回
 */
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {
    CHECK_FOR_FORK();
    // RunLoop正在釋放,完成返回
    if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
    __CFRunLoopLock(rl);
        // 根據(jù)modeName 取出當(dāng)前的運(yùn)行Mode
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false
    // 如果沒(méi)找到 || mode中沒(méi)有注冊(cè)任何事件县好,則就此停止围橡,不進(jìn)入循環(huán)                                           
    if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
        Boolean did = false;
        if (currentMode) 
            __CFRunLoopModeUnlock(currentMode);
            __CFRunLoopUnlock(rl);
            return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
    }
                                                       
    volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl
        //取上一次運(yùn)行的mode
    CFRunLoopModeRef previousMode = rl->_currentMode;
    //如果本次mode和上次的mode一致                                                          
    rl->_currentMode = currentMode;
    //初始化一個(gè)result為kCFRunLoopRunFinished                                                                   
    int32_t result = kCFRunLoopRunFinished;

    if (currentMode->_observerMask & kCFRunLoopEntry ) 
        // 1. 通知 Observers: 進(jìn)入RunLoop。                                                 
        __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);     
        // 2.RunLoop的運(yùn)行循環(huán)的核心代碼                                                
        result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
                                                       
    if (currentMode->_observerMask & kCFRunLoopExit ) 
        // 12. 通知 Observers: 退出RunLoop                                               
        __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
        __CFRunLoopModeUnlock(currentMode);
        __CFRunLoopPopPerRunData(rl, previousPerRun);
        rl->_currentMode = previousMode;
    __CFRunLoopUnlock(rl);
    return result;
}

上面代碼包含了以下內(nèi)容
1.如果當(dāng)前mode中沒(méi)有注冊(cè)任何的事件缕贡,不會(huì)進(jìn)入runloop
2.進(jìn)入runloop 發(fā)送entry通知 kCFRunLoopEntry
3.執(zhí)行runloop核心內(nèi)容在__CFRunLoopRun方法
4.runloop結(jié)束翁授,發(fā)送退出runloop通知 kCFRunLoopExit

__CFRunLoopRun 方法中具有runloop的整個(gè)執(zhí)行過(guò)程,其中主要代碼

static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
/// 方法內(nèi)部就是一個(gè) do...while 循環(huán)
    int32_t retVal = 0;
    do {
        // 1.發(fā)送observer 通知 kCFRunLoopBeforeTimers 即將處理timer
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
       // 2.發(fā)送observer通知kCFRunLoopBeforeSources 即將處理source
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources); 
      // 3.開(kāi)始處理blocks
        __CFRunLoopDoBlocks(rl, rlm);
       // 開(kāi)始處理source
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        if (sourceHandledThisLoop) {
        // 有可能還會(huì)繼續(xù)處理blocks
            __CFRunLoopDoBlocks(rl, rlm);
        }
        // 判斷是否有source1晾咪,如果有source1 跳轉(zhuǎn)到handle_msg步驟執(zhí)行
        if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
            // 如果有source1 跳轉(zhuǎn)到handle_msg
            goto handle_msg;
        }
        // 如果沒(méi)有source1 發(fā)送通知即將進(jìn)入休眠狀態(tài)
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        __CFRunLoopSetSleeping(rl);
        CFAbsoluteTime sleepStart = poll ? 0.0 : CFAbsoluteTimeGetCurrent();
        do { // 等待別的消息喚醒線(xiàn)程 收擦, 一旦喚醒,會(huì)繼續(xù)向下走禀酱,如果沒(méi)有炬守,則會(huì)阻塞在這
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
        } while (1);
      // 線(xiàn)程被喚醒,結(jié)束休眠狀態(tài)
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
/// 被喚醒之后剂跟,同樣會(huì)進(jìn)入handle_msg 中减途,判斷是如何被喚醒的酣藻,處理事件
    handle_msg:;
        __CFRunLoopSetIgnoreWakeUps(rl);
        
        if (被timer喚醒) {
            CFRUNLOOP_WAKEUP_FOR_TIMER();
            if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                __CFArmNextTimerInMode(rlm, rl);
            }
        }else if (被GCD喚醒) {
            CFRUNLOOP_WAKEUP_FOR_DISPATCH();
        __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
        } else { 被source1喚醒
            CFRUNLOOP_WAKEUP_FOR_SOURCE();
            __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply)
        }
// 處理block
        __CFRunLoopDoBlocks(rl, rlm);
//繼續(xù)循環(huán)返回第一步,依次向下執(zhí)行
    } while (0 == retVal);
    return retVal;
}

總結(jié)runloop執(zhí)行流程


9695297-05bce790991b43e9.jpeg

二鳍置、RunLoop的應(yīng)用

RunLoop的五種模式
(1) kCFRunLoopDefaultMode:App的默認(rèn)Mode辽剧,通常主線(xiàn)程是在這個(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)完成后就不再使用,會(huì)切換到kCFRunLoopDefaultMode
(4)GSEventReceiveRunLoopMode: 接受系統(tǒng)事件的內(nèi)部 Mode辟拷,通常用不到
(5)kCFRunLoopCommonModes: 這是一個(gè)占位用的Mode撞羽,作為標(biāo)記kCFRunLoopDefaultMode和UITrackingRunLoopMode用,并不是一種真正的Mode

1.NSTimer

NSTimer的運(yùn)行是基于RunLoop的衫冻,當(dāng)被添加到RunLoop中之后诀紊,RunLoop會(huì)根據(jù)它的時(shí)間間隔注冊(cè)相應(yīng)的時(shí)間點(diǎn),到時(shí)間點(diǎn)之后隅俘,喚醒RunLoop觸發(fā)timer事件邻奠,所以使用NSTimer 的時(shí)候,要將timer添加到runloop中并且制定mode为居。否則碌宴,將不會(huì)執(zhí)行

方法1.創(chuàng)建timer,手動(dòng)添加到RunLoop中蒙畴,如果不添加贰镣,將不會(huì)執(zhí)行timer

    __block int i = 0;
     NSTimer *timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
        NSLog(@"%d",i);
         i++;
    }];
/// 將timer添加到當(dāng)前runloop的default模式下,如果不添加這段代碼忍抽,timer將不會(huì)被執(zhí)行
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];

方法2.自動(dòng)添加到當(dāng)前的Runloop 的default模式下

__block int i = 0;
[NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
        NSLog(@"%d",i);
        i++;
    }];

NSTimer 在UITableView/UIScrollView/UICollectionView中滑動(dòng)的時(shí)候八孝,會(huì)導(dǎo)致失效。
原因是鸠项,在滾動(dòng)的時(shí)候干跛,主線(xiàn)程中的RunLoop切換到了UITrackingRunLoopMode模式下,如果timer不在這個(gè)模式里面祟绊,將會(huì)導(dǎo)致timer不會(huì)被執(zhí)行楼入,因此在這種情況下,需要同時(shí)將timer添加到NSDefaultRunLoopMode和UITrackingRunLoopMode模式下牧抽,或者直接使用 NSRunLoopCommonModes

注意:

使用NSTimer的時(shí)候可能會(huì)造成時(shí)間不準(zhǔn)確和循環(huán)引用無(wú)法退出的問(wèn)題嘉熊。

1.1 NSTimer的循環(huán)引用問(wèn)題
如果使用方法
 [NSTimer timerWithTimeInterval:(NSTimeInterval) target:(nonnull id) selector:(nonnull SEL) userInfo:(nullable id) repeats:(BOOL)]
創(chuàng)建NSTimer,并且控制器擁一個(gè)屬性 timer 指向這個(gè)NSTimer扬舒,那么就會(huì)產(chǎn)出循環(huán)引用
原因是阐肤,控制器強(qiáng)引用NSTimer,NSTimer中的target屬性又強(qiáng)引用控制器,最終導(dǎo)致無(wú)法釋放

單純的使用弱引用是無(wú)法解決這個(gè)問(wèn)題孕惜,__weak 弱指針是用于Block的解決方案愧薛。此時(shí)應(yīng)該使用一個(gè)代理,NSProxy衫画。原理是毫炉,控制器強(qiáng)引用NSTimer,NSTimer強(qiáng)引用NSProxy削罩,NSProxy弱引用控制器瞄勾。注意,NSProxy沒(méi)有方法實(shí)現(xiàn)弥激,它主要是應(yīng)用消息轉(zhuǎn)發(fā)機(jī)制进陡,將消息轉(zhuǎn)發(fā)給控制器,還是由控制器實(shí)現(xiàn)具體操作內(nèi)容秆撮。

1.2 NSTimer的不準(zhǔn)確性

使用NSTimer定時(shí)器的時(shí)候四濒,有可能會(huì)造成時(shí)間的不準(zhǔn)確性换况。
因?yàn)镹STimer 是通過(guò)RunLoop實(shí)現(xiàn)的职辨。
而RunLoop處理定時(shí)器的方法是:

RunLoop會(huì)記錄執(zhí)行一圈需要多少時(shí)間。如果定時(shí)器是1秒鐘執(zhí)行一次戈二,而RunLoop跑一圈是0.2秒舒裤,那么RunLoop跑5圈的時(shí)候,才會(huì)去執(zhí)行一次timer事件觉吭。然而腾供,RunLoop跑一圈的時(shí)間,有時(shí)會(huì)比較長(zhǎng)鲜滩,可能會(huì)跑5圈時(shí)候伴鳖,超過(guò)了1秒。所以徙硅,執(zhí)行timer事件的時(shí)間就會(huì)比1秒長(zhǎng)榜聂,導(dǎo)致NSTimer具有不準(zhǔn)確性

可以使用GCD做定時(shí)器,因?yàn)镚CD的定時(shí)器是直接操作內(nèi)核的嗓蘑,不需要RunLoop须肆。
實(shí)現(xiàn)代碼:

// 執(zhí)行的隊(duì)列
    dispatch_queue_t queue = dispatch_get_main_queue();
    // 創(chuàng)建定時(shí)器
    _source =   dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    // 設(shè)置時(shí)間
    // start 開(kāi)始時(shí)間
    NSTimeInterval start = 2.0;//兩秒后開(kāi)始
    // interval 執(zhí)行間隔時(shí)間
    NSTimeInterval intervla = 1.0;// 執(zhí)行的時(shí)間間隔
    // leeway 誤差
    dispatch_source_set_timer(_source, dispatch_time(DISPATCH_TIME_NOW, start * NSEC_PER_SEC), intervla * NSEC_PER_SEC, 0);
    // 設(shè)置回調(diào)
    dispatch_source_set_event_handler(_source, ^{
        NSLog(@"123");
    });
    // 啟動(dòng)定時(shí)器
    dispatch_resume(_source);

RunLoop源碼剖析---圖解RunLoop

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市桩皿,隨后出現(xiàn)的幾起案子豌汇,更是在濱河造成了極大的恐慌,老刑警劉巖泄隔,帶你破解...
    沈念sama閱讀 216,692評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件拒贱,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡佛嬉,警方通過(guò)查閱死者的電腦和手機(jī)逻澳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,482評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)岩调,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人赡盘,你說(shuō)我怎么就攤上這事号枕。” “怎么了陨享?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,995評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵葱淳,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我抛姑,道長(zhǎng)赞厕,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,223評(píng)論 1 292
  • 正文 為了忘掉前任定硝,我火速辦了婚禮皿桑,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蔬啡。我一直安慰自己诲侮,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,245評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布箱蟆。 她就那樣靜靜地躺著沟绪,像睡著了一般。 火紅的嫁衣襯著肌膚如雪空猜。 梳的紋絲不亂的頭發(fā)上绽慈,一...
    開(kāi)封第一講書(shū)人閱讀 51,208評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音辈毯,去河邊找鬼坝疼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛谆沃,可吹牛的內(nèi)容都是我干的钝凶。 我是一名探鬼主播,決...
    沈念sama閱讀 40,091評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼管毙,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼腿椎!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起夭咬,我...
    開(kāi)封第一講書(shū)人閱讀 38,929評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤啃炸,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后卓舵,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體南用,經(jīng)...
    沈念sama閱讀 45,346評(píng)論 1 311
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,570評(píng)論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了裹虫。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片肿嘲。...
    茶點(diǎn)故事閱讀 39,739評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖筑公,靈堂內(nèi)的尸體忽然破棺而出雳窟,到底是詐尸還是另有隱情,我是刑警寧澤匣屡,帶...
    沈念sama閱讀 35,437評(píng)論 5 344
  • 正文 年R本政府宣布封救,位于F島的核電站,受9級(jí)特大地震影響捣作,放射性物質(zhì)發(fā)生泄漏誉结。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,037評(píng)論 3 326
  • 文/蒙蒙 一券躁、第九天 我趴在偏房一處隱蔽的房頂上張望惩坑。 院中可真熱鬧,春花似錦也拜、人聲如沸以舒。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,677評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)稀轨。三九已至,卻和暖如春岸军,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背瓦侮。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,833評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工艰赞, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人肚吏。 一個(gè)月前我還...
    沈念sama閱讀 47,760評(píng)論 2 369
  • 正文 我出身青樓方妖,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親罚攀。 傳聞我的和親對(duì)象是個(gè)殘疾皇子党觅,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,647評(píng)論 2 354