iOS Runloop 補(bǔ)充

主要講解Runloop的底層結(jié)構(gòu);

文中使用的 CF源碼是CF-1153.18版本;

Runloop 簡(jiǎn)析
Runloop 補(bǔ)充


1. Runloop 的應(yīng)用范疇

  • 定時(shí)器(timer)和 PerformSelector;
  • GCD 的 Asyn Main Queue;
  • 事件相應(yīng), 手勢(shì)識(shí)別, 界面刷新 ;
  • 網(wǎng)絡(luò)請(qǐng)求;
  • AutoreleasePool

2. Runloop 的底層結(jié)構(gòu)

我的都知道RunloopCFRunloopRef和基于它封裝的CFRunloop,所以我們只需要探究下CFRunloopRef的結(jié)構(gòu)即可得知它的底層結(jié)構(gòu);

///源碼中CFRunLoopRef的定義如下
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef;
===>
///__CFRunLoop的定義如下, 有很多成員, 不過(guò)我們只需要確定它的主要組成是各個(gè) Mod 即可;
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
     ///每一個(gè) runloop 中都有一個(gè)線程, 確保線程和 runloop 是一一對(duì)應(yīng)的;
    pthread_t _pthread;
    uint32_t _winthread;
    CFMutableSetRef _commonModes;
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode;
     ///Mod的集合
    CFMutableSetRef _modes;
    struct _block_item *_blocks_head;
    struct _block_item *_blocks_tail;
    CFAbsoluteTime _runTime;
    CFAbsoluteTime _sleepTime;
    CFTypeRef _counterpart;
};
===>
///Mod的底層結(jié)構(gòu)如下
struct __CFRunLoopMode {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;  /* must have the run loop locked before locking this */
    CFStringRef _name;
    Boolean _stopped;
    char _padding[3];
    ///集合sources0
    CFMutableSetRef _sources0;
    ///集合souces1
    CFMutableSetRef _sources1;
    ///數(shù)組observers
    CFMutableArrayRef _observers;
    ///數(shù)組timers
    CFMutableArrayRef _timers;
    CFMutableDictionaryRef _portToV1SourceMap;
...
}

關(guān)于Runloop中常用的幾個(gè)類如下:
CFRunLoopRef, CFRunLoopModeRef, CFRunLoopSourceRef, CFRunLoopObserverRef, CFRunLoopTimerRef;
CFRunLoopModeRef中的幾個(gè)主要變量的含義:

  • source0 : 處理觸摸事件,或者 performSelector:OnThread:方法;
  • source1 : 基于port的線程通訊(addPort: forMode:), 系統(tǒng)事件捕捉(例如點(diǎn)擊屏幕后首先是source1捕捉然后再封裝轉(zhuǎn)發(fā)給source0處理);
  • Timer: 就是NSTimer, 調(diào)用performSelector: withObject: afterDelay:也是類似操作;
  • Observer :監(jiān)聽Runloop的狀態(tài); 主線程UI刷新(例如設(shè)置一個(gè)顏色, 代碼執(zhí)行完后并不會(huì)立即改變, 而是在Runloop狀態(tài)BeforeWaiting之前刷新的);另外AutoreleasePool也是一樣具體什么時(shí)候releaseRunloop的監(jiān)聽者決定;

如果Mode中沒有任何的Source0, Source, Timer, ObserverRunloop會(huì)立即退出;

3. Runloop的幾種狀態(tài):

我們都知道Runloop是一個(gè)事件循環(huán), 整個(gè)循環(huán)有多少種狀態(tài)呢, 源碼種定義如下:

/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    ///即將進(jìn)入 Runloop
    kCFRunLoopEntry = (1UL << 0),
    ///即將處理 Timer
    kCFRunLoopBeforeTimers = (1UL << 1),
    ///即將處理 Source
    kCFRunLoopBeforeSources = (1UL << 2),
    ///即將進(jìn)入休眠
    kCFRunLoopBeforeWaiting = (1UL << 5),
    ///即將喚醒 Runloop
    kCFRunLoopAfterWaiting = (1UL << 6),
    ///即將退出 Runloop
    kCFRunLoopExit = (1UL << 7),
    ///所有狀態(tài)集合
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};
  • kCFRunLoopEntry : 即將進(jìn)入Runloop;
  • kCFRunLoopBeforeTimers : 即將處理Timer;
  • kCFRunLoopBeforeSources: 即將處理Sources;
  • kCFRunLoopBeforeWaiting : 即將進(jìn)入休眠(事情已經(jīng)處理完畢);
  • kCFRunLoopAfterWaiting : 從休眠中喚醒;
  • kCFRunLoopExit: 即將退出Runloop;
  • kCFRunLoopAllActivities: 所有狀態(tài)集合;
    關(guān)于這些狀態(tài)的測(cè)試, 具體詳見補(bǔ)充1;

4. Runloop在日常開發(fā)種的使用場(chǎng)景

具體測(cè)試代碼相見下方補(bǔ)充階段;


補(bǔ)充

補(bǔ)充1:Runloop的各個(gè)狀態(tài)測(cè)試

測(cè)試代碼如下:

    ///創(chuàng)建一個(gè)Observer
    CFRunLoopObserverRef CFObserver = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        switch (activity) {
            case kCFRunLoopEntry: {
                NSLog(@"Runloop即將進(jìn)入Runloop: kCFRunLoopEntry");
                break;
            }
               case kCFRunLoopBeforeTimers: {
                   NSLog(@"Runloop即將處理Timer: kCFRunLoopBeforeTimers");
                   break;
               }
                case kCFRunLoopBeforeSources: {
                    NSLog(@"Runloop即將處理Source: kCFRunLoopBeforeSources");
                    break;
                }
                case kCFRunLoopBeforeWaiting: {
                    NSLog(@"Runloop即將進(jìn)入休眠: kCFRunLoopBeforeWaiting");
                    break;
                }
                case kCFRunLoopAfterWaiting: {
                    NSLog(@"Runloop從休眠中喚醒: kCFRunLoopAfterWaiting");
                    break;
                }
                case kCFRunLoopExit: {
                    NSLog(@"Runloop即將退出: kCFRunLoopExit");
                }
            default:
                break;
        }
      });
    ///為Runloop添加Observer
    CFRunLoopAddObserver(CFRunLoopGetMain(), CFObserver, kCFRunLoopCommonModes);
    ///CF框架下Create或者Copy的對(duì)象要進(jìn)行釋放
    CFRelease(CFObserver);

具體打印結(jié)果由于篇幅太大, 不再貼出, 請(qǐng)自行下載測(cè)試代碼查看打印結(jié)果;

補(bǔ)充2:控制線程的生命周期

通過(guò)啟動(dòng)子線程的Runloop并添加source1達(dá)到線程不會(huì) kill的效果; 詳解請(qǐng)看這篇文章

    /*
     case3:
     子線程中開啟runloop, 為其添加任意的souce0/souce1/timer/observer, 并讓其run, 則此線程由于runloop的關(guān)系就不會(huì)被銷毀;
     */
    self.thread3 = [[XThread alloc] initWithBlock:^{
            /*
         我們知道runloop中如果沒有任何source0/souce1/timer/observer 則runloop會(huì)立即退出;
         所以為runloop添加source1, 然后讓runloop執(zhí)行run;
         */
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc]init ] forMode:NSDefaultRunLoopMode];
        /*
         注意run的釋義:If no input sources or timers are attached to the run loop, this method exits immediately; otherwise, it runs the receiver in the NSDefaultRunLoopMode by repeatedly invoking runMode:beforeDate:. In other words, this method effectively begins an infinite loop that processes data from the run loop’s input sources and timers.
         大致翻譯:  如果沒有souces或者timers添加到runloop中則方法理解退出; 如果有,將在NSDefaultRunLoopMode模式下無(wú)限次執(zhí)行runMode:beforeDate:來(lái)處理添加的souces和timers;
         注意: 調(diào)用 runloop 的 run 方法則不再能取消 runloop 即使是調(diào)用了CFRunLoopStop(CFRunLoopGetCurrent()); 也只是停了其中一次循環(huán);

         */
        [[NSRunLoop currentRunLoop] run];
    }];
    [self.thread3 start];

另一種方式

 ///創(chuàng)建一個(gè)source --  往 runloop 中添加 source1 source0 timer  observer 均可打到效果
    ///初始化 context
    CFRunLoopSourceContext context = {0};
    ///當(dāng)一個(gè)runloop中沒有事件源處理時(shí), 運(yùn)行完就會(huì)退出;
    CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
    ///1. 2. 創(chuàng)建runloop 同時(shí)向runloop中的defaultMode下面添加source
    CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
    ///3. 啟動(dòng)runloop
    while (shouldRun) {
        @autoreleasepool {
            ///令當(dāng)前的runloop運(yùn)行在defaultMode下
            CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e20, true);
        }
    }
    ///某個(gè)時(shí)機(jī), 將靜態(tài)變量shouldRun = NO時(shí), 退出runloop, 進(jìn)而退出線程;
    CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
    CFRelease(source);
補(bǔ)充3: 解決NSTimer在界面拖動(dòng)時(shí)暫時(shí)失效的問(wèn)題

正常情況下, NSTimer都是添加在DefaultMode模式下; 一旦界面拖動(dòng)時(shí)Runloop就會(huì)切換模式到TrackingMode模式下, NSTimer就會(huì)暫時(shí)失效;
解決方案:將NStimer添加到RunloopCommonMode模式下;這樣Runloop就會(huì)同時(shí)處理DefaultModeTrackingMode中的事件;

    static NSInteger i = 1;
    NSTimer *timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
        NSLog(@"執(zhí)行次數(shù):  %ld", i ++);
    }];
    [timer fire];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
補(bǔ)充4:詳見性能優(yōu)化部分
補(bǔ)充5:相見性能優(yōu)化部分

文中測(cè)試代碼


參考文章和下載鏈接
Apple 一些源碼的下載地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末攀例,一起剝皮案震驚了整個(gè)濱河市罩润,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,978評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異庇茫,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)螃成,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,954評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門港令,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人锈颗,你說(shuō)我怎么就攤上這事∵浠荩” “怎么了击吱?”我有些...
    開封第一講書人閱讀 156,623評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)遥昧。 經(jīng)常有香客問(wèn)我覆醇,道長(zhǎng),這世上最難降的妖魔是什么炭臭? 我笑而不...
    開封第一講書人閱讀 56,324評(píng)論 1 282
  • 正文 為了忘掉前任永脓,我火速辦了婚禮,結(jié)果婚禮上鞋仍,老公的妹妹穿的比我還像新娘常摧。我一直安慰自己,他們只是感情好威创,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,390評(píng)論 5 384
  • 文/花漫 我一把揭開白布落午。 她就那樣靜靜地躺著,像睡著了一般肚豺。 火紅的嫁衣襯著肌膚如雪溃斋。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,741評(píng)論 1 289
  • 那天吸申,我揣著相機(jī)與錄音梗劫,去河邊找鬼。 笑死截碴,一個(gè)胖子當(dāng)著我的面吹牛梳侨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播日丹,決...
    沈念sama閱讀 38,892評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼猫妙,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了聚凹?” 一聲冷哼從身側(cè)響起割坠,我...
    開封第一講書人閱讀 37,655評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤齐帚,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后彼哼,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體对妄,經(jīng)...
    沈念sama閱讀 44,104評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年敢朱,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了剪菱。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,569評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡拴签,死狀恐怖孝常,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情蚓哩,我是刑警寧澤构灸,帶...
    沈念sama閱讀 34,254評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站岸梨,受9級(jí)特大地震影響喜颁,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜曹阔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,834評(píng)論 3 312
  • 文/蒙蒙 一半开、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧赃份,春花似錦寂拆、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,725評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至园蝠,卻和暖如春渺蒿,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背彪薛。 一陣腳步聲響...
    開封第一講書人閱讀 31,950評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工茂装, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人善延。 一個(gè)月前我還...
    沈念sama閱讀 46,260評(píng)論 2 360
  • 正文 我出身青樓少态,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親易遣。 傳聞我的和親對(duì)象是個(gè)殘疾皇子彼妻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,446評(píng)論 2 348