iOS-OC底層29:Runloop

1.前沿

1.1概念

Runloop不僅僅是一個運(yùn)行循環(huán)(do-while循環(huán))您单,也是提供了一個入口函數(shù)的對象,消息機(jī)制處理模式砾脑。運(yùn)行循環(huán)從兩種不同類型的源接收事件兔乞。
輸入源提供異步事件,通常是來自另一個線程或來自不同應(yīng)用程序的消息凄敢。定時器源提供同步事件碌冶,發(fā)生在預(yù)定時間或重復(fù)間隔。
兩種類型的源都使用特定于應(yīng)用程序的處理程序例程來處理事件涝缝。除了處理輸入源之外扑庞,Runloop還會生成有關(guān)Runloop行為的通知。
已注冊的運(yùn)行循環(huán)觀察器可以接收這些通知并使用它們在線程上執(zhí)行其他處理拒逮。
runloop官方文檔

1.2runloop實(shí)質(zhì)做了什么事

runloop實(shí)質(zhì)是一個dowhile循環(huán)

void CFRunLoopRun(void) {    /* DOES CALLOUT */
    int32_t result;
    do {
        // 1.0e10 : 科學(xué)技術(shù) 1*10^10
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
        CHECK_FOR_FORK();
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}

runloop中的do while循環(huán)和我們自己寫的有什么區(qū)別呢罐氨?

  while (1) {
            NSLog(@"hello");
        }

我們自己寫的dowhile循環(huán),cpu被大量占用滩援,但是系統(tǒng)中runloop的dowhile并沒有大量占有cpu栅隐,有此可以推斷系統(tǒng)的runloop做了優(yōu)化。

1.3.RunLoop的作用

1.保持程序的持續(xù)運(yùn)行
dowhile讓程序蓖婊玻活租悄。
2.處理APP中的各種事件(觸摸、定時器恩袱、performSelector)
當(dāng)我們點(diǎn)擊屏幕時泣棋,GraphicsServices中GSEventRunModal會想runloop發(fā)送消息,然后執(zhí)行CFRunLoopRunSpecific畔塔,然后讓UIKit處理事件
3.節(jié)省cpu資源潭辈、提供程序的性能:該做事就做事,該休息就休息
當(dāng)我們的應(yīng)用沒有事情處理時澈吨,占有的cpu幾乎為0把敢,當(dāng)我們外部或者系統(tǒng)有事情處理時被喚醒。

1.4.runloop 的item

1.block應(yīng)用:
CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK
這個item就是讓系統(tǒng)調(diào)用blcok

image.png

2.調(diào)timer:
CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION
當(dāng)runloop被這個item喚醒時棚辽,調(diào)用timer回調(diào)技竟。
image.png

3.響應(yīng)source0:
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION
當(dāng)我們UI有事件需要處理時,比如我們實(shí)現(xiàn)touchesBegan方法屈藐,看堆棧信息
image.png

  1. 響應(yīng)source1: CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION
    這個系統(tǒng)的榔组,筆者因?yàn)槟芰τ邢蓿詻]有找到出發(fā)的方法
  2. GCD主隊列:
    CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE
    系統(tǒng)啟動這個item联逻,然后調(diào)用GCD主隊列
    image.png
  3. observer源: CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION
    當(dāng)runloop被這個item喚醒時搓扯,就是該調(diào)用observer了
    image.png

2.runloop底層實(shí)現(xiàn)

源碼

2.1. runloop的結(jié)構(gòu)

typedef struct __CFRunLoop * CFRunLoopRef;
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;
    uint32_t _winthread;
    CFMutableSetRef _commonModes;
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode;
    CFMutableSetRef _modes;
    struct _block_item *_blocks_head;
    struct _block_item *_blocks_tail;
    CFTypeRef _counterpart;
};

runloop結(jié)構(gòu)體中保存了線程,_commonModes _currentMode包归,_commonModeItems, _block_item等等信息锨推。

2.2. runloop和線程關(guān)系

當(dāng)我們調(diào)用CFRunLoopGetCurrent和CFRunLoopGetMain時到底發(fā)生了什么?我們從源碼分析CFRunLoopGetCurrent和CFRunLoopGetMain最終掉的都是_CFRunLoopGet0

CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
//如果線程是空的,默認(rèn)是豬線程
    if (pthread_equal(t, kNilPthreadT)) {
        t = pthread_main_thread_np();
    }
    __CFSpinLock(&loopsLock);
//判斷靜態(tài)字典是否為空(為空說明時第一次使用)
    if (!__CFRunLoops) {
//是空的創(chuàng)建换可,并創(chuàng)建主線程的runloop并保存到__CFRunLoops
        __CFSpinUnlock(&loopsLock);
        CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
        CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
                CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop); 
        if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
            CFRelease(dict);
        } 
        CFRelease(mainLoop);
        __CFSpinLock(&loopsLock);
    } 
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFSpinUnlock(&loopsLock);
    if (!loop) {
        CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        __CFSpinLock(&loopsLock);
        loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        if (!loop) {
            CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
            loop = newLoop;
        }
        // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
        __CFSpinUnlock(&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;
}

1.首先判斷傳入的線程是否為空椎椰,如果是空,設(shè)置成主線程
2.判斷__CFRunLoops是否為空沾鳄,__CFRunLoops是一個Dictionary 以線程為key慨飘,runloop為value進(jìn)行保存,如果為空進(jìn)行創(chuàng)建译荞,并保存main線程和主runloop瓤的,3
3.試著從__CFRunLoops以線程為key進(jìn)行取值,如果取到就在下面返回吞歼,如果取不到則進(jìn)行創(chuàng)建圈膏。并在下面返回。

2.3. runloop機(jī)制

image.png

image.png

CFRunLoopRun----》CFRunLoopRunSpecific

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
    __CFRunLoopLock(rl);
    
    /// 首先根據(jù)modeName找到對應(yīng)mode
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
    
    /// 通知 Observers: RunLoop 即將進(jìn)入 loop。
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    
    /// 內(nèi)部函數(shù),進(jìn)入loop
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    
    /// 通知 Observers: RunLoop 即將退出潮太。
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
    
    return result;
}

/// 核心函數(shù)
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    
    int32_t retVal = 0;
    
    do {  // itmes do
        
        /// 通知 Observers: 即將處理timer事件
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        
        /// 通知 Observers: 即將處理Source事件
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources)
        
        /// 處理Blocks
        __CFRunLoopDoBlocks(rl, rlm);
        
        /// 處理sources0
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        
        /// 處理sources0返回為YES
        if (sourceHandledThisLoop) {
            /// 處理Blocks
            __CFRunLoopDoBlocks(rl, rlm);
        }
        
        
        /// 判斷有無端口消息(Source1)
        if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
            /// 處理消息
            goto handle_msg;
        }
        
        /// 通知 Observers: 即將進(jìn)入休眠
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        __CFRunLoopSetSleeping(rl);
        
        /// 等待被喚醒
        __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
        
        
        // user callouts now OK again
        __CFRunLoopUnsetSleeping(rl);
        
        /// 通知 Observers: 被喚醒,結(jié)束休眠
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
        
    handle_msg:
        if (被Timer喚醒) {
            /// 處理Timers
            __CFRunLoopDoTimers(rl, rlm, mach_absolute_time())慎皱;
        } else if (被GCD喚醒) {
            /// 處理gcd
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
        } else if (被Source1喚醒) {
            /// 被Source1喚醒,處理Source1
            __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply)
        }
        
        /// 處理block
        __CFRunLoopDoBlocks(rl, rlm);
        
        
        if (sourceHandledThisLoop && stopAfterHandle) {
            retVal = kCFRunLoopRunHandledSource;
        } else if (timeout_context->termTSR < mach_absolute_time()) {
            retVal = kCFRunLoopRunTimedOut;
        } else if (__CFRunLoopIsStopped(rl)) {
            __CFRunLoopUnsetStopped(rl);
            retVal = kCFRunLoopRunStopped;
        } else if (rlm->_stopped) {
            rlm->_stopped = false;
            retVal = kCFRunLoopRunStopped;
        } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
            retVal = kCFRunLoopRunFinished;
        }
        
    } while (0 == retVal);
    
    return retVal;
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末叶骨,一起剝皮案震驚了整個濱河市茫多,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌忽刽,老刑警劉巖天揖,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異跪帝,居然都是意外死亡今膊,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進(jìn)店門伞剑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來斑唬,“玉大人,你說我怎么就攤上這事黎泣∷×酰” “怎么了?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵抒倚,是天一觀的道長褐着。 經(jīng)常有香客問我,道長托呕,這世上最難降的妖魔是什么含蓉? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任频敛,我火速辦了婚禮,結(jié)果婚禮上馅扣,老公的妹妹穿的比我還像新娘斟赚。我一直安慰自己,他們只是感情好岂嗓,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布汁展。 她就那樣靜靜地躺著鹊碍,像睡著了一般厌殉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上侈咕,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天公罕,我揣著相機(jī)與錄音,去河邊找鬼耀销。 笑死楼眷,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的熊尉。 我是一名探鬼主播罐柳,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼狰住!你這毒婦竟也來了张吉?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤催植,失蹤者是張志新(化名)和其女友劉穎肮蛹,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體创南,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡伦忠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了稿辙。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片昆码。...
    茶點(diǎn)故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖邻储,靈堂內(nèi)的尸體忽然破棺而出赋咽,到底是詐尸還是另有隱情,我是刑警寧澤芥备,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布冬耿,位于F島的核電站,受9級特大地震影響萌壳,放射性物質(zhì)發(fā)生泄漏亦镶。R本人自食惡果不足惜日月,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望缤骨。 院中可真熱鬧爱咬,春花似錦、人聲如沸绊起。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽虱歪。三九已至蜂绎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間笋鄙,已是汗流浹背师枣。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留萧落,地道東北人践美。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像找岖,于是被迫代替她去往敵國和親陨倡。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,077評論 2 355

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