# iOS開(kāi)發(fā)之RunLoop

iOS開(kāi)發(fā)之RunLoop

什么是RunLoop

  • 運(yùn)行循環(huán),跑圈
  • 其實(shí)內(nèi)部就是do-while循環(huán),在這個(gè)循環(huán)n內(nèi)部不斷的處理各種任務(wù)(比如Source/Timer/Observer)
  • 一個(gè)線程對(duì)應(yīng)一個(gè)RunLoop,主線程的RunLoop默認(rèn)啟動(dòng)了,子線程需要手動(dòng)調(diào)用(run方法)
  • RunLoop只能選擇一個(gè)Mode啟動(dòng),如果當(dāng)前Mode中沒(méi)有任何Source/Timer/Observer,那就直接退出RunLoop

RunLoop的作用

  • 讓程序一直運(yùn)行并接受用戶輸入
  • 決定程序在何時(shí)應(yīng)該處理哪些Event
  • 調(diào)用結(jié)構(gòu)(Message Queue)
  • 節(jié)省CPU時(shí)間

RunLoop對(duì)象

  • Foundation : NSRunLoop(OC對(duì)C的RunLoop的簡(jiǎn)單的封裝)
[NSRunLoop currentRunLoop]; //獲取當(dāng)前線程的Runloop
[NSRunLoop mainRunLoop]; //獲取主線程的RunLoop
  • Core Foundation CFRunLoop(C語(yǔ)言 開(kāi)源 跨平臺(tái)的)
CFRunLoopGetCurrent();//獲取當(dāng)前線程的Runloop
CFRunLoopGetMain(); //獲取主線程的RunLoop

RunLoop的機(jī)制

  • 每條線程都有唯一一個(gè)與之對(duì)應(yīng)的Runloop對(duì)象
  • 主線程的RunLoop已經(jīng)創(chuàng)建好了,子線程的Runloop需要自動(dòng)創(chuàng)建
  • RunLoop在第一次獲取時(shí)創(chuàng)建,在線程結(jié)束時(shí)銷毀

CFRunLoopModelRef

代表的是Runloop的運(yùn)行模式

  • 一個(gè)Runloop包含若干個(gè)Mode,每個(gè)Model又包含了多個(gè)Source/Timer/Observer
  • Runloop在同一段時(shí)間只能且必須在一種特定的Mode下Run
  • 更換Mode時(shí),需要停止當(dāng)前的Loop,然后重啟新的Loop
  • Mode是iOS App滑動(dòng)順利的關(guān)鍵
  • 可以自己定制Mode(基本不會(huì)發(fā)生的)

NSDefaultRunLoopMode 默認(rèn)狀態(tài),空閑狀態(tài),通常主線程是在這個(gè)Mode下運(yùn)行
UITrackingRunLoopMode 界面跟蹤 Mode, 滑動(dòng)ScrollView時(shí)
UIInitializationRunLoopMode 私有,App啟動(dòng)時(shí)進(jìn)入的第一個(gè)Mode 啟動(dòng)完后不在使用
NSRunLoopCommonModes ? Mode集合 默認(rèn)包括上面第一和第二
GSEventReceiveRunLoopMode 接受系統(tǒng)內(nèi)部時(shí)間的Mode

CFRunLoopSource

  • Source是Runloop的數(shù)據(jù)源的抽象類(類似IOS中的protocol)
  • 定義了2個(gè)版本的Source
    1. Source0 :處理app的內(nèi)部時(shí)間,App自己負(fù)責(zé)管理 如:UIEvent CFSocket
    2. Source1 :用Runloop和內(nèi)核管理,Mach port驅(qū)動(dòng),如 CFMachPort CFMessagePort (Port可以用于進(jìn)程間的端口通訊)

CFRunLoopTimerRef

  • CFRunLoopTimerRef是基于時(shí)間的觸發(fā)器
  • 基本上說(shuō)的就是NSTimer

CFRunLoopObserver

向外部報(bào)告Runloop當(dāng)前狀態(tài)的更改,能夠監(jiān)聽(tīng)Runloop的狀態(tài)的改變

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

    // 創(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);
    });

    // 添加觀察者:監(jiān)聽(tīng)RunLoop的狀態(tài)
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
    
    // 釋放Observer
    CFRelease(observer);
    

注意這里面的observer是需要釋放的: 在ARC中自動(dòng)內(nèi)存管理的是OC的對(duì)象

CF的內(nèi)存管理(Core Foundation)

  • 凡是帶有Create、Copy、Retain等字眼的函數(shù),創(chuàng)建出來(lái)的對(duì)象,都需要在最后做一次release 比如CFRunLoopObserverCreate.
  • release函數(shù):CFRelease(對(duì)象);

RunLoop的處理邏輯

RunLoopObserver與Autorelease Pool

在RunLoop睡覺(jué)之前釋放(kCFRunLoopBeforeWaiting)

UITrackingRunLoopMode 與 Timer

Timer默認(rèn)是被添加在NSDefaultRunLoopMode中的,當(dāng)ScrollerView滑動(dòng)的時(shí)候就會(huì)影響到
Timer,若不希望Timer被影響,需要添加到NSRunLoopCommonModes

    
    [[NSRunLoop currentRunLoop] addTimer:[NSTimer timerWithTimeInterval:1 target:self selector:@selector(timeaction) userInfo:nil repeats:NO] forMode:NSRunLoopCommonModes];
    

Runloop與dispath_get_main_queue()

GCD到dispath到main queque的block被分發(fā)到main Runloop執(zhí)行

GCD中的定時(shí)器和Runloop沒(méi)有關(guān)系的,GCD的定時(shí)器是不受RunLoop的Mode的影響的

    // 獲得隊(duì)列
//    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    // 創(chuàng)建一個(gè)定時(shí)器(dispatch_source_t本質(zhì)還是個(gè)OC對(duì)象)
    self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    
    // 設(shè)置定時(shí)器的各種屬性(什么時(shí)候開(kāi)始任務(wù),每隔多長(zhǎng)時(shí)間執(zhí)行一次)
    // GCD的時(shí)間參數(shù),一般是納秒(1秒 == 10的9次方納秒)
    // 何時(shí)開(kāi)始執(zhí)行第一個(gè)任務(wù)
    // dispatch_time(DISPATCH_TIME_NOW, 1.0 * NSEC_PER_SEC) 比當(dāng)前時(shí)間晚3秒
    dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC));
    uint64_t interval = (uint64_t)(1.0 * NSEC_PER_SEC);
    dispatch_source_set_timer(self.timer, start, interval, 0);
    
    // 設(shè)置回調(diào)
    dispatch_source_set_event_handler(self.timer, ^{
        NSLog(@"------------%@", [NSThread currentThread]);
        count++;
        
//        if (count == 4) {
//            // 取消定時(shí)器
//            dispatch_cancel(self.timer);
//            self.timer = nil;
//        }
    });
    
    // 啟動(dòng)定時(shí)器
    dispatch_resume(self.timer);

RunLoop的掛起與喚醒

  • 指定用于喚醒的mach_port端口
  • 調(diào)用mach_msg監(jiān)聽(tīng)喚醒端口,被喚醒前,系統(tǒng)內(nèi)核將這個(gè)線程掛起,停留在mach_msg_trap狀態(tài)
  • 由另一個(gè)線程(或者另一個(gè)進(jìn)程中的線程) 向內(nèi)核發(fā)送這個(gè)端口的msg后,trap狀態(tài)被喚醒.
     //在子線程中默認(rèn)是沒(méi)有RunLoop的 
    //獲取Runloop,當(dāng)當(dāng)前線程沒(méi)有runloop的時(shí)候,該方法就會(huì)啟動(dòng)runloop
    NSRunLoop *loop = [NSRunLoop currentRunLoop];
    
    //給Runloop一個(gè)端口,這樣就可以保持Runloop處于喚醒的狀態(tài)
    [loop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];

    //run
    [loop run];

RunLoop創(chuàng)建一個(gè)常駐服務(wù)線程的方法

 [[NSThread currentThread] setName:@"thread1"];
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefalutRunLoopMode]//一直活著
[runLoop run];

Topic: 一個(gè)TableView延遲加載圖片的思路

    UIImageView * iconImageView = [UIImageView new];

    UIImage *image = nil;
    
    [iconImageView performSelector:@selector(setImage:) withObject:image afterDelay:0 inModes:@[NSDefaultRunLoopMode]];
    
    //NSDefaultRunLoopMode 設(shè)置為這個(gè)模式的時(shí)候 當(dāng)TableView在滑動(dòng)到時(shí)候Runloop在UITrackingRunLoopMode模式,這樣setimage方法就不會(huì)被調(diào)用.只有不滑動(dòng)的時(shí)候,runloop切換到NSDefaultRunLoopMode模式,這時(shí)候設(shè)置圖片
    

應(yīng)用場(chǎng)景

  • 開(kāi)啟一個(gè)常駐線程(讓一個(gè)子線程不進(jìn)入消亡狀態(tài),等待其他線程發(fā)送消息,處理其他時(shí)間)
    • 在子線程中開(kāi)啟一個(gè)定時(shí)器
    • 在子線程中長(zhǎng)期監(jiān)控行為
  • 可以控制定時(shí)器在哪種模式下運(yùn)行
  • 可以讓某些任務(wù)在特定模式下執(zhí)行
  • 可以添加Observer監(jiān)聽(tīng)Runloop的狀態(tài),比如監(jiān)聽(tīng)點(diǎn)擊時(shí)間前做一些事情
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • runLoop,正如其名,表示一直運(yùn)行著的循環(huán)。 一般來(lái)說(shuō),一個(gè)線程只能執(zhí)行一個(gè)任務(wù),執(zhí)行完就會(huì)推出,如果我們需要...
    li大鵬閱讀 1,955評(píng)論 3 11
  • 前言 最近離職了,可以盡情熬夜寫(xiě)點(diǎn)總結(jié),不用擔(dān)心第二天上班爽并蛋疼著,這篇的主角 RunLoop 一座大山,涵蓋的...
    zerocc2014閱讀 12,434評(píng)論 13 67
  • RunLoop 是 iOS 開(kāi)發(fā)中一個(gè)非常基礎(chǔ)而又重要的一個(gè)概念 為什么說(shuō)它非常重要呢? 它不僅是檢驗(yàn)一個(gè)程序員水...
    小小小阿博er閱讀 834評(píng)論 2 19
  • Runloop是iOS和OSX開(kāi)發(fā)中非常基礎(chǔ)的一個(gè)概念,從概念開(kāi)始學(xué)習(xí)。 RunLoop的概念 -般說(shuō),一個(gè)線程一...
    小貓仔閱讀 1,019評(píng)論 0 1
  • Run loop 剖析:Runloop 接收的輸入事件來(lái)自兩種不同的源:輸入源(intput source)和定時(shí)...
    Mitchell閱讀 12,480評(píng)論 17 111