詳解runloop

iOS 里很重要的一個(gè)概念就是runloop泛领,到底什么是runloop呢?先從概念說起敛惊,如果大家接觸過node渊鞋,就會(huì)感到很熟悉,事件驅(qū)動(dòng),或者叫事件循環(huán)锡宋。

一儡湾、RunLoop概念

RunLoop是通過內(nèi)部維護(hù)的事件循環(huán)(Event Loop)來對(duì)事件/消息進(jìn)行管理的一個(gè)對(duì)象。

1执俩、沒有消息處理時(shí)徐钠,休眠已避免資源占用,由用戶態(tài)切換到內(nèi)核態(tài)(CPU-內(nèi)核態(tài)和用戶態(tài))
2役首、有消息需要處理時(shí)尝丐,立刻被喚醒,由內(nèi)核態(tài)切換到用戶態(tài)

為什么main函數(shù)不會(huì)退出衡奥?

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

UIApplicationMain內(nèi)部默認(rèn)開啟了主線程的RunLoop摊崭,并執(zhí)行了一段無限循環(huán)的代碼(不是簡(jiǎn)單的for循環(huán)或while循環(huán))
UIApplicationMain函數(shù)一直沒有返回,而是不斷地接收處理消息以及等待休眠杰赛,所以運(yùn)行程序之后會(huì)保持持續(xù)運(yùn)行狀態(tài)呢簸。

二、RunLoop的數(shù)據(jù)結(jié)構(gòu)

NSRunLoop(Foundation)CFRunLoop(CoreFoundation)的封裝乏屯,提供了面向?qū)ο蟮腁PI
RunLoop 相關(guān)的主要涉及五個(gè)類:

CFRunLoop:RunLoop對(duì)象
CFRunLoopMode:運(yùn)行模式
CFRunLoopSource:輸入源/事件源
CFRunLoopTimer:定時(shí)源
CFRunLoopObserver:觀察者

1根时、CFRunLoop

pthread(線程對(duì)象,說明RunLoop和線程是一一對(duì)應(yīng)的)辰晕、currentMode(當(dāng)前所處的運(yùn)行模式)蛤迎、modes(多個(gè)運(yùn)行模式的集合)、commonModes(模式名稱字符串集合)含友、commonModelItems(Observer,Timer,Source集合)構(gòu)成

2替裆、CFRunLoopMode

由name、source0窘问、source1辆童、observers、timers構(gòu)成

3惠赫、CFRunLoopSource

分為source0和source1兩種

  • source0:
    即非基于port的把鉴,也就是用戶觸發(fā)的事件。需要手動(dòng)喚醒線程儿咱,將當(dāng)前線程從內(nèi)核態(tài)切換到用戶態(tài)
  • source1:
    基于port的庭砍,包含一個(gè) mach_port 和一個(gè)回調(diào),可監(jiān)聽系統(tǒng)端口和通過內(nèi)核和其他線程發(fā)送的消息混埠,能主動(dòng)喚醒RunLoop怠缸,接收分發(fā)系統(tǒng)事件。
    具備喚醒線程的能力
4钳宪、CFRunLoopTimer

基于時(shí)間的觸發(fā)器揭北,基本上說的就是NSTimer概耻。在預(yù)設(shè)的時(shí)間點(diǎn)喚醒RunLoop執(zhí)行回調(diào)。因?yàn)樗腔赗unLoop的罐呼,因此它不是實(shí)時(shí)的(就是NSTimer 是不準(zhǔn)確的。 因?yàn)镽unLoop只負(fù)責(zé)分發(fā)源的消息侦高。如果線程當(dāng)前正在處理繁重的任務(wù)嫉柴,就有可能導(dǎo)致Timer本次延時(shí),或者少執(zhí)行一次)奉呛。

5计螺、CFRunLoopObserver

監(jiān)聽以下時(shí)間點(diǎn):CFRunLoopActivity

kCFRunLoopEntry
RunLoop準(zhǔn)備啟動(dòng)
kCFRunLoopBeforeTimers
RunLoop將要處理一些Timer相關(guān)事件
kCFRunLoopBeforeSources
RunLoop將要處理一些Source事件
kCFRunLoopBeforeWaiting
RunLoop將要進(jìn)行休眠狀態(tài),即將由用戶態(tài)切換到內(nèi)核態(tài)
kCFRunLoopAfterWaiting
RunLoop被喚醒,即從內(nèi)核態(tài)切換到用戶態(tài)后
kCFRunLoopExit
RunLoop退出
kCFRunLoopAllActivities
監(jiān)聽所有狀態(tài)

6瞧壮、各數(shù)據(jù)結(jié)構(gòu)之間的聯(lián)系

線程和RunLoop一一對(duì)應(yīng)登馒, RunLoop和Mode是一對(duì)多的,Mode和source咆槽、timer陈轿、observer也是一對(duì)多的

三、RunLoop的Mode

關(guān)于Mode首先要知道一個(gè)RunLoop 對(duì)象中可能包含多個(gè)Mode秦忿,且每次調(diào)用 RunLoop 的主函數(shù)時(shí)麦射,只能指定其中一個(gè) Mode(CurrentMode)。切換 Mode灯谣,需要重新指定一個(gè) Mode 潜秋。主要是為了分隔開不同的 Source、Timer胎许、Observer峻呛,讓它們之間互不影響。

當(dāng)RunLoop運(yùn)行在一個(gè)Mode上時(shí)辜窑,是無法接受處理其他Mode上的Source钩述、Timer、Observer事件的穆碎。

總共是有五種CFRunLoopMode:

kCFRunLoopDefaultMode:默認(rèn)模式切距,主線程是在這個(gè)運(yùn)行模式下運(yùn)行

UITrackingRunLoopMode:跟蹤用戶交互事件(用于 ScrollView 追蹤觸摸滑動(dòng),保證界面滑動(dòng)時(shí)不受其他Mode影響)

UIInitializationRunLoopMode:在剛啟動(dòng)App時(shí)第進(jìn)入的第一個(gè) Mode惨远,啟動(dòng)完成后就不再使用

GSEventReceiveRunLoopMode:接受系統(tǒng)內(nèi)部事件谜悟,通常用不到

kCFRunLoopCommonModes:偽模式,不是一種真正的運(yùn)行模式北秽,是同步Source/Timer/Observer到多個(gè)Mode中的一種解決方案葡幸。

四、RunLoop的實(shí)現(xiàn)機(jī)制

對(duì)于RunLoop而言最核心的事情就是保證線程在沒有消息的時(shí)候休眠贺氓,在有消息時(shí)喚醒蔚叨,以提高程序性能。RunLoop這個(gè)機(jī)制是依靠系統(tǒng)內(nèi)核來完成的(蘋果操作系統(tǒng)核心組件Darwin中的Mach)。

RunLoop通過mach_msg()函數(shù)接收蔑水、發(fā)送消息邢锯。它的本質(zhì)是調(diào)用函數(shù)mach_msg_trap(),相當(dāng)于是一個(gè)系統(tǒng)調(diào)用搀别,會(huì)觸發(fā)內(nèi)核狀態(tài)切換丹擎。在用戶態(tài)調(diào)用 mach_msg_trap()時(shí)會(huì)切換到內(nèi)核態(tài);內(nèi)核態(tài)中內(nèi)核實(shí)現(xiàn)的mach_msg()函數(shù)會(huì)完成實(shí)際的工作歇父。
即基于port的source1蒂培,監(jiān)聽端口,端口有消息就會(huì)觸發(fā)回調(diào)榜苫;而source0护戳,要手動(dòng)標(biāo)記為待處理和手動(dòng)喚醒RunLoop(是執(zhí)行 source0 時(shí)需要手動(dòng)調(diào)用 CFRunLoopWakeUp 來喚醒 run loop,實(shí)際覺得好像大部分場(chǎng)景下其它事件都會(huì)導(dǎo)致 run loop 正常進(jìn)行著循環(huán)垂睬,只要 run loop 進(jìn)行循環(huán)則標(biāo)記為待處理的 source0 就能得到執(zhí)行媳荒,好像并不需要我們刻意的手動(dòng)調(diào)用 CFRunLoopWakeUp 來喚醒當(dāng)前的 run loop。)

Mach消息發(fā)送機(jī)制
大致邏輯為:
1驹饺、通知觀察者 RunLoop 即將啟動(dòng)肺樟。
2、通知觀察者即將要處理Timer事件逻淌。
3么伯、通知觀察者即將要處理source0事件。
4卡儒、處理source0事件田柔。
5、如果基于端口的源(Source1)準(zhǔn)備好并處于等待狀態(tài)骨望,進(jìn)入步驟9硬爆。
6、通知觀察者線程即將進(jìn)入休眠狀態(tài)擎鸠。
7缀磕、將線程置于休眠狀態(tài),由用戶態(tài)切換到內(nèi)核態(tài)劣光,直到下面的任一事件發(fā)生才喚醒線程袜蚕。

  • 一個(gè)基于 port 的Source1 的事件(圖里應(yīng)該是source0)。
  • 一個(gè) Timer 到時(shí)間了绢涡。
  • RunLoop 自身的超時(shí)時(shí)間到了牲剃。
  • 被其他調(diào)用者手動(dòng)喚醒。

8雄可、通知觀察者線程將被喚醒凿傅。
9缠犀、處理喚醒時(shí)收到的事件。

  • 如果用戶定義的定時(shí)器啟動(dòng)聪舒,處理定時(shí)器事件并重啟RunLoop辨液。進(jìn)入步驟2。
  • 如果輸入源啟動(dòng)箱残,傳遞相應(yīng)的消息滔迈。
  • 如果RunLoop被顯示喚醒而且時(shí)間還沒超時(shí),重啟RunLoop疚宇。進(jìn)入步驟2

10、通知觀察者RunLoop結(jié)束赏殃。

六敷待、一些runloop常見的問題和應(yīng)用

1、RunLoop與NSTimer

一個(gè)比較常見的問題:滑動(dòng)tableView時(shí)仁热,定時(shí)器還會(huì)生效嗎榜揖?
默認(rèn)情況下RunLoop運(yùn)行在kCFRunLoopDefaultMode下,而當(dāng)滑動(dòng)tableView時(shí)抗蠢,RunLoop切換到UITrackingRunLoopMode举哟,而Timer是在kCFRunLoopDefaultMode下的,就無法接受處理Timer的事件迅矛。
怎么去解決這個(gè)問題呢妨猩?把Timer添加到UITrackingRunLoopMode上并不能解決問題,因?yàn)檫@樣在默認(rèn)情況下就無法接受定時(shí)器事件了秽褒。
所以我們需要把Timer同時(shí)添加到UITrackingRunLoopModekCFRunLoopDefaultMode上壶硅。
那么如何把timer同時(shí)添加到多個(gè)mode上呢?就要用到NSRunLoopCommonModes

[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

Timer就被添加到多個(gè)mode上销斟,這樣即使RunLoop由kCFRunLoopDefaultMode切換到UITrackingRunLoopMode下庐椒,也不會(huì)影響接收Timer事件

2、RunLoop和線程
  • 線程和RunLoop是一一對(duì)應(yīng)的,其映射關(guān)系是保存在一個(gè)全局的 Dictionary 里
  • 自己創(chuàng)建的線程默認(rèn)是沒有開啟RunLoop的
怎么創(chuàng)建一個(gè)常駐線程蚂踊?

1约谈、為當(dāng)前線程開啟一個(gè)RunLoop(第一次調(diào)用 [NSRunLoop currentRunLoop]方法時(shí)實(shí)際是會(huì)先去創(chuàng)建一個(gè)RunLoop)
2、向當(dāng)前RunLoop中添加一個(gè)Port/Source等維持RunLoop的事件循環(huán)(如果RunLoop的mode中一個(gè)item都沒有犁钟,RunLoop會(huì)退出)
3棱诱、啟動(dòng)該RunLoop

   @autoreleasepool {
        
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        
        [[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
        
        [runLoop run];
        
    }
輸出下邊代碼的執(zhí)行順序
 NSLog(@"1");

dispatch_async(dispatch_get_global_queue(0, 0), ^{
    
    NSLog(@"2");

    [self performSelector:@selector(test) withObject:nil afterDelay:10];
    
    NSLog(@"3");
});

NSLog(@"4");

- (void)test
{
    
    NSLog(@"5");
}

答案是1423,test方法并不會(huì)執(zhí)行涝动。
原因是如果是帶afterDelay的延時(shí)函數(shù)军俊,會(huì)在內(nèi)部創(chuàng)建一個(gè) NSTimer,然后添加到當(dāng)前線程的RunLoop中捧存。也就是如果當(dāng)前線程沒有開啟RunLoop粪躬,該方法會(huì)失效担败。
那么我們改成:

dispatch_async(dispatch_get_global_queue(0, 0), ^{
        
        NSLog(@"2");
        
        [[NSRunLoop currentRunLoop] run];
        
        [self performSelector:@selector(test) withObject:nil afterDelay:10];
  
        NSLog(@"3");
    });

然而test方法依然不執(zhí)行。
原因是如果RunLoop的mode中一個(gè)item都沒有镰官,RunLoop會(huì)退出提前。即在調(diào)用RunLoop的run方法后,由于其mode中沒有添加任何item去維持RunLoop的時(shí)間循環(huán)泳唠,RunLoop隨即還是會(huì)退出狈网。
所以我們自己?jiǎn)?dòng)RunLoop,一定要在添加item后

dispatch_async(dispatch_get_global_queue(0, 0), ^{
        
        NSLog(@"2");
        
        [self performSelector:@selector(test) withObject:nil afterDelay:10];
        
        [[NSRunLoop currentRunLoop] run];
  
        NSLog(@"3");
    });
3笨腥、怎樣保證子線程數(shù)據(jù)回來更新UI的時(shí)候不打斷用戶的滑動(dòng)操作拓哺?

當(dāng)我們?cè)谧诱?qǐng)求數(shù)據(jù)的同時(shí)滑動(dòng)瀏覽當(dāng)前頁面,如果數(shù)據(jù)請(qǐng)求成功要切回主線程更新UI脖母,那么就會(huì)影響當(dāng)前正在滑動(dòng)的體驗(yàn)士鸥。
我們就可以將更新UI事件放在主線程的NSDefaultRunLoopMode上執(zhí)行即可,這樣就會(huì)等用戶不再滑動(dòng)頁面谆级,主線程RunLoop由UITrackingRunLoopMode切換到NSDefaultRunLoopMode時(shí)再去更新UI

[self performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:NO modes:@[NSDefaultRunLoopMode]];
4烤礁、runloop 在監(jiān)控卡頓中的應(yīng)用

主線程卡頓監(jiān)控:這是業(yè)內(nèi)常用的一種檢測(cè)卡頓的方法,通過開辟一個(gè)子線程來監(jiān)控主線程的 RunLoop肥照,當(dāng)兩個(gè)狀態(tài)區(qū)域之間的耗時(shí)大于閾值時(shí)脚仔,就記為發(fā)生一次卡頓。美團(tuán)的移動(dòng)端性能監(jiān)控方案 Hertz 采用的就是這種方式


image.png

主線程卡頓監(jiān)控的實(shí)現(xiàn)思路:開辟一個(gè)子線程舆绎,然后實(shí)時(shí)計(jì)算 kCFRunLoopBeforeSourceskCFRunLoopAfterWaiting 兩個(gè)狀態(tài)區(qū)域之間的耗時(shí)是否超過某個(gè)閥值鲤脏,來斷定主線程的卡頓情況,可以將這個(gè)過程想象成操場(chǎng)上跑圈的運(yùn)動(dòng)員吕朵,我們會(huì)每隔一段時(shí)間間隔去判斷是否跑了一圈凑兰,如果發(fā)現(xiàn)在指定時(shí)間間隔沒有跑完一圈,則認(rèn)為在消息處理的過程中耗時(shí)太多边锁,視為主線程卡頓姑食,這時(shí)我們要保存應(yīng)用的上下文,即卡頓發(fā)生時(shí)程序的堆棧調(diào)用和運(yùn)行日志上傳茅坛。

static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
    MyClass *object = (__bridge MyClass*)info;
    
    // 記錄狀態(tài)值
    object->activity = activity;
    
    // 發(fā)送信號(hào)
    dispatch_semaphore_t semaphore = moniotr->semaphore;
    dispatch_semaphore_signal(semaphore);
}

- (void)registerObserver
{
    CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL};
    CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
                                                            kCFRunLoopAllActivities,
                                                            YES,
                                                            0,
                                                            &runLoopObserverCallBack,
                                                            &context);
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
    
    // 創(chuàng)建信號(hào)
    semaphore = dispatch_semaphore_create(0);
    
    // 在子線程監(jiān)控時(shí)長(zhǎng)
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        while (YES)
        {
            // 假定連續(xù)5次超時(shí)50ms認(rèn)為卡頓(當(dāng)然也包含了單次超時(shí)250ms)
            long st = dispatch_semaphore_wait(semaphore, dispatch_time(DISPATCH_TIME_NOW, 50*NSEC_PER_MSEC));
            if (st != 0)
            {
                if (activity==kCFRunLoopBeforeSources || activity==kCFRunLoopAfterWaiting)
                {
                    if (++timeoutCount < 5)
                        continue;
                    // 檢測(cè)到卡頓音半,進(jìn)行卡頓上報(bào)
                }
            }
            timeoutCount = 0;
        }
    });
}   

代碼中使用 timeoutCount 變量來覆蓋多次連續(xù)的小卡頓,當(dāng)累計(jì)次數(shù)超過5次贡蓖,也會(huì)進(jìn)入到卡頓邏輯曹鸠。當(dāng)檢測(cè)到了卡頓,下一步需要做的就是記錄卡頓的現(xiàn)場(chǎng)斥铺,即此時(shí)程序的堆棧調(diào)用彻桃,可以借助開源庫 PLCrashReporter 來實(shí)現(xiàn)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市晾蜘,隨后出現(xiàn)的幾起案子邻眷,更是在濱河造成了極大的恐慌眠屎,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,482評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件肆饶,死亡現(xiàn)場(chǎng)離奇詭異改衩,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)驯镊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門葫督,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人板惑,你說我怎么就攤上這事橄镜。” “怎么了冯乘?”我有些...
    開封第一講書人閱讀 152,762評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵洽胶,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我往湿,道長(zhǎng)妖异,這世上最難降的妖魔是什么惋戏? 我笑而不...
    開封第一講書人閱讀 55,273評(píng)論 1 279
  • 正文 為了忘掉前任领追,我火速辦了婚禮,結(jié)果婚禮上响逢,老公的妹妹穿的比我還像新娘绒窑。我一直安慰自己,他們只是感情好舔亭,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評(píng)論 5 373
  • 文/花漫 我一把揭開白布些膨。 她就那樣靜靜地躺著,像睡著了一般钦铺。 火紅的嫁衣襯著肌膚如雪订雾。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,046評(píng)論 1 285
  • 那天矛洞,我揣著相機(jī)與錄音洼哎,去河邊找鬼。 笑死沼本,一個(gè)胖子當(dāng)著我的面吹牛噩峦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播抽兆,決...
    沈念sama閱讀 38,351評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼识补,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了辫红?” 一聲冷哼從身側(cè)響起凭涂,我...
    開封第一講書人閱讀 36,988評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤祝辣,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后导盅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體较幌,經(jīng)...
    沈念sama閱讀 43,476評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評(píng)論 2 324
  • 正文 我和宋清朗相戀三年白翻,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了乍炉。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,064評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡滤馍,死狀恐怖岛琼,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情巢株,我是刑警寧澤槐瑞,帶...
    沈念sama閱讀 33,712評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站阁苞,受9級(jí)特大地震影響困檩,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜那槽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評(píng)論 3 307
  • 文/蒙蒙 一悼沿、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧骚灸,春花似錦糟趾、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至丈钙,卻和暖如春非驮,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背雏赦。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工劫笙, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人喉誊。 一個(gè)月前我還...
    沈念sama閱讀 45,511評(píng)論 2 354
  • 正文 我出身青樓邀摆,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親伍茄。 傳聞我的和親對(duì)象是個(gè)殘疾皇子栋盹,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評(píng)論 2 345

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

  • RunLoop 是 iOS 和 OSX 開發(fā)中非常基礎(chǔ)的一個(gè)概念敷矫,這篇文章將從 CFRunLoop 的源碼入手例获,介...
    缽_Right閱讀 814評(píng)論 0 1
  • Runloop 是和線程緊密相關(guān)的一個(gè)基礎(chǔ)組件汉额,是很多線程有關(guān)功能的幕后功臣。盡管在平常使用中幾乎不太會(huì)直接用到榨汤,...
    jackyshan閱讀 9,843評(píng)論 10 75
  • 概述RunLoop作為iOS中一個(gè)基礎(chǔ)組件和線程有著千絲萬縷的關(guān)系蠕搜,同時(shí)也是很多常見技術(shù)的幕后功臣。盡管在平時(shí)多數(shù)...
    飛天豬Pony閱讀 514評(píng)論 0 7
  • 轉(zhuǎn)載自ibireme-深入理解RunLoop看了很多遍,每次都有不同的收獲.想要了解Runloop的朋友可以來看看...
    Mr_Baymax閱讀 905評(píng)論 0 5
  • 開胃面試題 1.講講 RunLoop收壕,項(xiàng)目中有用到嗎妓灌?2.RunLoop內(nèi)部實(shí)現(xiàn)邏輯?2.Runloop和線程的關(guān)...
    非洲小白猿閱讀 2,300評(píng)論 0 11