Run Loops

1. Run Loops

Run loops是線程基礎(chǔ)機(jī)構(gòu)中非常重要的一環(huán)缤削。它是一個處理事件的循環(huán)窘哈,幫助你安排事件工作和協(xié)調(diào)接收到的事件。其目的是在工作時讓線程處理亭敢,在沒工作時讓線程休眠滚婉。
runloop的不是完全自管理的,你必須在適當(dāng)?shù)臅r機(jī)啟動它來處理接收到的事件帅刀。你可以使用Cocoa和Core Foundation中的run loop object來配置和管理線程的runloop让腹。你不必手動創(chuàng)建一個runloop远剩,因?yàn)榘ㄖ骶€程在內(nèi)的每個線程都包含一個run loop object。但是你得手動啟動子線程的runloop骇窍,而主線程的runloop在引用啟動時就被啟動運(yùn)行了瓜晤。

2. Run Loop解析

runloop的內(nèi)部是一個do-while循環(huán)。它接收兩種不同類型的sources(Input sourcesTimer sources)分發(fā)的事件腹纳。Input sources分發(fā)異步的事件痢掠,Timer sources分發(fā)同步的事件。

image.png

此外嘲恍,在處理sources分發(fā)的事件時足画,runloop同時會產(chǎn)生關(guān)于runloop狀態(tài)的通知。你可以注冊runloop observers來接收這些通知佃牛。

2.1Run Loop Modes

runloop mode是一個input sources锌云、timers和observers的集合。每次你運(yùn)行一個runloop時吁脱,你必須指定一個mode。同時只有在這個mode中的input sources彬向、timers和observers才能得到處理兼贡。
每個mode都可以指定一個name用來區(qū)分。

Mode Name Description
Default NSDefaultRunLoopMode kCFRunLoopDefaultMode 默認(rèn)mode娃胆,runloop中最常用的mode
Connection NSConnectionReplyMode 用于監(jiān)測NSConnection對象的回應(yīng)遍希,很少需要用到
Modal NSModalPanelRunLoopMode 用于識別事件的modal
Event tracking NSEventTrackingRunLoopMode 追蹤觸摸手勢,限制其他接收到的事件里烦,確保界面刷新不會卡頓凿蒜。
Common modes NSRunLoopCommonModes kCFRunLoopCommonModes 常用的modes組合。默認(rèn)包含default胁黑、modal和event tracking modes废封。
2.2 Input Sources

input sources包含port-based sources和custom input sources。port-based sources監(jiān)聽?wèi)?yīng)用的Mach ports丧蘸。custom input sources監(jiān)聽事件的custom sources漂洋。

2.2.1 Port-Based Sources(source1)

在Cocoa中,你只需要使用NSPort來增加port到runloop中力喷。這個port對象會為你處理需要的input sources的創(chuàng)建和配置刽漂。在Core Foundation中,你必須通過CFMachPortRef,CFMessagePortRef,CFSocketRef手動創(chuàng)建port和它的runloop source弟孟。

2.2.2 Custom Input Sources(source0)

你需要使用CFRunLoopSourceRef相關(guān)的函數(shù)來創(chuàng)建一個自定義輸入源贝咙。你要通過回調(diào)函數(shù)來配置自定義輸入源,從而處理接收到的事件和在從runloop中移除時關(guān)閉source拂募。同時你必須定義事件的分發(fā)機(jī)制庭猩。相關(guān)的例子可以參考 Defining a Custom Input Source窟她。它主要處理UIEvent、CFSocket這樣的事件眯娱。

2.2.3 Cocoa Perform Selector Sources

Cocoa定義了一種自定義輸入源礁苗,它允許你在任何線程上perform selector。與port-based sources類似徙缴,在目標(biāo)線程上perform selector請求也是連續(xù)的试伙,這樣減輕了線程中多方法執(zhí)行的同步問題。與port-based sources不同的是于样,perform selector在執(zhí)行后自動從runloop中移除疏叨。
當(dāng)然,perform selector要想在指定線程中執(zhí)行穿剖,這個線程的runloop必須是啟動的蚤蔓。

2.3 Timer Sources

計時器用來通知線程做指定的一些事情。但是計時器的時間不是實(shí)時的糊余,有時可能會延后秀又。當(dāng)這個線程的runloop不在計時器指定的mode中時,那么計時器將會被暫停贬芥,直到runloop重新回到計時器指定的mode中去吐辙。

2.4 Run Loop Observers

一個計時器或者輸入源開始時會給觀察者發(fā)送通知。runloop中的通知包含以下:

  • 進(jìn)入runloop
  • runloop即將處理計時器
  • runloop即將處理輸入源
  • runloop即將進(jìn)入睡眠
  • runloop被喚醒
  • runloop退出
2.5 run loop事件隊(duì)列
image.png

3 什么時候使用Run Loop

只有當(dāng)你創(chuàng)建一個子線程時你才需要啟動runloop蘸劈。對于子線程昏苏,你需要考慮是否需要啟動runloop。比如威沫,你需要執(zhí)行一些耗時的操作時贤惯,就不需要啟動runloop。啟動runloop是為了能夠與線程互動棒掠。你需要啟動runloop在以下情況:

  • 使用輸入源來與其他線程通信
  • 在子線程上使用計時器
  • 使用performselector...方法
  • 需要子線程執(zhí)行周期性任務(wù)

4.Run Loop實(shí)踐

4.1 TableView中實(shí)現(xiàn)平滑滾動延遲加載圖片

由于滾動時runloop的current mode是UITrackingRunLoopMode孵构。如果此時圖片在加載且較慢的話,會影響runloop的循環(huán)句柠,導(dǎo)致滾動卡頓浦译。因此可以利用CFRunLoopMode特性,將圖片的加載放到NSDefaultRunLoopMode的mode里溯职,這樣在滾動的時候就不會加載圖片精盅,等滾動完current mode切換為NSDefaultRunLoopMode時再加載圖片。

UIImage *downloadedImage = ...;
[self.avatarImageView performSelector:@selector(setImage:)
 withObject:downloadedImage
 afterDelay:0
 inModes:@[NSDefaultRunLoopMode]];
4.2 卡頓檢測

通過分析上面講過的runloop事件隊(duì)列谜酒。runloop處理事件主要集中在step3(kCFRunLoopBeforeSources)之后以及step9(kCFRunLoopAfterWaiting)之后叹俏。所以可以創(chuàng)建一個observer來觀察這兩個狀態(tài),同時通過semphore來監(jiān)測這兩個狀態(tài)的處理時間僻族。如果好幾次出現(xiàn)大于某個值則認(rèn)為是出現(xiàn)了UI卡頓粘驰。

- (void)startMonitor {
    if (_runLoopObserver) {
        return;
    }
    self.semaphore = dispatch_semaphore_create(0);

    CFRunLoopObserverContext context = {
    0,
    (__bridge void*)self,
    NULL,
    NULL
    };
    _runLoopObserver = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, &runLoopObserverCallBack, &context);

    CFRunLoopAddObserver(CFRunLoopGetMain(), _runLoopObserver, kCFRunLoopCommonModes);

    dispatch_async(fluency_monitor_queue(), ^{
        while (YES) {
            long st = dispatch_semaphore_wait(self.semaphore, dispatch_time(DISPATCH_TIME_NOW, 500 * NSEC_PER_MSEC));
            if (st != 0) {
                if (!_runLoopObserver) {
                    _timeoutCount = 0;
                    self.semaphore = 0;
                    self.runLoopActivity = 0;
                    return;
                }
            
                if (self.runLoopActivity == kCFRunLoopBeforeSources || self.runLoopActivity == kCFRunLoopAfterWaiting) {
                    if (++_timeoutCount < 3) {
                        continue;
                    }
                    //出現(xiàn)了UI卡頓屡谐。可以打印堆棧信息看看哪個線程出了問題蝌数。
                
                }
            }
        }
    });
}

static void runLoopObserverCallBack(CFRunLoopObserverRef observer,
                                CFRunLoopActivity activity,
                                void* info) {
    StackMonitor *monitor = (__bridge StackMonitor*)info;
    monitor.runLoopActivity = activity;
    dispatch_semaphore_signal(monitor.semaphore);
}

5. Run Loop源碼

CFRunLoopRef的代碼是開源的愕掏,你可以在http://opensource.apple.com/tarballs/CF/里面下載到包含CFRunLoopRef在內(nèi)的整個CoreFoundation源碼。

CFRunLoop結(jié)構(gòu)體如下顶伞,包含了對應(yīng)的線程饵撑,modes等。

struct __CFRunLoop {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;          /* locked for accessing mode list */
    __CFPort _wakeUpPort;           // used for CFRunLoopWakeUp 
    Boolean _unused;
    volatile _per_run_data *_perRunData;              // reset for runs of the run loop
    pthread_t _pthread;        //runloop對應(yīng)的線程
    uint32_t _winthread;
    CFMutableSetRef _commonModes;        //所有標(biāo)記為common的mode集合
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode;        //當(dāng)前運(yùn)行的mode
    CFMutableSetRef _modes;
    struct _block_item *_blocks_head;
    struct _block_item *_blocks_tail;
    CFAbsoluteTime _runTime;
    CFAbsoluteTime _sleepTime;
    CFTypeRef _counterpart;
};

RunLoopMode包含了名稱和source0唆貌、source1滑潘、observers、timers的集成等锨咙。RunLoop管理RunLoopMode语卤,而RunLoopMode管理具體的事件。

struct __CFRunLoopMode {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;  /* must have the run loop locked before locking this */
    CFStringRef _name;    //mode名稱
    Boolean _stopped;    //mode是否被終止
    char _padding[3];
    CFMutableSetRef _sources0;    //source0(custom input source)
    CFMutableSetRef _sources1;    //source1(port-based input source)
    CFMutableArrayRef _observers;    //通知者
    CFMutableArrayRef _timers;    //計時器
    CFMutableDictionaryRef _portToV1SourceMap;    //記錄source1port的字典
    __CFPortSet _portSet;
    CFIndex _observerMask;      //觀察的runloop的狀態(tài)
#if USE_DISPATCH_SOURCE_FOR_TIMERS
    dispatch_source_t _timerSource;
    dispatch_queue_t _queue;
    Boolean _timerFired; // set to true by the source when a timer has fired
    Boolean _dispatchTimerArmed;
#endif
#if USE_MK_TIMER_TOO
    mach_port_t _timerPort;
    Boolean _mkTimerArmed;
#endif
#if DEPLOYMENT_TARGET_WINDOWS
    DWORD _msgQMask;
    void (*_msgPump)(void);
#endif
    uint64_t _timerSoftDeadline; /* TSR */
    uint64_t _timerHardDeadline; /* TSR */
};

CFRunLoopSource結(jié)構(gòu)體如下:

struct __CFRunLoopSource {
    CFRuntimeBase _base;
    uint32_t _bits;
    pthread_mutex_t _lock;
    CFIndex _order;         /* immutable */
    CFMutableBagRef _runLoops;    //添加該source的runloop
    union {
    CFRunLoopSourceContext version0;    /* immutable, except invalidation */
        CFRunLoopSourceContext1 version1;   /* immutable, except invalidation */
    } _context;
};

CFRunLoopObserver酪刀,觀察runloop的狀態(tài)粹舵,并拋出回調(diào)。

struct __CFRunLoopObserver {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;
    CFRunLoopRef _runLoop;      //觀察的runloop
    CFIndex _rlCount;
    CFOptionFlags _activities;      /* immutable */     //狀態(tài)
    CFIndex _order;         /* immutable */
    CFRunLoopObserverCallBack _callout; /* immutable */
    CFRunLoopObserverContext _context;  /* immutable, except invalidation */
};

CFRunLoopActivity的6種狀態(tài)骂倘。

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0), //即將進(jìn)入run loop
    kCFRunLoopBeforeTimers = (1UL << 1), //即將處理timer
    kCFRunLoopBeforeSources = (1UL << 2),//即將處理source
    kCFRunLoopBeforeWaiting = (1UL << 5),//即將進(jìn)入休眠
    kCFRunLoopAfterWaiting = (1UL << 6),//被喚醒但是還沒開始處理事件
    kCFRunLoopExit = (1UL << 7),//run loop已經(jīng)退出
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};

CFRunLoopTimer可以在設(shè)定的時間點(diǎn)拋出回調(diào)齐婴,另外它包含了mode集合,因此可以被添加到任何mode中去稠茂。

struct __CFRunLoopTimer {
    CFRuntimeBase _base;
    uint16_t _bits;
    pthread_mutex_t _lock;
    CFRunLoopRef _runLoop;    //添加該timer的runloop
    CFMutableSetRef _rlModes;    //mode集合
    CFAbsoluteTime _nextFireDate;    //下一次計時器開始時間
    CFTimeInterval _interval;       /* immutable */    //理想時間間隔
    CFTimeInterval _tolerance;          /* mutable */    //時間偏差
    uint64_t _fireTSR;          /* TSR units */
    CFIndex _order;         /* immutable */
    CFRunLoopTimerCallBack _callout;    /* immutable */
    CFRunLoopTimerContext _context; /* immutable, except invalidation */
};

在Core Foundation中通過以下兩個API來啟動runloop。第一個使用default mode情妖,第二個使用指定的mode睬关。

  • void CFRunLoopRun(void)
  • SInt32 CFRunLoopRunInMode(CFStringRef mode, CFTimeInterval seconds, Boolean returnAfterSourceHandled);
void CFRunLoopRun(void) {   /* DOES CALLOUT */
    int32_t result;
//do-while循環(huán)
    do {
        //DefaultMode方式啟動CFRunLoopRunSpecific
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(),kCFRunLoopDefaultMode, 1.0e10, false);
    CHECK_FOR_FORK();
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}

SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    CHECK_FOR_FORK();
    return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}

通過源碼可知,內(nèi)部都是運(yùn)行了CFRunLoopRunSpecific函數(shù)毡证。我們來看一下實(shí)現(xiàn)电爹。

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    CHECK_FOR_FORK();
    //1.如果已經(jīng)釋放掉r1,返回kCFRunLoopRunFinished
    if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
    //2.對r1上鎖
    __CFRunLoopLock(rl);
    //3.根據(jù)modeName找到當(dāng)前的mode
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
    //4.如果沒有找到或者mode為空,退出runloop
    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);
    //5.取出先前的mode
    CFRunLoopModeRef previousMode = rl->_currentMode;
    //6.設(shè)置當(dāng)前的mode
    rl->_currentMode = currentMode;
    //7.初始化一個kCFRunLoopRunFinished的result
    int32_t result = kCFRunLoopRunFinished;
    //8.如果當(dāng)前_observerMask為kCFRunLoopEntry,通知observer,然后進(jìn)入__CFRunLoopRun
    if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    //9.如果當(dāng)前_observerMask為kCFRunLoopExit料睛,通知observer丐箩,退出
    if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

        __CFRunLoopModeUnlock(currentMode);
        __CFRunLoopPopPerRunData(rl, previousPerRun);
    rl->_currentMode = previousMode;
    __CFRunLoopUnlock(rl);
    return result;
}

由上面的代碼可知,mode必須存在恤煞,且包含modeItem屎勘,這個runloop才能運(yùn)行。在進(jìn)入和退出runloop之前通知observer居扒。另外概漱,runloop的核心函數(shù)是__CFRunLoopRun。我們來看一下它的邏輯喜喂。

static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    ........
    //2.通知observer瓤摧,即將觸發(fā)計時器回調(diào)
    if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
    //3.通知observer竿裂,即將觸發(fā)source0回調(diào)
    if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
    //執(zhí)行加入當(dāng)前runloop的block
__CFRunLoopDoBlocks(rl, rlm);

    //4.處理source0事件
    Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
    if (sourceHandledThisLoop) {
        //執(zhí)行加入當(dāng)前runloop的block
        __CFRunLoopDoBlocks(rl, rlm);
    }

    Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
    
    if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
        msg = (mach_msg_header_t *)msg_buffer;
        //5.接收dispatchPort端口的source1事件,如果接收到照弥,前往第9步處理msg
        if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
            
            goto handle_msg;
        }
#elif DEPLOYMENT_TARGET_WINDOWS
        if (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) {
            goto handle_msg;
        }
#endif
    }

    didDispatchPortLastTime = false;
    
    //6.通知observer腻异,runloop即將休眠
if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
    //runloop進(jìn)入休眠
__CFRunLoopSetSleeping(rl);
// do not do any user callouts after this point (after notifying of sleeping)

    // Must push the local-to-this-activation ports in on every loop
    // iteration, as this mode could be run re-entrantly and we don't
    // want these ports to get serviced.

    __CFPortSetInsert(dispatchPort, waitSet);
    
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);

    CFAbsoluteTime sleepStart = poll ? 0.0 : CFAbsoluteTimeGetCurrent();

#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
#if USE_DISPATCH_SOURCE_FOR_TIMERS
    //7.do-while循環(huán),用于接收等待端口的消息这揣。
    // ? 一個內(nèi)核端口的source事件
    // ? 一個計時器到時間了
    // ? RunLoop自身的超時時間到了
    // ? 被其他什么調(diào)用者手動喚醒
    //滿足以上四個條件中一個悔常,退出循環(huán)
    do {
        if (kCFUseCollectableAllocator) {
            // objc_clear_stack(0);
            // <rdar://problem/16393959>
            memset(msg_buffer, 0, sizeof(msg_buffer));
        }
        msg = (mach_msg_header_t *)msg_buffer;
        
        __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
        
        if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
            // Drain the internal queue. If one of the callout blocks sets the timerFired flag, break out and service the timer.
            while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));
            if (rlm->_timerFired) {
                // Leave livePort as the queue port, and service timers below
                rlm->_timerFired = false;
                break;
            } else {
                if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
            }
        } else {
            // Go ahead and leave the inner loop.
            break;
        }
    } while (1);
#else
    if (kCFUseCollectableAllocator) {
        // objc_clear_stack(0);
        // <rdar://problem/16393959>
        memset(msg_buffer, 0, sizeof(msg_buffer));
    }
    msg = (mach_msg_header_t *)msg_buffer;
    __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
#endif
    
    
#elif DEPLOYMENT_TARGET_WINDOWS
    // Here, use the app-supplied message queue mask. They will set this if they are interested in having this run loop receive windows messages.
    __CFRunLoopWaitForMultipleObjects(waitSet, NULL, poll ? 0 : TIMEOUT_INFINITY, rlm->_msgQMask, &livePort, &windowsMessageReceived);
#endif
    
    __CFRunLoopLock(rl);
    __CFRunLoopModeLock(rlm);

    rl->_sleepTime += (poll ? 0.0 : (CFAbsoluteTimeGetCurrent() - sleepStart));

    // Must remove the local-to-this-activation ports in on every loop
    // iteration, as this mode could be run re-entrantly and we don't
    // want these ports to get serviced. Also, we don't want them left
    // in there if this function returns.

    __CFPortSetRemove(dispatchPort, waitSet);
    
    __CFRunLoopSetIgnoreWakeUps(rl);

    // user callouts now OK again
__CFRunLoopUnsetSleeping(rl);
    //8.通知observer,runloop被喚醒
if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);

    //9.處理接收到的事件
    handle_msg:;
    __CFRunLoopSetIgnoreWakeUps(rl);

#if DEPLOYMENT_TARGET_WINDOWS
    if (windowsMessageReceived) {
        // These Win32 APIs cause a callout, so make sure we're unlocked first and relocked after
        __CFRunLoopModeUnlock(rlm);
    __CFRunLoopUnlock(rl);

        if (rlm->_msgPump) {
            rlm->_msgPump();
        } else {
            MSG msg;
            if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE | PM_NOYIELD)) {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
        }
        
        __CFRunLoopLock(rl);
    __CFRunLoopModeLock(rlm);
    sourceHandledThisLoop = true;
        
        // To prevent starvation of sources other than the message queue, we check again to see if any other sources need to be serviced
        // Use 0 for the mask so windows messages are ignored this time. Also use 0 for the timeout, because we're just checking to see if the things are signalled right now -- we will wait on them again later.
        // NOTE: Ignore the dispatch source (it's not in the wait set anymore) and also don't run the observers here since we are polling.
        __CFRunLoopSetSleeping(rl);
        __CFRunLoopModeUnlock(rlm);
        __CFRunLoopUnlock(rl);
        
        __CFRunLoopWaitForMultipleObjects(waitSet, NULL, 0, 0, &livePort, NULL);
        
        __CFRunLoopLock(rl);
        __CFRunLoopModeLock(rlm);            
        __CFRunLoopUnsetSleeping(rl);
        // If we have a new live port then it will be handled below as normal
    }
    
    
#endif
    if (MACH_PORT_NULL == livePort) {
        CFRUNLOOP_WAKEUP_FOR_NOTHING();
        // handle nothing
    } else if (livePort == rl->_wakeUpPort) {
        CFRUNLOOP_WAKEUP_FOR_WAKEUP();
        // do nothing on Mac OS
#if DEPLOYMENT_TARGET_WINDOWS
        // Always reset the wake up port, or risk spinning forever
        ResetEvent(rl->_wakeUpPort);
#endif
    }
#if USE_DISPATCH_SOURCE_FOR_TIMERS
    else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
        CFRUNLOOP_WAKEUP_FOR_TIMER();
        if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
            // Re-arm the next timer, because we apparently fired early
            __CFArmNextTimerInMode(rlm, rl);
        }
    }
#endif
#if USE_MK_TIMER_TOO
    //9.1如果計時器時間到了曾沈,觸發(fā)回調(diào)
    else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
        CFRUNLOOP_WAKEUP_FOR_TIMER();
        // On Windows, we have observed an issue where the timer port is set before the time which we requested it to be set. For example, we set the fire time to be TSR 167646765860, but it is actually observed firing at TSR 167646764145, which is 1715 ticks early. The result is that, when __CFRunLoopDoTimers checks to see if any of the run loop timers should be firing, it appears to be 'too early' for the next timer, and no timers are handled.
        // In this case, the timer port has been automatically reset (since it was returned from MsgWaitForMultipleObjectsEx), and if we do not re-arm it, then no timers will ever be serviced again unless something adjusts the timer list (e.g. adding or removing timers). The fix for the issue is to reset the timer here if CFRunLoopDoTimers did not handle a timer itself. 9308754
        if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
            // Re-arm the next timer
            __CFArmNextTimerInMode(rlm, rl);
        }
    }
#endif
    //9.2如果是dispatch到main_queue的block这嚣,執(zhí)行block
    else if (livePort == dispatchPort) {
        CFRUNLOOP_WAKEUP_FOR_DISPATCH();
        __CFRunLoopModeUnlock(rlm);
        __CFRunLoopUnlock(rl);
        _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
#if DEPLOYMENT_TARGET_WINDOWS
        void *msg = 0;
#endif
        __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
        _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
        __CFRunLoopLock(rl);
        __CFRunLoopModeLock(rlm);
        sourceHandledThisLoop = true;
        didDispatchPortLastTime = true;
    } else {
        CFRUNLOOP_WAKEUP_FOR_SOURCE();
        
        // If we received a voucher from this mach_msg, then put a copy of the new voucher into TSD. CFMachPortBoost will look in the TSD for the voucher. By using the value in the TSD we tie the CFMachPortBoost to this received mach_msg explicitly without a chance for anything in between the two pieces of code to set the voucher again.
        voucher_t previousVoucher = _CFSetTSD(__CFTSDKeyMachMessageHasVoucher, (void *)voucherCopy, os_release);

        // Despite the name, this works for windows handles as well
        CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
        if (rls) {
            //9.3如果有source1事件,處理source1事件
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
    mach_msg_header_t *reply = NULL;
    sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
    if (NULL != reply) {
        (void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
        CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);
    }
#elif DEPLOYMENT_TARGET_WINDOWS
            sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls) || sourceHandledThisLoop;
#endif
    }
        
        // Restore the previous voucher
        _CFSetTSD(__CFTSDKeyMachMessageHasVoucher, previousVoucher, os_release);
        
    } 
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
    if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
#endif
    
        //執(zhí)行加入到loop中的block
    __CFRunLoopDoBlocks(rl, rlm);
    

    if (sourceHandledThisLoop && stopAfterHandle) {
        retVal = kCFRunLoopRunHandledSource;
        } else if (timeout_context->termTSR < mach_absolute_time()) {   //runloop超時
            retVal = kCFRunLoopRunTimedOut;
    } else if (__CFRunLoopIsStopped(rl)) {  //runloop被stop
            __CFRunLoopUnsetStopped(rl);
        retVal = kCFRunLoopRunStopped;
    } else if (rlm->_stopped) {     //mode被stop
        rlm->_stopped = false;
        retVal = kCFRunLoopRunStopped;
    } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {     //mode為空
        retVal = kCFRunLoopRunFinished;
    }
    
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
        voucher_mach_msg_revert(voucherState);
        os_release(voucherCopy);
#endif
        //如果沒超時塞俱,mode沒空姐帚,loop沒被停止,繼續(xù)loop
    } while (0 == retVal);

    if (timeout_timer) {
        dispatch_source_cancel(timeout_timer);
        dispatch_release(timeout_timer);
    } else {
        free(timeout_context);
    }

    return retVal;
}

6. 總結(jié)

本文主要分析了runloop的機(jī)制以及源碼障涯,并給出了幾種相應(yīng)的應(yīng)用場景罐旗。

7. 參考

Run Loops
深入理解RunLoop
RunLoop系列之源碼分析
iOS開發(fā)優(yōu)化篇之卡頓檢測
CFRunLoop

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市唯蝶,隨后出現(xiàn)的幾起案子九秀,更是在濱河造成了極大的恐慌,老刑警劉巖粘我,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件鼓蜒,死亡現(xiàn)場離奇詭異,居然都是意外死亡征字,警方通過查閱死者的電腦和手機(jī)都弹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來匙姜,“玉大人畅厢,你說我怎么就攤上這事〉粒” “怎么了框杜?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長袖肥。 經(jīng)常有香客問我咪辱,道長,這世上最難降的妖魔是什么椎组? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任梧乘,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘选调。我一直安慰自己夹供,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布仁堪。 她就那樣靜靜地躺著哮洽,像睡著了一般。 火紅的嫁衣襯著肌膚如雪弦聂。 梳的紋絲不亂的頭發(fā)上鸟辅,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天,我揣著相機(jī)與錄音莺葫,去河邊找鬼匪凉。 笑死,一個胖子當(dāng)著我的面吹牛捺檬,可吹牛的內(nèi)容都是我干的再层。 我是一名探鬼主播,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼堡纬,長吁一口氣:“原來是場噩夢啊……” “哼聂受!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起烤镐,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤蛋济,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后炮叶,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體碗旅,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年镜悉,在試婚紗的時候發(fā)現(xiàn)自己被綠了扛芽。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡积瞒,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出登下,到底是詐尸還是另有隱情茫孔,我是刑警寧澤,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布被芳,位于F島的核電站缰贝,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏畔濒。R本人自食惡果不足惜剩晴,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧赞弥,春花似錦毅整、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至拼窥,卻和暖如春戏蔑,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鲁纠。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工总棵, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人改含。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓情龄,卻偏偏與公主長得像,于是被迫代替她去往敵國和親候味。 傳聞我的和親對象是個殘疾皇子刃唤,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評論 2 355

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