關(guān)于Runloop的原理探究及基本使用

一、什么是runloop

字面意思是“消息循環(huán)泌类、運(yùn)行循環(huán)”癞谒。它不是線(xiàn)程,但它和線(xiàn)程息息相關(guān)刃榨。
一般來(lái)講弹砚,一個(gè)線(xiàn)程一次只能執(zhí)行一個(gè)任務(wù),執(zhí)行完成后線(xiàn)程就會(huì)退出枢希。比如在c語(yǔ)言程序中桌吃,main函數(shù)執(zhí)行完程序就退出了,因此有時(shí)候需要一個(gè)while死循環(huán)來(lái)讓程序不死苞轿。而當(dāng)我們打開(kāi)一個(gè)IOS應(yīng)用之后读存,什么也不做,這時(shí)候看起來(lái)是沒(méi)有代碼在執(zhí)行的呕屎,但程序并沒(méi)有退出让簿。iOS這種沒(méi)有任務(wù)時(shí)仍然可以讓程序維持運(yùn)行的現(xiàn)象,都是得益于runloop秀睛。

runloop內(nèi)部實(shí)際上就是一個(gè)do-while循環(huán)尔当,它在循環(huán)監(jiān)聽(tīng)著各種事件源、消息,對(duì)他們進(jìn)行管理并分發(fā)給線(xiàn)程來(lái)執(zhí)行椭迎。

RunLoop 實(shí)際上就是一個(gè)對(duì)象锐帜,這個(gè)對(duì)象管理了其需要處理的事件和消息,并提供了一個(gè)入口函數(shù)來(lái)執(zhí)行上面 Event Loop 的邏輯畜号。線(xiàn)程執(zhí)行了這個(gè)函數(shù)后缴阎,就會(huì)一直處于這個(gè)函數(shù)內(nèi)部 "接受消息->等待->處理" 的循環(huán)中,直到這個(gè)循環(huán)結(jié)束(比如傳入 quit 的消息)简软,函數(shù)返回蛮拔。

iOS程序main.m文件中會(huì)有這樣一段代碼:

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

其中,UIApplicationMain函數(shù)內(nèi)部啟動(dòng)了runloop痹升,因此UIApplicationMain函數(shù)是一直都沒(méi)有返回的建炫,保持了程序的持續(xù)運(yùn)行。而這個(gè)默認(rèn)啟動(dòng)的runloop是和主線(xiàn)程相關(guān)的
總結(jié)runloop的作用:

  • 保證程序不退出
  • 負(fù)責(zé)處理各種事件(source疼蛾、timer肛跌、observer)
  • 如果沒(méi)有事件發(fā)生,會(huì)讓程序進(jìn)入休眠狀態(tài)察郁。這樣可以節(jié)省cup資源衍慎,提高程序性能,有事做就去處理皮钠,沒(méi)事做就休息西饵。

API
iOS 中提供了兩套API來(lái)訪(fǎng)問(wèn)和使用runloop:

  • CFRunLoopRef(CoreFoundation 框架),純 C 函數(shù) API鳞芙,所有API都是線(xiàn)程安全的。
  • NSRunLoop(Foundation框架)期虾, 是基于 CFRunLoopRef 的oc封裝原朝,提供了面向?qū)ο蟮?API,但是這些 API 不是線(xiàn)程安全的镶苞。

二喳坠、runloop與線(xiàn)程

CFRunLoop 是基于 pthread 來(lái)管理的,而pthread和NSThread是一一對(duì)應(yīng)的茂蚓。

主線(xiàn)程的runloop自動(dòng)創(chuàng)建壕鹉,子線(xiàn)程的runloop默認(rèn)不創(chuàng)建。
runloop是不能夠通過(guò)alloc init來(lái)創(chuàng)建聋涨。要獲取runloop可以通過(guò)這兩個(gè)函數(shù)CFRunLoopGetMain()CFRunLoopGetCurrent()來(lái)獲得主線(xiàn)程晾浴、當(dāng)前線(xiàn)程runloop(實(shí)質(zhì)是一種懶加載)。(在NSRunloop中對(duì)應(yīng)就是- mainRunloop- currentRunloop方法)

由蘋(píng)果源碼牍白,這兩個(gè)函數(shù)的內(nèi)部實(shí)現(xiàn)看出線(xiàn)程和runloop是一一對(duì)應(yīng)的脊凰,這種對(duì)應(yīng)關(guān)系用一個(gè)字典保存起來(lái),key是pthread茂腥,value是CFRunLoopRef:

源碼

源碼

runloop在第一次獲取時(shí)創(chuàng)建狸涌,然后在線(xiàn)程結(jié)束時(shí)銷(xiāo)毀切省。所以,在子線(xiàn)程如果不手動(dòng)獲取runloop帕胆,它是一直都不會(huì)有的朝捆。

三、RunLoop相關(guān)類(lèi)

在 CoreFoundation 里面關(guān)于 RunLoop 有5個(gè)類(lèi):CFRunLoopRef懒豹、CFRunLoopModeRef芙盘、CFRunLoopSourceRefCFRunLoopTimerRef歼捐、CFRunLoopObserverRef
其關(guān)系如下:

截自 深入理解RunLoop

  • 一個(gè) RunLoop可以有多個(gè)Mode何陆,每個(gè) Mode 又包含若干個(gè) Source/Timer/Observer。
  • Source/Timer/Observer又叫mode item豹储。不同mode下的mode item互不影響
  • 一個(gè) item可被加入不同的mode贷盲。但一個(gè) item 被重復(fù)加入同一個(gè) mode 時(shí)是不會(huì)有效果的。如果一個(gè) mode 中一個(gè) item 都沒(méi)有剥扣,RunLoop退出巩剖。(不過(guò)如果僅僅依賴(lài)沒(méi)有mode item來(lái)讓runloop退出,這做法是不可靠的)

Mode Item

  • source钠怯。CFRunLoopSourceRef事件源
    按照官方文檔CFRunLoopSourceRef為3類(lèi)佳魔,但數(shù)據(jù)結(jié)構(gòu)只有兩類(lèi)(source0、source1)
    官方文檔截圖

一晦炊、官方文檔版分類(lèi):
RunLoop只處理兩種源:輸入源鞠鲜、時(shí)間源。
而輸入源又可以分為:NSPort断国、自定義源贤姆、performSelector:OnThread:delay:

  • 基于端口的源:與內(nèi)核端口相關(guān)。只需要簡(jiǎn)單的創(chuàng)建端口對(duì)象稳衬,并使用NSPort的方法將端口對(duì)象加入到run loop霞捡。端口對(duì)象會(huì)處理創(chuàng)建以及配置輸入源。對(duì)應(yīng)的是source1.
  • 自定義源:使用CFRunLoopSourceRef類(lèi)型相關(guān)的函數(shù)來(lái)創(chuàng)建自定義輸入源薄疚,比如CFRunLoopSourceCreate碧信?。一般用不到
  • selector :執(zhí)行完后會(huì)自動(dòng)清除出runloop(這是文檔的說(shuō)法街夭,和實(shí)際測(cè)試不一樣砰碴。基于端口的源不會(huì))板丽。
    關(guān)于Selector Sources要說(shuō)的幾點(diǎn):
    一般有這么幾種performxxx
/// 主線(xiàn)程
performSelectorOnMainThread:withObject:waitUntilDone:
performSelectorOnMainThread:withObject:waitUntilDone:modes:
/// 指定線(xiàn)程
performSelector:onThread:withObject:waitUntilDone:
performSelector:onThread:withObject:waitUntilDone:modes:
/// 針對(duì)當(dāng)前線(xiàn)程
performSelector:withObject:afterDelay:         
performSelector:withObject:afterDelay:inModes:
/// 取消衣式,在當(dāng)前線(xiàn)程,和上面兩個(gè)方法對(duì)應(yīng)
cancelPreviousPerformRequestsWithTarget:
cancelPreviousPerformRequestsWithTarget:selector:object:

經(jīng)過(guò)測(cè)試:
1.對(duì)于onMainThreadonthread這兩種情況,創(chuàng)建的是source0任務(wù)碴卧。
如果調(diào)用線(xiàn)程和指定線(xiàn)程為同一線(xiàn)程:
1.1wait參數(shù)設(shè)為YES(阻塞當(dāng)前線(xiàn)程直到selector執(zhí)行完)弱卡,那么aSelector會(huì)直接在指定線(xiàn)程運(yùn)行,不會(huì)添加到runloop住册。(其實(shí)就有點(diǎn)類(lèi)似于線(xiàn)程死鎖)
1.2wait參數(shù)為NO婶博,selector源添加到runloop且執(zhí)行完不會(huì)自動(dòng)清除出runloop。
如果調(diào)用線(xiàn)程和指定線(xiàn)程不是同一線(xiàn)程:selector源添加到runloop且執(zhí)行完不會(huì)自動(dòng)清除出runloop荧飞。
2.而performSelector:withObject:afterDelay則不是source0而是timer凡人,使用是添加到runloop,執(zhí)行完會(huì)自動(dòng)移除出runloop叹阔。

還有一些方法:
  - (id)performSelector:(SEL)aSelector;
  - (id)performSelector:(SEL)aSelector withObject:(id)object;
  - (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;

他們是同步執(zhí)行的挠轴,和線(xiàn)程無(wú)關(guān),主線(xiàn)程子線(xiàn)程都可以用耳幢。不會(huì)添加到runloop岸晦,而是直接執(zhí)行,相當(dāng)于是[self xxx]這樣調(diào)用睛藻,只不過(guò)是編譯期启上、運(yùn)行期處理的不同。

時(shí)間源:基于時(shí)間的觸發(fā)器店印,上層對(duì)應(yīng)NSTimer冈在。

二、源碼版(source0/source1):

  • source0 非基于port的:負(fù)責(zé)App內(nèi)部事件按摘,由App負(fù)責(zé)管理觸發(fā)包券,例如UIEvent、UITouch事件炫贤。包含了一個(gè)回調(diào)溅固,不能主動(dòng)觸發(fā)事件。使用時(shí)照激,你需要先調(diào)用 CFRunLoopSourceSignal(source),將這個(gè) Source 標(biāo)記為待處理盹牧,然后手動(dòng)調(diào)用 CFRunLoopWakeUp(runloop)來(lái)喚醒 RunLoop俩垃,讓其處理這個(gè)事件。
    -performSelector:onThread:withObject:waitUntilDone: inModes:創(chuàng)建的是source0任務(wù)汰寓。
  • source1 基于port的:包含一個(gè) mach_port 和一個(gè)回調(diào)口柳,可監(jiān)聽(tīng)系統(tǒng)端口和通過(guò)內(nèi)核和其他線(xiàn)程發(fā)送的消息,能主動(dòng)喚醒runloop有滑,接收分發(fā)系統(tǒng)事件跃闹。

Source1和Timer都屬于端口事件源,不同的是所有的Timer都共用一個(gè)端口(Timer Port),而每個(gè)Source1都有不同的對(duì)應(yīng)端口望艺。
Source0屬于input Source中的一部分苛秕,Input Source還包括cuntom自定義源,由其他線(xiàn)程手動(dòng)發(fā)出找默。

  • timer
    CFRunLoopTimerRef是基于時(shí)間的觸發(fā)器艇劫,基本上說(shuō)的就是NSTimer。在預(yù)設(shè)的時(shí)間點(diǎn)喚醒runloop執(zhí)行回調(diào)惩激。因?yàn)樗腔赗unLoop的店煞,因此它不是實(shí)時(shí)的(就是NSTimer 是不準(zhǔn)確的。 因?yàn)镽unLoop只負(fù)責(zé)分發(fā)源的消息风钻。如果線(xiàn)程當(dāng)前正在處理繁重的任務(wù)顷蟀,就有可能導(dǎo)致Timer本次延時(shí),或者少執(zhí)行一次)骡技。

  • observer
    CFRunLoopObserverRef觀(guān)察者鸣个,監(jiān)聽(tīng)runloop的狀態(tài)。通過(guò)回調(diào)接收狀態(tài)變化哮兰。它不屬于runloop的事件源毛萌。

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry         = (1UL << 0), // 即將進(jìn)入Loop
    kCFRunLoopBeforeTimers  = (1UL << 1), // 即將處理 Timer
    kCFRunLoopBeforeSources = (1UL << 2), // 即將處理 Source
    kCFRunLoopBeforeWaiting = (1UL << 5), // 即將進(jìn)入休眠
    kCFRunLoopAfterWaiting  = (1UL << 6), // 剛從休眠中喚醒
    kCFRunLoopExit          = (1UL << 7), // 即將退出Loop
};

Mode 暴露的管理 mode item 的接口有下面幾個(gè):

CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);

Mode

CFRunLoopModeRef。每次啟動(dòng)RunLoop時(shí)喝滞,只能指定其中一個(gè) Mode阁将,這個(gè)就是CurrentMode。要切換 Mode右遭,只能退出 Loop做盅,再重新指定一個(gè) Mode 進(jìn)入。

數(shù)據(jù)結(jié)構(gòu):

 struct __CFRunLoopMode {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;  /* must have the run loop locked before locking this */
//mode名
    CFStringRef _name;
    Boolean _stopped;
    char _padding[3];
//source0 源
    CFMutableSetRef _sources0;
//source1 源
    CFMutableSetRef _sources1;
//observer 源
    CFMutableArrayRef _observers;
//timer 源
    CFMutableArrayRef _timers;

//mach port 到 mode的映射,為了在runloop主邏輯中過(guò)濾runloop自己的port消息窘哈。
    CFMutableDictionaryRef _portToV1SourceMap;

//記錄了所有當(dāng)前mode中需要監(jiān)聽(tīng)的port吹榴,作為調(diào)用監(jiān)聽(tīng)消息函數(shù)的參數(shù)。
    __CFPortSet _portSet;
    CFIndex _observerMask;
#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
//使用 mk timer滚婉, 用到的mach port图筹,和source1類(lèi)似,都依賴(lài)于mach port
    mach_port_t _timerPort;
    Boolean _mkTimerArmed;
#endif
//timer觸發(fā)的理想時(shí)間
    uint64_t _timerSoftDeadline; /* TSR */
//timer觸發(fā)的實(shí)際時(shí)間让腹,理想時(shí)間加上tolerance(偏差)
    uint64_t _timerHardDeadline; /* TSR */
};

系統(tǒng)默認(rèn)注冊(cè)了5個(gè)mode远剩,以下兩個(gè)是比較常用的:
1.kCFRunLoopDefaultMode (NSDefaultRunLoopMode),默認(rèn)模式
2.UITrackingRunLoopMode骇窍, scrollview滑動(dòng)時(shí)就是處于這個(gè)模式下瓜晤。保證界面滑動(dòng)時(shí)不受其他mode影響

CFRunLoop對(duì)外暴露的管理 Mode 接口只有下面2個(gè):

CFRunLoopAddCommonMode(CFRunLoopRef runloop, CFStringRef modeName);
CFRunLoopRunInMode(CFStringRef modeName, ...);

對(duì)于傳入的 mode name 如果runLoop 內(nèi)部沒(méi)有對(duì)應(yīng) mode 時(shí),runLoop會(huì)自動(dòng)創(chuàng)建對(duì)應(yīng)的 CFRunLoopModeRef腹纳。mode只能添加不能刪除

kCFRunLoopCommonModes(NSRunLoopCommonModes)

它是一個(gè)占位用的mode痢掠,它不是真正意義上的mode驱犹。
如果要在線(xiàn)程中開(kāi)啟runloop,這樣寫(xiě)是不對(duì)的:[[NSRunLoop currentRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate distantFuture]];

下面來(lái)看看CFRunLoop中有關(guān)CommonMode的結(jié)構(gòu):

struct __CFRunLoop {
    CFMutableSetRef _commonModes;     // Set
    CFMutableSetRef _commonModeItems; // Set<Source/Observer/Timer>
    CFRunLoopModeRef _currentMode;    // Current Runloop Mode
    CFMutableSetRef _modes;           // Set
    ...
};

有兩個(gè)要說(shuō)明的地方:
1.關(guān)于mode:一個(gè)mode可以標(biāo)記為common屬性(用CFRunLoopAddCommonMode函數(shù))足画,然后它就會(huì)保存在_commonModes雄驹。主線(xiàn)程已有的兩個(gè)modekCFRunLoopDefaultModeUITrackingRunLoopMode 都已經(jīng)是CommonModes了。
2.關(guān)于item:_commonModeItems里面存放的source, observer, timer等锌云,在每次runLoop運(yùn)行的時(shí)候都會(huì)被同步到具有Common標(biāo)記的Modes里荠医。比如這樣:[[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];就是把timer放到commonItem里。

四桑涎、runloop的實(shí)現(xiàn)

在這一部分會(huì)涉及到比較多的源碼彬向。源碼比較長(zhǎng)(兩三百行,后來(lái)我還寫(xiě)了注釋攻冷。娃胆。),如果只想了解runloop大致流程可以跳過(guò)等曼,后面有圖表總結(jié)里烦。
當(dāng)然..源碼我也并不是完完全全每一句都讀懂了,如果有寫(xiě)得不對(duì)的地方請(qǐng)指正禁谦。

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {    
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
    __CFRunLoopLock(rl);
    //根據(jù)mode name找到對(duì)應(yīng)的mode
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
    //如果mode里沒(méi)有source/timer/observer,直接返回胁黑。
    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);
    CFRunLoopModeRef previousMode = rl->_currentMode;
    rl->_currentMode = currentMode;
    int32_t result = kCFRunLoopRunFinished;

    //1.通知 Observers:即將進(jìn)入runloop。
    if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    //內(nèi)部函數(shù)州泊,進(jìn)入loop
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    //10.通知 Observers:RunLoop 即將退出丧蘸。
    if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

        __CFRunLoopModeUnlock(currentMode);
        __CFRunLoopPopPerRunData(rl, previousPerRun);
    rl->_currentMode = previousMode;
    __CFRunLoopUnlock(rl);
    return result;
}
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    uint64_t startTSR = mach_absolute_time();//獲取系統(tǒng)啟動(dòng)之后 的內(nèi)核時(shí)間

    //如果當(dāng)前runLoop或者runLoopMode為停止?fàn)顟B(tài)的話(huà)直接返回
    if (__CFRunLoopIsStopped(rl)) {
        __CFRunLoopUnsetStopped(rl);
    return kCFRunLoopRunStopped;
    } else if (rlm->_stopped) {
    rlm->_stopped = false;
    return kCFRunLoopRunStopped;
    }

    mach_port_name_t dispatchPort = MACH_PORT_NULL;//用來(lái)保存主隊(duì)列(mainQueue)的端口蒸走。mach端口--線(xiàn)程之間通信
    //判斷當(dāng)前線(xiàn)程是否主線(xiàn)程(當(dāng)前runloop是否主線(xiàn)程runloop)遭赂,如果是就分發(fā)一個(gè)隊(duì)列調(diào)度端口
    Boolean libdispatchQSafe = pthread_main_np() && ((HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL == previousMode) || (!HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && 0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ)));
    // 只有在MainRunLoop汤踏,才會(huì)有下面這行賦值淹接,否則 dispatchPort 為NULL
    if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name))
        dispatchPort = _dispatch_get_main_queue_port_4CF();
    
    
#if USE_DISPATCH_SOURCE_FOR_TIMERS
    //給當(dāng)前mode分發(fā)隊(duì)列端口
    mach_port_name_t modeQueuePort = MACH_PORT_NULL;
    if (rlm->_queue) {
        modeQueuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue);
        if (!modeQueuePort) {
            CRASH("Unable to get port for run loop mode queue (%d)", -1);
        }
    }
#endif
    //gcd有關(guān)的東西,實(shí)現(xiàn)runloop超時(shí)管理庙楚。
    /*
     struct __timeout_context {
     dispatch_source_t ds;
     CFRunLoopRef rl;//runloop
     uint64_t termTSR;//超時(shí)時(shí)間點(diǎn)膝擂?
     };
     */
    //對(duì)gcd的一些api不是很熟悉亚斋,但大概看懂是主要通過(guò)dispatch_source_t創(chuàng)建計(jì)時(shí)器样悟;精度很高拂募,系統(tǒng)自動(dòng)觸發(fā),是系統(tǒng)級(jí)別的源
    dispatch_source_t timeout_timer = NULL;
    struct __timeout_context *timeout_context = (struct __timeout_context *)malloc(sizeof(*timeout_context));//超時(shí)上下文
    if (seconds <= 0.0) { //seconds:設(shè)置的runloop超時(shí)時(shí)間
        seconds = 0.0;
        timeout_context->termTSR = 0ULL;
    } else if (seconds <= TIMER_INTERVAL_LIMIT) {//設(shè)置的超時(shí)時(shí)間在最大限制內(nèi)窟她,才創(chuàng)建timeout_timer
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, DISPATCH_QUEUE_OVERCOMMIT);
        timeout_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
        dispatch_retain(timeout_timer);
        timeout_context->ds = timeout_timer;
        timeout_context->rl = (CFRunLoopRef)CFRetain(rl);
        timeout_context->termTSR = startTSR + __CFTimeIntervalToTSR(seconds);
        dispatch_set_context(timeout_timer, timeout_context); //綁定// source gets ownership of context
        dispatch_source_set_event_handler_f(timeout_timer, __CFRunLoopTimeout);
        dispatch_source_set_cancel_handler_f(timeout_timer, __CFRunLoopTimeoutCancel);
        uint64_t ns_at = (uint64_t)((__CFTSRToTimeInterval(startTSR) + seconds) * 1000000000ULL);
        dispatch_source_set_timer(timeout_timer, dispatch_time(1, ns_at), DISPATCH_TIME_FOREVER, 1000ULL);
        dispatch_resume(timeout_timer);
    } else { // infinite timeout 超時(shí)時(shí)間無(wú)窮盡
        seconds = 9999999999.0;
        timeout_context->termTSR = UINT64_MAX;
    }

    Boolean didDispatchPortLastTime = true;
    int32_t retVal = 0;
    do {
        uint8_t msg_buffer[3 * 1024];
        mach_msg_header_t *msg = NULL;
        mach_port_t livePort = MACH_PORT_NULL;
        __CFPortSet waitSet = rlm->_portSet;//_portSet:記錄了所有當(dāng)前mode中需要監(jiān)聽(tīng)的port陈症,作為調(diào)用監(jiān)聽(tīng)消息函數(shù)的參數(shù)。

        __CFRunLoopUnsetIgnoreWakeUps(rl);//不忽略端口喚醒
        //2. 通知Observers:通知即將處理timer
        if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        //3. 通知Observers:通知即將處理Source0(非port)礁苗。
        if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);

        //處理加入到runLoop中的block爬凑。(非延遲的主線(xiàn)程徙缴?)
        __CFRunLoopDoBlocks(rl, rlm);
        //4.處理source0事件
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        //處理block
        if (sourceHandledThisLoop) {
            __CFRunLoopDoBlocks(rl, rlm);
        }

        //poll標(biāo)志著有沒(méi)有處理source0的消息试伙,如果沒(méi)有則為false嘁信,反之為true
        //poll=NO的情況:沒(méi)有source0且超時(shí)時(shí)間!=0
        Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);//(后一條件似乎是必然為false的)
        
        //主線(xiàn)程runloop 端口存在、didDispatchPortLastTime為假(首次執(zhí)行不會(huì)進(jìn)入判斷疏叨,因?yàn)閐idDispatchPortLastTime為true)
        if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
            msg = (mach_msg_header_t *)msg_buffer;
            //__CFRunLoopServiceMachPort用于接受指定端口(一個(gè)也可以是多個(gè))的消息,最后一個(gè)參數(shù)代表當(dāng)端口無(wú)消息的時(shí)候是否休眠,0是立刻返回不休眠,TIMEOUT_INFINITY代表休眠
            //處理通過(guò)GCD派發(fā)到主線(xiàn)程的任務(wù),這些任務(wù)優(yōu)先級(jí)最高會(huì)被最先處理
            //5.如果有Source1潘靖,就直接跳轉(zhuǎn)去處理消息。(文檔說(shuō)是檢測(cè)source1蚤蔓,不過(guò)源碼看來(lái)是檢測(cè)dispatchPort---gcd端口事件)
            if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0)) {
                goto handle_msg;//如果端口有事件則跳轉(zhuǎn)至handle_msg
            }
        }

        didDispatchPortLastTime = false;
        
        //之前沒(méi)有處理過(guò)source0卦溢,也沒(méi)有source1消息,就讓線(xiàn)程進(jìn)入睡眠秀又。
        //6.通知 Observers: RunLoop 的線(xiàn)程即將進(jìn)入休眠
        if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting))
            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        // 標(biāo)志當(dāng)前runLoop為休眠狀態(tài)
        __CFRunLoopSetSleeping(rl);

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

#if USE_DISPATCH_SOURCE_FOR_TIMERS
        //  進(jìn)入循環(huán)開(kāi)始不斷的讀取端口信息单寂,如果端口有喚醒信息則喚醒當(dāng)前runLoop
        do {
            if (kCFUseCollectableAllocator) {
                objc_clear_stack(0);
                memset(msg_buffer, 0, sizeof(msg_buffer));
            }
            msg = (mach_msg_header_t *)msg_buffer;
            //7. 調(diào)用 mach_msg 等待接受 mach_port 的消息。線(xiàn)程將進(jìn)入休眠, 直到被下面某一個(gè)事件喚醒吐辙。
            /// ? 一個(gè)基于 port 的Source 的事件宣决。
            /// ? 一個(gè) Timer 到時(shí)間了
            /// ? RunLoop 自身的超時(shí)時(shí)間到了
            /// ? 被其他什么調(diào)用者手動(dòng)喚醒
            
            //如果poll為no,且waitset中無(wú)port有消息,線(xiàn)程進(jìn)入休眠昏苏;否則喚醒
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);
            //livePort是modeQueuePort尊沸,則代表為當(dāng)前mode隊(duì)列的端口
            if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
                //不太懂
                while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));
                //知道Timer被激活了才跳出二級(jí)循環(huán)繼續(xù)循環(huán)一級(jí)循環(huán)
                if (rlm->_timerFired) {
                    rlm->_timerFired = false;
                    break;
                } else {
                    if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
                }
            }
            //如果livePort不為modeQueuePort,runLoop被喚醒贤惯。這代表__CFRunLoopServiceMachPort給出的livePort只有兩種可能:一種情況為MACH_PORT_NULL洼专,另一種為真正獲取的消息的端口。
            else {
                // Go ahead and leave the inner loop.
                break;
            }
        } while (1);
#else
        if (kCFUseCollectableAllocator) {
            objc_clear_stack(0);
            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);
#endif
        
        __CFRunLoopLock(rl);
        __CFRunLoopModeLock(rlm);
        __CFPortSetRemove(dispatchPort, waitSet);
        //忽略端口喚醒
        __CFRunLoopSetIgnoreWakeUps(rl);
        // user callouts now OK again
        __CFRunLoopUnsetSleeping(rl);
        //8.通知 Observers:RunLoop的線(xiàn)程剛剛被喚醒了孵构。
        if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting))
            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
        
        //處理端口消息
        handle_msg:;
        __CFRunLoopSetIgnoreWakeUps(rl);//設(shè)置此時(shí)runLoop忽略端口喚醒(保證線(xiàn)程安全)

  
        // 9.處理待處理的事件
        if (MACH_PORT_NULL == livePort) {//什么都不做(超時(shí)或..?
            CFRUNLOOP_WAKEUP_FOR_NOTHING();
            // handle nothing
        } 
        //struct __CFRunLoop中有這么一項(xiàng):__CFPort _wakeUpPort屁商,用于手動(dòng)將當(dāng)前runloop線(xiàn)程喚醒,通過(guò)調(diào)用CFRunLoopWakeUp完成浦译,CFRunLoopWakeUp會(huì)向_wakeUpPort發(fā)送一條消息
        else if (livePort == rl->_wakeUpPort) {//只有外界調(diào)用CFRunLoopWakeUp才會(huì)進(jìn)入此分支棒假,這是外部主動(dòng)喚醒runLoop的接口
            CFRUNLOOP_WAKEUP_FOR_WAKEUP();//喚醒
            // do nothing on Mac OS
        }
#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
        //處理因timer的喚醒。9.1 如果一個(gè) Timer 到時(shí)間了精盅,觸發(fā)這個(gè)Timer的回調(diào)帽哑。
        else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
            CFRUNLOOP_WAKEUP_FOR_TIMER();
            if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                // Re-arm the next timer
                __CFArmNextTimerInMode(rlm, rl);
            }
        }
#endif
        else if (livePort == dispatchPort) {
            CFRUNLOOP_WAKEUP_FOR_DISPATCH();
            __CFRunLoopModeUnlock(rlm);
            __CFRunLoopUnlock(rl);
            _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);

            //9.2處理異步方法喚醒。處理gcd dispatch到main_queue的block叹俏,執(zhí)行block妻枕。
            /*有判斷是否是在MainRunLoop,有獲取Main_Queue 的port粘驰,并且有調(diào)用 Main_Queue 上的回調(diào)屡谐,這只能是是 GCD 主隊(duì)列上的異步任務(wù)。即:dispatch_async(dispatch_get_main_queue(), block)產(chǎn)生的任務(wù)蝌数。
             */
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
            _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
            __CFRunLoopLock(rl);
            __CFRunLoopModeLock(rlm);
            sourceHandledThisLoop = true;
            didDispatchPortLastTime = true;
        } else {
            // 9.3處理Source1 (基于port)
            CFRUNLOOP_WAKEUP_FOR_SOURCE();
            // Despite the name, this works for windows handles as well
            //過(guò)濾macPort消息愕掏,有一些消息不一定是runloop中注冊(cè)的,這里只處理runloop中注冊(cè)的消息顶伞,在rlm->_portToV1SourceMap通過(guò)macPort找有沒(méi)有對(duì)應(yīng)的runloopMode
            CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
            if (rls) {
                mach_msg_header_t *reply = NULL;
                // 處理Source1
                sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
                if (NULL != reply) {
                    //當(dāng)前線(xiàn)程處理完source1饵撑,給發(fā)消息的線(xiàn)程反饋消息
                    (void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
                    CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);
                }
            }
        } 
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
        if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
#endif
        //block處理
        __CFRunLoopDoBlocks(rl, rlm);
        
        // 進(jìn)入loop時(shí)參數(shù)說(shuō)處理完事件就返回剑梳。
    if (sourceHandledThisLoop && stopAfterHandle) {
        retVal = kCFRunLoopRunHandledSource;
        }
        // 超出傳入?yún)?shù)標(biāo)記的超時(shí)時(shí)間了
    else if (timeout_context->termTSR < mach_absolute_time()) {
            retVal = kCFRunLoopRunTimedOut;
    }
        // 被外部調(diào)用者強(qiáng)制停止了
    else if (__CFRunLoopIsStopped(rl)) {
            __CFRunLoopUnsetStopped(rl);
        retVal = kCFRunLoopRunStopped;
    } else if (rlm->_stopped) {
        rlm->_stopped = false;
        retVal = kCFRunLoopRunStopped;
    }
        // source/timer/observer一個(gè)都沒(méi)有了
    else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
        retVal = kCFRunLoopRunFinished;
    }
        // 如果沒(méi)超時(shí),mode里沒(méi)空滑潘,loop也沒(méi)被停止垢乙,那繼續(xù)loop。
    } while (0 == retVal);

    //釋放定時(shí)器
    if (timeout_timer) {
        dispatch_source_cancel(timeout_timer);
        dispatch_release(timeout_timer);
    } else {
        free(timeout_context);
    }

    return retVal;
}

函數(shù)內(nèi)部主要做了一個(gè)do-while循環(huán)语卤,線(xiàn)程一直處于 "等待消息->接受->處理" 的循環(huán)中追逮,直到這個(gè)循環(huán)結(jié)束。

根據(jù)蘋(píng)果官方文檔說(shuō)明粹舵,上面的代碼歸納起來(lái)就是下面這十步:


流程歸納

主線(xiàn)程runloop會(huì)處理gcd端口事件源钮孵。上面第五步從源碼來(lái)看是檢測(cè)gcd端口事件..這里還是有點(diǎn)疑惑的。

關(guān)于runloop的休眠與喚醒

對(duì)于runloop而言最核心的事情就是保證線(xiàn)程在沒(méi)有消息的時(shí)候休眠眼滤,在有消息時(shí)喚醒油猫,以提高程序性能。runloop這個(gè)機(jī)制是依靠系統(tǒng)內(nèi)核來(lái)完成的(蘋(píng)果操作系統(tǒng)核心組件Darwin中的Mach)柠偶。

Mach提供了諸如處理器調(diào)度情妖、IPC (進(jìn)程間通信)等基礎(chǔ)服務(wù)。在 Mach 中诱担,進(jìn)程毡证、線(xiàn)程和虛擬內(nèi)存都被稱(chēng)為"對(duì)象"。Mach 的對(duì)象間只能通過(guò)消息傳遞的方式實(shí)現(xiàn)對(duì)象間的通信蔫仙。消息在兩個(gè)端口 (port) 之間傳遞料睛,這就是 Mach 的 IPC (進(jìn)程間通信) 的核心。

Runloop本質(zhì):mach_msg()
Runloop通過(guò)mach_msg()函數(shù)接收摇邦、發(fā)送消息恤煞。它的本質(zhì)是調(diào)用函數(shù)mach_msg_trap(),相當(dāng)于是一個(gè)系統(tǒng)調(diào)用施籍,會(huì)觸發(fā)內(nèi)核狀態(tài)切換居扒。當(dāng)你在用戶(hù)態(tài)調(diào)用 mach_msg_trap() 時(shí)會(huì)觸發(fā)陷阱機(jī)制,切換到內(nèi)核態(tài)丑慎;內(nèi)核態(tài)中內(nèi)核實(shí)現(xiàn)的 mach_msg() 函數(shù)會(huì)完成實(shí)際的工作喜喂。

截自 深入理解runloop

runloop用mach_msg()這個(gè)函數(shù)去接收消息,**如果沒(méi)有內(nèi)核發(fā)送port 消息過(guò)來(lái)竿裂,內(nèi)核會(huì)將線(xiàn)程置于等待狀態(tài) mach_msg_trap() **(當(dāng)前線(xiàn)程阻塞)玉吁。如果有消息返回(內(nèi)核開(kāi)新線(xiàn)程返回消息),判斷消息類(lèi)型處理事件腻异,并通過(guò)modeItem的callback回調(diào)进副。(總結(jié):基于port的source1,監(jiān)聽(tīng)端口悔常,端口有消息影斑,觸發(fā)回調(diào)曾沈;而source0,要手動(dòng)標(biāo)記為待處理和手動(dòng)喚醒runloop)
關(guān)于Mach消息發(fā)送機(jī)制鸥昏,可以看看這篇文章

現(xiàn)在回到runloop的休眠與喚醒的討論上來(lái)。
我們關(guān)注于上述十步流程中的第七步:

//  進(jìn)入循環(huán)開(kāi)始不斷的讀取端口信息姐帚,如果端口有喚醒信息則喚醒當(dāng)前runLoop
do {
    if (kCFUseCollectableAllocator) {
        objc_clear_stack(0);
        memset(msg_buffer, 0, sizeof(msg_buffer));
    }
    msg = (mach_msg_header_t *)msg_buffer;
    //7. 調(diào)用 mach_msg 等待接受 mach_port 的消息吏垮。線(xiàn)程將進(jìn)入休眠, 直到被下面某一個(gè)事件喚醒。
    /// ? 一個(gè)基于 port 的Source 的事件罐旗。
    /// ? 一個(gè) Timer 到時(shí)間了
    /// ? RunLoop 自身的超時(shí)時(shí)間到了
    /// ? 被其他什么調(diào)用者手動(dòng)喚醒
    
    //如果poll為no膳汪,且waitset中無(wú)port有消息,線(xiàn)程進(jìn)入休眠;否則喚醒
    __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);
    //livePort是modeQueuePort九秀,則代表為當(dāng)前mode隊(duì)列的端口
    if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
        //不太懂
        while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));
        //知道Timer被激活了才跳出二級(jí)循環(huán)繼續(xù)循環(huán)一級(jí)循環(huán)
        if (rlm->_timerFired) {
            rlm->_timerFired = false;
            break;
        } else {
            if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
        }
    }
    //如果livePort不為modeQueuePort遗嗽,runLoop被喚醒。這代表__CFRunLoopServiceMachPort給出的livePort只有兩種可能:一種情況為MACH_PORT_NULL鼓蜒,另一種為真正獲取的消息的端口痹换。
    else {
        // Go ahead and leave the inner loop.
        break;
    }
} while (1);

runloop的休眠狀態(tài)也是一個(gè)do-while循環(huán)。其中的核心處理函數(shù)__CFRunLoopServiceMachPort

//真正讓runloop休眠的地方
static Boolean __CFRunLoopServiceMachPort(mach_port_name_t port, mach_msg_header_t **buffer, size_t buffer_size, mach_port_t *livePort, mach_msg_timeout_t timeout) {
    Boolean originalBuffer = true;
    kern_return_t ret = KERN_SUCCESS;
    for (;;) {      /* In that sleep of death what nightmares may come ... */
        mach_msg_header_t *msg = (mach_msg_header_t *)*buffer;
        msg->msgh_bits = 0;
        msg->msgh_local_port = port;
        msg->msgh_remote_port = MACH_PORT_NULL;
        msg->msgh_size = buffer_size;
        msg->msgh_id = 0;
        //根據(jù)timerout參數(shù)的值來(lái)判斷是讓runloop 休眠還是繼續(xù)查詢(xún)(poll)
        if (TIMEOUT_INFINITY == timeout) { CFRUNLOOP_SLEEP(); } else { CFRUNLOOP_POLL(); }
        //調(diào)用mach_msg()函數(shù)接收消息
        ret = mach_msg(msg, MACH_RCV_MSG|MACH_RCV_LARGE|((TIMEOUT_INFINITY != timeout) ? MACH_RCV_TIMEOUT : 0)|MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0)|MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AV), 0, msg->msgh_size, port, timeout, MACH_PORT_NULL);
        CFRUNLOOP_WAKEUP(ret);//喚醒
        if (MACH_MSG_SUCCESS == ret) {//成功獲取到消息
            *livePort = msg ? msg->msgh_local_port : MACH_PORT_NULL;
            return true;
        }
        if (MACH_RCV_TIMED_OUT == ret) {//獲取消息超時(shí)
            if (!originalBuffer) free(msg);
            *buffer = NULL;
            *livePort = MACH_PORT_NULL;
            return false;
        }
        if (MACH_RCV_TOO_LARGE != ret) break;
        buffer_size = round_msg(msg->msgh_size + MAX_TRAILER_SIZE);
        if (originalBuffer) *buffer = NULL;
        originalBuffer = false;
        *buffer = realloc(*buffer, buffer_size);
    }
    HALT;
    return false;
}

__CFRunLoopServiceMachPort用于接受指定端口(一個(gè)也可以是多個(gè))的消息都弹,最后一個(gè)參數(shù)代表當(dāng)端口無(wú)消息的時(shí)候是否休眠娇豫,0是立刻返回不休眠,TIMEOUT_INFINITY代表休眠畅厢。
然后調(diào)用mach_msg()函數(shù)接收消息(如果沒(méi)有port 消息冯痢,內(nèi)核會(huì)將線(xiàn)程置于等待狀態(tài) mach_msg_trap(),所以來(lái)到這里框杜,runloop如果沒(méi)事做就休眠了)浦楣,根據(jù)接收消息的結(jié)果對(duì)livePort進(jìn)行賦值,一種是成功獲取到消息后咪辱,會(huì)根據(jù)情況賦值為msg->msgh_local_port或者MACH_PORT_NULL振劳,而另一種獲取消息超時(shí)的情況會(huì)賦值為MACH_PORT_NULL

然后在do-while循環(huán)那油狂,對(duì)livePortmodeQueuePort (當(dāng)前mode隊(duì)列端口)這一情況的if處理那幾行代碼看得不太懂澎迎,如果有知道那幾行代碼是做什么的同學(xué)請(qǐng)聯(lián)系我~。如果livePort不是modeQueuePort那么就是喚醒runloop的消息端口或者MACH_PORT_NULL选调。

接著來(lái)到第九步夹供,就會(huì)看到對(duì)livePort不同情況的處理了:

        if (MACH_PORT_NULL == livePort) {//什么都不做(超時(shí)或..?
            CFRUNLOOP_WAKEUP_FOR_NOTHING();
            // handle nothing
        }
        //struct __CFRunLoop中有這么一項(xiàng):__CFPort _wakeUpPort,用于手動(dòng)將當(dāng)前runloop線(xiàn)程喚醒仁堪,通過(guò)調(diào)用CFRunLoopWakeUp完成哮洽,CFRunLoopWakeUp會(huì)向_wakeUpPort發(fā)送一條消息
        else if (livePort == rl->_wakeUpPort) {//只有外界調(diào)用CFRunLoopWakeUp才會(huì)進(jìn)入此分支,這是外部主動(dòng)喚醒runLoop的接口
            CFRUNLOOP_WAKEUP_FOR_WAKEUP();//喚醒
        }
#if USE_DISPATCH_SOURCE_FOR_TIMERS
        else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
            CFRUNLOOP_WAKEUP_FOR_TIMER();
            ......
        }
#endif
#if USE_MK_TIMER_TOO
        //處理因timer的喚醒弦聂。9.1 如果一個(gè) Timer 到時(shí)間了鸟辅,觸發(fā)這個(gè)Timer的回調(diào)氛什。
        else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
            CFRUNLOOP_WAKEUP_FOR_TIMER();
            ......
        }
#endif
        else if (livePort == dispatchPort) {
            CFRUNLOOP_WAKEUP_FOR_DISPATCH(); 
            //9.2處理異步方法喚醒。處理gcd dispatch到main_queue的block匪凉,執(zhí)行block枪眉。
        } else {
            // 9.3處理Source1 (基于port)
            CFRUNLOOP_WAKEUP_FOR_SOURCE();
            ......
            if (rls) {
                __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
                ......
        } 

runloop的CallOut

runloop的回調(diào),一般都是通過(guò)一個(gè)名字很長(zhǎng)的函數(shù)再层,比如下面六個(gè):打斷點(diǎn)調(diào)試的時(shí)候可以在調(diào)用棧中看到他們的身影
(換句話(huà)說(shuō)你的代碼其實(shí)最終都是通過(guò)下面幾個(gè)函數(shù)來(lái)負(fù)責(zé)調(diào)用的贸铜,即使你自己監(jiān)聽(tīng)Observer也會(huì)先調(diào)用下面的函數(shù)然后間接通知你):

    static void __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__();
    static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__();
    static void __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__();
    static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__();
    static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__();
    static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__();

runloop退出的條件

一次性執(zhí)行;app退出聂受;線(xiàn)程關(guān)閉蒿秦;設(shè)置最大時(shí)間到期;modeItem為空(實(shí)際上observer不算是源蛋济,所以就算有observer也是會(huì)返回的)棍鳖;

五、如何使用

開(kāi)啟和關(guān)閉的接口

1.CFRunLoopRef(CoreFoundation 框架)

//運(yùn)行 CFRunLoopRef
void CFRunLoopRun();
//運(yùn)行 CFRunLoopRef: 參數(shù)為運(yùn)行模式碗旅、時(shí)間和是否在處理Input Source后退出標(biāo)志渡处,返回值是exit原因
SInt32 CFRunLoopRunInMode (mode, seconds, returnAfterSourceHandled);
//停止運(yùn)行 CFRunLoopRef
void CFRunLoopStop( CFRunLoopRef rl );

1.1

void CFRunLoopRun();
源碼
void CFRunLoopRun(void) {   /* DOES CALLOUT */
    int32_t result;
    do {
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
        CHECK_FOR_FORK();
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
  • 運(yùn)行在NSDefaultRunLoopMode模式下。直到調(diào)用CFRunLoopStop()強(qiáng)制停止(kCFRunLoopRunStopped)或者source/timer/一個(gè)都沒(méi)有了(kCFRunLoopRunFinished)祟辟。即源碼int32_t __CFRunLoopRun()(就是之前上面那個(gè)幾百行的)中的:

1.2

SInt32 CFRunLoopRunInMode (mode, seconds, returnAfterSourceHandled);
SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    CHECK_FOR_FORK();
    return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
  • 參數(shù)骂蓖。第一個(gè):runloop運(yùn)行模式。第二個(gè):運(yùn)行時(shí)間(超時(shí)時(shí)間)川尖。第三個(gè):是否在處理完source(source0\source1)之后讓runloop退出返回登下。
  • 接下來(lái)要講的NSRunloop中的兩個(gè)接口:- (void)runUntilDate:(NSDate *)limitDate;- (BOOL)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate;是基于這和函數(shù)進(jìn)行封裝的(猜測(cè))。
  • 源碼上看叮喳,和上一個(gè)函數(shù)一樣被芳,都用到CFRunLoopRunSpecific()所以最終都是調(diào)用int32_t __CFRunLoopRun()。因此也即能用CFRunLoopStop()來(lái)退出runloop馍悟。
  • 返回值畔濒。是導(dǎo)致runloop退出的原因。
enum {
    kCFRunLoopRunFinished = 1,
    kCFRunLoopRunStopped = 2,
    kCFRunLoopRunTimedOut = 3,
    kCFRunLoopRunHandledSource = 4
};

1.3

void CFRunLoopStop( CFRunLoopRef rl );

能停掉由上面兩個(gè)函數(shù)啟動(dòng)的runloop锣咒。

2.NSRunloop(Foundation框架)
//運(yùn)行模式為默認(rèn)的NSDefaultRunLoopMode模式侵状,沒(méi)有超時(shí)限制
- (void)run;
//運(yùn)行模式為默認(rèn)的NSDefaultRunLoopMode模式 ,參數(shù)為運(yùn)時(shí)間期限
- (void)runUntilDate:(NSDate *)limitDate;
//對(duì)runloop運(yùn)行模式毅整、時(shí)間期限可以自行設(shè)置
- (BOOL)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate;

2.1

- (void)run;
  • 循環(huán)一旦開(kāi)啟趣兄,就關(guān)閉不了,并且之后的代碼就無(wú)法執(zhí)行悼嫉。api文檔中提到:如果沒(méi)有輸入源和定時(shí)源加入到runloop中艇潭,runloop就馬上退出,否則通過(guò)頻繁調(diào)用-runMode:beforeDate:方法來(lái)讓runloop運(yùn)行在NSDefaultRunLoopMode模式下。
    但是蹋凝!人為地移除輸入源鲁纠、timer不能保證runloop會(huì)退出,因?yàn)橄到y(tǒng)有可能會(huì)自己添加一些源來(lái)處理事件鳍寂。(下面兩種方法也是)
  • 無(wú)法用CFRunLoopStop(runloopRef)退出改含,這種方式啟動(dòng)的runloop不利于控制,不建議使用迄汛。

2.2

- (void)runUntilDate:(NSDate *)limitDate;
  • 運(yùn)行在NSDefaultRunLoopMode模式捍壤,有超時(shí)時(shí)間限制。它實(shí)際上也是不斷調(diào)用-runMode:beforeDate:方法來(lái)讓runloop運(yùn)行在NSDefaultRunLoopMode模式下隔心,直到到達(dá)超時(shí)時(shí)間。
    調(diào)用CFRunLoopStop(runloopRef)無(wú)法停止Run Loop的運(yùn)行尚胞。為什么呢..因?yàn)檫@個(gè)方法只會(huì)結(jié)束當(dāng)前-runMode:beforeDate:的調(diào)用硬霍,之后的-runMode:beforeDate:該調(diào)用的還是會(huì)繼續(xù)。笼裳。唯卖。直到timeout。
  • 對(duì)應(yīng)CFRunLoopRunInMode(kCFRunLoopDefaultMode,limiteDate,false)

2.3

- (BOOL)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate;
  • 相比上一種方法可以指定運(yùn)行模式躬柬。
  • 對(duì)應(yīng)CFRunLoopRunInMode(mode,limiteDate,true)方法,只執(zhí)行一次拜轨,執(zhí)行完就退出。
  • 可以用CFRunLoopStop(runloopRef)退出runloop允青。
  • api文檔里面提到:在第一個(gè)input source(非timer)被處理或到達(dá)limitDate之后runloop退出橄碾。
 - (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    
    UIButton *btn = [[UIButton alloc]initWithFrame:CGRectMake(0, 80, 50, 50)];
    btn.backgroundColor = [UIColor redColor];
    [self.view addSubview:btn];
    [btn addTarget:self action:@selector(clicked) forControlEvents:UIControlEventTouchUpInside];
    
    NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(threadMethod) object:nil];
    self.thread = thread;
    [self.thread start];
}

 - (void)threadMethod{
    @autoreleasepool {   
        [[NSRunLoop currentRunLoop]addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        NSLog(@"thread end");
    }
}

 - (void)clicked{
    [self performSelector:@selector(doSomething) onThread:self.thread withObject:nil waitUntilDone:NO];
    }
}

 - (void)doSomething{
    NSLog(@"doSomething");
}
2017-06-04 21:05:55.316 Test[61381:5557174] doSomething
2017-06-04 21:05:55.317 Test[61381:5557174] thread end

給runloop添加源[NSMachPort port]保活颠锉,執(zhí)行完perform selector(重溫一下法牲,這里會(huì)給runloop添加一個(gè)源) runloop就退出了。

這里可以從源碼上解釋一下:
回顧上面那幾百行的源碼琼掠,我們可以看到其實(shí)這個(gè)runloop 的do-while循環(huán)是由一個(gè)變量retVal的值來(lái)控制的拒垃,也即代碼的最后幾行:

        // 進(jìn)入loop時(shí)參數(shù)說(shuō)處理完事件就返回。
    if (sourceHandledThisLoop && stopAfterHandle) {
        retVal = kCFRunLoopRunHandledSource;
        }
        // 超出傳入?yún)?shù)標(biāo)記的超時(shí)時(shí)間了
    else if (timeout_context->termTSR < mach_absolute_time()) {
            retVal = kCFRunLoopRunTimedOut;
    }
        // 被外部調(diào)用者強(qiáng)制停止了
    else if (__CFRunLoopIsStopped(rl)) {
            __CFRunLoopUnsetStopped(rl);
        retVal = kCFRunLoopRunStopped;
    } else if (rlm->_stopped) {
        rlm->_stopped = false;
        retVal = kCFRunLoopRunStopped;
    }
        // source/timer/observer一個(gè)都沒(méi)有了
    else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
        retVal = kCFRunLoopRunFinished;
    }
        // 如果沒(méi)超時(shí)瓷蛙,mode里沒(méi)空悼瓮,loop也沒(méi)被停止,那繼續(xù)loop艰猬。
    } while (0 == retVal);

- (BOOL)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate;横堡,對(duì)應(yīng)CFRunLoopRunInMode(mode,limiteDate,true)。因此

  if (sourceHandledThisLoop && stopAfterHandle) {
      retVal = kCFRunLoopRunHandledSource;
      }

中的stopAfterHandle值就是true冠桃。由于這里的perform源是source0翅萤,因此也即:

//4.處理source0事件
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);

sourceHandledThisLoop在這里賦值為true。
如果是source1源也是同理分析,所以在第一個(gè)input source被處理之后runloop退出套么。

一些比較基本的常見(jiàn)問(wèn)題

基本使用三步驟
添加事件到runloop中:1.創(chuàng)建事件(源) 2.指定該事件(源)在runloop中的運(yùn)行模式培己,并加入到runloop中 3.在與runloop的模式匹配時(shí),事件(源)運(yùn)行

1.ModeItem要在對(duì)應(yīng)的Mode下才會(huì)被runloop處理

主線(xiàn)程中執(zhí)行以下代碼:

- (void)viewDidLoad {
    [super viewDidLoad];
    //創(chuàng)建timer
//    參數(shù)1:間隔時(shí)間   參數(shù)2:對(duì)象   參數(shù)3:方法   參數(shù)4:自定義   參數(shù)5:是否重復(fù)執(zhí)行
    NSTimer *timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(task) userInfo:nil repeats:YES];
    //把定時(shí)源加入到當(dāng)前線(xiàn)程下的消息循環(huán)中
    [[NSRunLoop currentRunLoop]addTimer:timer forMode:NSDefaultRunLoopMode];
}
 - (void)task{
//    輸出當(dāng)前循環(huán)模式
    NSLog(@"%@",[[NSRunLoop currentRunLoop]currentMode]);
    NSLog(@"task is running");
}

設(shè)置timer在NSDefaultRunLoopMode模式下運(yùn)行胚泌。一開(kāi)始什么也不做省咨,timer正常運(yùn)行;當(dāng)對(duì)屏幕界面進(jìn)行滾動(dòng)時(shí)玷室,timer停止運(yùn)行零蓉。這是因?yàn)椋簺](méi)有拖動(dòng)界面時(shí)是kCFRunLoopDefaultMode,拖動(dòng)界面則是UITrackingRunLoopMode模式穷缤,與設(shè)置的模式不匹配所以無(wú)法運(yùn)行敌蜂。

如果需要在兩個(gè) Mode 中都能得到運(yùn)行,一種辦法就是將這個(gè) Timer 分別加入這兩個(gè) Mode津肛。還有一個(gè)方法章喉,就是將 Timer 加入到頂層的 RunLoop 的 commonItem中,在每次runLoop運(yùn)行的時(shí)候都會(huì)被同步到具有Common標(biāo)記的Modes里身坐。
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

ps:[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];就相當(dāng)于:上面viewDidLoad里面的兩句代碼秸脱,自動(dòng)添加到當(dāng)前runloop且在default mode模式下。如果要修改模式部蛇,調(diào)用一次addTimer:forMode:方法就可以了[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

2.子線(xiàn)程的runloop要手動(dòng)開(kāi)啟

往指定線(xiàn)程的runloop中加入源:performSelector...

- (void)viewDidLoad {
    [super viewDidLoad];
//創(chuàng)建子線(xiàn)程
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(task2) object:nil];
    [thread start];
    //往指定線(xiàn)程的消息循環(huán)中加入源
    //參數(shù)1:方法   參數(shù)2:指定線(xiàn)程   參數(shù)3:對(duì)象   參數(shù)4:等待方法執(zhí)行完成
    [self performSelector:@selector(addtask) onThread:thread withObject:nil waitUntilDone:NO];
    }
 -(void)task2{
    NSLog(@"task2 is running %@",[NSThread currentThread]);
}
 -(void)addtask{
    NSLog(@"addtask is running");
}

performSelector中的方法addtask不會(huì)執(zhí)行摊唇,因?yàn)榫€(xiàn)程的方法瞬間就執(zhí)行完了,線(xiàn)程就結(jié)束被回收了而且線(xiàn)程也不會(huì)監(jiān)聽(tīng)是否有方法繼續(xù)交給它執(zhí)行涯鲁。
每個(gè)線(xiàn)程有自己的runLoop, 我們可以通過(guò)[NSRunLoop currentRunLoop]CFRunLoopGetCurrent()來(lái)獲取巷查。 不過(guò)只有主線(xiàn)程的runLoop是默認(rèn)啟動(dòng)的,其他線(xiàn)程的runloop就需要我們手動(dòng)啟動(dòng)抹腿。

需要在子線(xiàn)程中開(kāi)啟runloop:

 - (void)task2{
    NSLog(@"task2 is running %@",[NSThread currentThread]);
//方式一
//    [[NSRunLoop currentRunLoop]run];
//    NSLog(@"over");//不會(huì)打印吮便,因?yàn)橐恢痹谘h(huán),沒(méi)有退出 
//方式二
//    [[NSRunLoop currentRunLoop]runUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]];
//    NSLog(@"over");    
//方式三幢踏,apple推薦
//    BOOL shouldKeepRunning = YES;        // global
    NSRunLoop *theRL = [NSRunLoop currentRunLoop];
    while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);//通過(guò)全局變量控制runloop的開(kāi)關(guān)
    NSLog(@"over");//這句不會(huì)執(zhí)行
}

over不會(huì)被打印出來(lái)

如果想runloop可以終止的髓需,官方推薦:- (BOOL)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate;官方example:

BOOL shouldKeepRunning = YES;        //全局變量
  NSRunLoop *theRL = [NSRunLoop currentRunLoop];
  while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);

通過(guò)全局變量控制runloop的開(kāi)關(guān)。

3.要給runloop添加modeItem房蝉,否則runloop退出

如果mode中一個(gè)item都沒(méi)有僚匆,runloop退出。下面稍微修改一下上面2的代碼:把performSelector方法放到touchesBegan中

- (void)viewDidLoad {
    [super viewDidLoad];
    //創(chuàng)建子線(xiàn)程
    self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(task2) object:nil];
    [self.thread start];
    //往指定線(xiàn)程的消息循環(huán)中加入源
    //參數(shù)1:方法   參數(shù)2:指定線(xiàn)程   參數(shù)3:對(duì)象   參數(shù)4:等待方法執(zhí)行完成
//    [self performSelector:@selector(addtask) onThread:thread withObject:nil waitUntilDone:NO];
}
-(void)task2{
    NSLog(@"task2 is running %@",[NSThread currentThread]);
//    [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
    while (YES && [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
    NSLog(@"over");
}
-(void)addtask{
    NSLog(@"addtask is running");
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    [self performSelector:@selector(addtask) onThread:self.thread withObject:nil waitUntilDone:NO];
}

運(yùn)行搭幻,"over"被打印出來(lái)了咧擂,證明runloop退出。點(diǎn)擊屏幕檀蹋,addtask方法沒(méi)有執(zhí)行松申。
這個(gè)方法創(chuàng)建Source 0 任務(wù),并分發(fā)到指定線(xiàn)程的 RunLoop 中。

- (void)performSelector:(SEL)aSelector
               onThread:(NSThread *)thr
             withObject:(id)arg
          waitUntilDone:(BOOL)wait
                  modes:(NSArray *)array;

那為什么和代碼修改前的運(yùn)行結(jié)果不一樣贸桶?個(gè)人理解是這樣的舅逸,2.中在子線(xiàn)程runloop開(kāi)啟前就已經(jīng)添加了source0事件源了。而現(xiàn)在修改后的代碼皇筛,把事件源的添加分離開(kāi)來(lái)了琉历,先開(kāi)了子線(xiàn)程,而等到用戶(hù)點(diǎn)擊屏幕的時(shí)候才會(huì)添加事件源水醋;對(duì)于子線(xiàn)程旗笔,runloop雖然創(chuàng)建并且開(kāi)啟了,但是因?yàn)橐恢睕](méi)有mode item拄踪,因此runloop馬上就退出了蝇恶。

那怎么解決?一種簡(jiǎn)單粗暴的方法惶桐,在runloop開(kāi)啟之前給它加個(gè)mode item咯:

-(void)task2{
    NSLog(@"task2 is running %@",[NSThread currentThread]);
//加個(gè)端口的源
    [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
    while (YES && [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
    NSLog(@"over");
}

如果這樣子改:

-(void)task2{
    NSLog(@"task2 is running %@",[NSThread currentThread]);
    while (1) {
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    }
}

在運(yùn)行上來(lái)講是沒(méi)問(wèn)題的撮弧,用戶(hù)點(diǎn)擊屏幕的時(shí)候addtask方法會(huì)執(zhí)行。不過(guò)耀盗,runloop一開(kāi)始沒(méi)有modeitem的問(wèn)題還是存在的想虎,這導(dǎo)致在while循環(huán)里面runloop不斷創(chuàng)建卦尊、啟動(dòng)叛拷、退出,直到點(diǎn)擊屏幕有事件源添加進(jìn)來(lái)為止岂却。

ps:

當(dāng)調(diào)用 NSObject 的 performSelecter:afterDelay: 后忿薇,實(shí)際上其內(nèi)部會(huì)創(chuàng)建一個(gè) Timer 并添加到當(dāng)前線(xiàn)程的 RunLoop 中。所以如果當(dāng)前線(xiàn)程沒(méi)有 RunLoop躏哩,則這個(gè)方法會(huì)失效署浩。

4.observer設(shè)置監(jiān)聽(tīng)runloop狀態(tài)
// 創(chuàng)建observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        NSLog(@"----監(jiān)聽(tīng)到RunLoop狀態(tài)發(fā)生改變---%zd", activity);
    });
    // 添加觀(guān)察者:監(jiān)聽(tīng)RunLoop的狀態(tài)
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);   
    // 釋放Observer
    CFRelease(observer);

創(chuàng)建observer,用CFRunLoopObserverCreateWithHandler函數(shù)扫尺,參數(shù)1:分配內(nèi)存 參數(shù)2:要監(jiān)聽(tīng)哪個(gè)runloop狀態(tài)的標(biāo)記 參數(shù)3:是否重復(fù)筋栋。這個(gè)observer是只調(diào)用一次還是runloop每次循環(huán)都調(diào) 參數(shù)4:優(yōu)先級(jí),一般傳0 參數(shù)5:回調(diào)

5.CF的內(nèi)存管理

1.凡是帶有Create正驻、Copy弊攘、Retain等字眼的函數(shù),創(chuàng)建出來(lái)的對(duì)象姑曙,都需要在最后做一次release
2.release函數(shù):CFRelease(對(duì)象);

上面這段代碼放到vc的viewDidLoad中跑襟交,其他什么也不加不做,會(huì)發(fā)現(xiàn)控制臺(tái)打印最后停在“32”的地方:

按鈕點(diǎn)擊前

這個(gè)就是kCFRunLoopBeforeWaiting = (1UL << 5), // runloop即將進(jìn)入休眠伤靠。然后點(diǎn)擊一下屏幕上的一個(gè)按鈕捣域,然后看控制臺(tái)輸出,可知runloop先喚醒,然后處理各種事件(包括點(diǎn)擊事件)焕梅,最后又回到休眠狀態(tài)
按鈕點(diǎn)擊后迹鹅,還有很多行打印沒(méi)截,但最后停在休眠32的地方

其他runloop相關(guān)

autoreleasepool自動(dòng)釋放池在什么時(shí)候釋放

先上代碼丘侠,運(yùn)行在MRC環(huán)境下:

Person.h

@interface Person : NSObject
@property (nonatomic,copy) NSString *name;
+ (instancetype)personInitWithName:(NSString *)name;
@end

Person.m

@implementation Person
+ (instancetype)personInitWithName:(NSString *)name{
//MRC內(nèi)存管理原則:誰(shuí)創(chuàng)建誰(shuí)釋放
    Person *person = [[[Person alloc]init]autorelease];//自動(dòng)釋放池 延時(shí)釋放
    person.name = name;
    return person;
}
@end

vc.m
#import "Person.h"
@property (nonatomic ,assign)Person *person1;
- (void)viewDidLoad{
    ........ 
    self.person1 = [Person personInitWithName:@"name1"];
    NSLog(@"%@",self.person1.name);
}
- (IBAction)clicked:(id)sender {
    NSLog(@"person.name:%@",self.person1.name);//MRC下點(diǎn)擊按鈕后報(bào)野指針錯(cuò)誤
}

MRC內(nèi)存管理原則:誰(shuí)創(chuàng)建誰(shuí)釋放徒欣。代碼中使用autorelease來(lái)對(duì)person對(duì)象進(jìn)行釋放。自動(dòng)釋放池釋放時(shí)蜗字,對(duì)池內(nèi)所有對(duì)象都發(fā)送一次release打肝,person對(duì)象被釋放。

運(yùn)行結(jié)果:在點(diǎn)擊完按鈕之后報(bào)野指針錯(cuò)誤挪捕,即對(duì)象已經(jīng)被釋放掉了粗梭。對(duì)此,可以從對(duì)象釋放反推出自動(dòng)釋放池已經(jīng)被釋放了级零。那為什么會(huì)在這個(gè)時(shí)候釋放断医?

先看下面這張圖:


iPhone應(yīng)用程序運(yùn)行->有觸摸事件->cocoaTouch創(chuàng)建事件,生成事件對(duì)象->cocoaTouch創(chuàng)建自動(dòng)釋放池->應(yīng)用處理事件(就是一些我們自己寫(xiě)的代碼奏纪,并有可能產(chǎn)生一些中間鉴嗤、臨時(shí)對(duì)象,這些對(duì)象放在自動(dòng)釋放池中)->事件處理完畢自動(dòng)釋放池釋放
官方文檔:The Application Kit creates an autorelease pool on the main thread at the beginning of every cycle of the event loop, and drains it at the end, thereby releasing any autoreleased objects generated while processing an event. 在主線(xiàn)程事件循環(huán)開(kāi)始的時(shí)候創(chuàng)建了自動(dòng)釋放池,在事件循環(huán)結(jié)束的時(shí)候釋放自動(dòng)釋放池序调,因此在處理事件過(guò)程中產(chǎn)生的一些自動(dòng)釋放的對(duì)象會(huì)被釋放掉

現(xiàn)在回頭看上面的一段代碼:把viewDidLoad看作是一個(gè)事件醉锅,在viewDidLoad開(kāi)始的時(shí)候創(chuàng)建了自動(dòng)釋放池,結(jié)束時(shí)自動(dòng)釋放池釋放发绢,并且把person對(duì)象釋放掉硬耍。然后進(jìn)行點(diǎn)擊事件時(shí),又創(chuàng)建了一個(gè)新的自動(dòng)釋放池边酒,因?yàn)閜erson對(duì)象已經(jīng)被釋放掉了经柴,此時(shí)再去訪(fǎng)問(wèn)就會(huì)報(bào)野指針錯(cuò)誤。

實(shí)際上,蘋(píng)果在主線(xiàn)程 RunLoop 里注冊(cè)了兩個(gè) Observer墩朦。第一個(gè) Observer 監(jiān)視的事件是即將進(jìn)入Loop坯认,其優(yōu)先級(jí)最高,保證創(chuàng)建釋放池發(fā)生在其他所有回調(diào)之前氓涣。
第二個(gè) Observer 監(jiān)視了兩個(gè)事件:準(zhǔn)備進(jìn)入休眠時(shí)** 釋放舊的池并創(chuàng)建新池**牛哺;即將退出Loop時(shí)釋放自動(dòng)釋放池。優(yōu)先級(jí)最低春哨,保證其釋放池子發(fā)生在其他所有回調(diào)之后荆隘。

代碼中,viewDidLoad的代碼執(zhí)行完runloop就要進(jìn)入休眠了赴背,這時(shí)候先釋放舊池并把person對(duì)象釋放椰拒。點(diǎn)擊事件把runloop喚醒晶渠,之后再新池里訪(fǎng)問(wèn)釋放掉的對(duì)象就報(bào)野指針錯(cuò)誤了。

在主線(xiàn)程執(zhí)行的代碼燃观,通常是寫(xiě)在諸如事件回調(diào)褒脯、Timer回調(diào)內(nèi)的。這些回調(diào)會(huì)被 RunLoop 創(chuàng)建好的 AutoreleasePool 環(huán)繞著缆毁,所以不會(huì)出現(xiàn)內(nèi)存泄漏番川,開(kāi)發(fā)者也不必顯示創(chuàng)建 Pool 了。

什么時(shí)候使用自動(dòng)釋放池脊框?

  • 開(kāi)啟子線(xiàn)程的時(shí)候要自己創(chuàng)建自動(dòng)釋放池颁督,否則可能會(huì)發(fā)生內(nèi)存泄露。
    使用 NSThread 做多線(xiàn)程開(kāi)發(fā)時(shí),需要在線(xiàn)程調(diào)度方法中手動(dòng)添加自動(dòng)釋放池浇雹。比如:
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(execute) object:nil];
[thread start];
......
 - (void)execute{
    @autoreleasepool{
        NSTimer *timer ...
        [[NSRunLoop currentRunLoop]addTimer:timer forMode:NSDefaultRunLoopMode];
        [[NSRunLoop currentRunLoop]run];
    }
}
  • 循環(huán)中創(chuàng)建了許多臨時(shí)對(duì)象沉御,在循環(huán)里面使用自動(dòng)釋放池,用來(lái)減少高內(nèi)存占用昭灵。舉例:
for (int i = 0; i < largeNumber; ++i) {
   NSString *str = @"Hello World";
   str = [str stringByAppendingFormat:@" - %d", i];
   str = [str uppercaseString];
   NSLog(@"%@", str);
}

運(yùn)行這段代碼吠裆,將會(huì)看到內(nèi)存占用情況一直在增長(zhǎng),因?yàn)檠h(huán)中的臨時(shí)對(duì)象沒(méi)有被釋放掉烂完。
改進(jìn):

 for (int i = 0; i < largeNumber; ++i) {
    @autoreleasepool{
    ......要執(zhí)行的代碼
    }
 }

更多有關(guān) 蘋(píng)果用 RunLoop 實(shí)現(xiàn)的功能比如事件響應(yīng)试疙、手勢(shì)識(shí)別、界面更新抠蚣、定時(shí)器祝旷、performSelector、GCD等本人還沒(méi)有深入研究柱徙,在這里就不展開(kāi)了缓屠,如果以后用到再另外寫(xiě)筆記吧奇昙。

參考文章:
官方文檔
經(jīng)典runloop技術(shù)文:
深入理解RunLoop
runloop源碼分析(runloop幾個(gè)重要的函數(shù)源碼閱讀):
Runloop源碼分析
Mach相關(guān):
Mach消息發(fā)送機(jī)制
進(jìn)程間通信 (OSX/iOS)
其他:
runloop嵌套:NSRunLoop原理詳解——不再有盲點(diǎn)

Cocoa深入學(xué)習(xí):NSOperationQueue护侮、NSRunLoop和線(xiàn)程安全
源碼解析之RunLoop詳解
ios開(kāi)發(fā)--RunLoop 與GCD 、Autorelease Pool之間的關(guān)系

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末储耐,一起剝皮案震驚了整個(gè)濱河市羊初,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌什湘,老刑警劉巖长赞,帶你破解...
    沈念sama閱讀 219,270評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異闽撤,居然都是意外死亡得哆,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)哟旗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)贩据,“玉大人栋操,你說(shuō)我怎么就攤上這事”チ粒” “怎么了矾芙?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,630評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)近上。 經(jīng)常有香客問(wèn)我剔宪,道長(zhǎng),這世上最難降的妖魔是什么壹无? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,906評(píng)論 1 295
  • 正文 為了忘掉前任葱绒,我火速辦了婚禮,結(jié)果婚禮上斗锭,老公的妹妹穿的比我還像新娘哈街。我一直安慰自己,他們只是感情好拒迅,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,928評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布骚秦。 她就那樣靜靜地躺著,像睡著了一般璧微。 火紅的嫁衣襯著肌膚如雪作箍。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,718評(píng)論 1 305
  • 那天前硫,我揣著相機(jī)與錄音胞得,去河邊找鬼。 笑死屹电,一個(gè)胖子當(dāng)著我的面吹牛阶剑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播危号,決...
    沈念sama閱讀 40,442評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼牧愁,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,345評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤住拭,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后磨确,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,802評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡声邦,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,984評(píng)論 3 337
  • 正文 我和宋清朗相戀三年乏奥,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片亥曹。...
    茶點(diǎn)故事閱讀 40,117評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡邓了,死狀恐怖盏檐,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情驶悟,我是刑警寧澤胡野,帶...
    沈念sama閱讀 35,810評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站痕鳍,受9級(jí)特大地震影響硫豆,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜笼呆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,462評(píng)論 3 331
  • 文/蒙蒙 一熊响、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧诗赌,春花似錦汗茄、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,011評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至叼屠,卻和暖如春瞳腌,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背镜雨。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,139評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工嫂侍, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人荚坞。 一個(gè)月前我還...
    沈念sama閱讀 48,377評(píng)論 3 373
  • 正文 我出身青樓挑宠,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親颓影。 傳聞我的和親對(duì)象是個(gè)殘疾皇子各淀,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,060評(píng)論 2 355

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