OC-Runloop

RunLoop 是什么

runloop 就是一個運行循環(huán)获三,目的是讓程序運行起來不會直接結(jié)束层坠,能在有任務的時候處理任務退盯,沒有任務的時候等待處理任務奄喂。

iOS 中有兩套API 可以訪問 runloop

  • Foundation : NSRunLoop
  • Core Foundation : CFRunLoopRef

參考

RunLoop 與線程的關(guān)系

  • 每條線程都有唯一的一個與之對應的RunLoop對象
  • RunLoop保存在一個全局的Dictionary里婆赠,線程作為key绵脯,RunLoop作為value
  • 線程剛創(chuàng)建時并沒有RunLoop對象,RunLoop會在第一次獲取它時創(chuàng)建
  • RunLoop會在線程結(jié)束時銷毀
  • 主線程的RunLoop已經(jīng)自動獲纫吃濉(創(chuàng)建)桨嫁,子線程默認沒有開啟RunLoop

獲取 RunLoop 對象

Foundation
[NSRunLoop currentRunLoop]; // 獲得當前線程的RunLoop對象
[NSRunLoop mainRunLoop]; // 獲得主線程的RunLoop對象

Core Foundation
CFRunLoopGetCurrent(); // 獲得當前線程的RunLoop對象
CFRunLoopGetMain(); // 獲得主線程的RunLoop對象

可以從 CFRunLoopGetCurrent() 源碼中看到相關(guān)獲取邏輯,代碼簡化如下


CFRunLoopRef CFRunLoopGetCurrent(void) {
    // 返回根據(jù)當前線程取到的runloop
    return _CFRunLoopGet0(pthread_self());
}

// 核心邏輯如下,已簡化代碼
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    
    // 1.判斷如果沒有runloop字典
    if (!__CFRunLoops) {
        // 2.創(chuàng)建一個字典
        CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
        // 3.根據(jù)主線程創(chuàng)建runloop
        CFRunLoopRef mainLoop = __CFRunLoopCreate(_CFMainPThread);
        // 4.在dict中將主線程指針和當前創(chuàng)建的runloop以 key:value 形式保存
        CFDictionarySetValue(dict, pthreadPointer(_CFMainPThread), mainLoop);
        // 5.dict 賦值給 __CFRunLoops(代碼經(jīng)簡化份帐,源碼并非如此簡單)
        __CFRunLoops = dict;
    }
    
    // 6. 從 __CFRunLoops 中根據(jù)參數(shù)t指針獲取runloop
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    if (!loop) {
        // 7. 如果沒有就創(chuàng)建一個新loop對象
        CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        // 8. __CFRunLoops 中添加參數(shù)t的指針和新loop對象保存
        CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
        // 9. 新runloop 對象就是要返回的loop對象
        loop = newLoop;
    }
    return loop;
}

runloop 內(nèi)部數(shù)據(jù)結(jié)構(gòu)

runloop 內(nèi)部核心數(shù)據(jù)

struct __CFRunLoop {
    pthread_t _pthread;                 // 對應的線程
    CFMutableSetRef _commonModes;       // 通用Mode
    CFMutableSetRef _commonModeItems;   // 通用mode的items
    CFRunLoopModeRef _currentMode;      // 當前mode
    CFMutableSetRef _modes;             // 所有的mode
};

從數(shù)據(jù)結(jié)構(gòu)中可以看到璃吧,實際就是 runloop 中包含自己的 thread 和 mode, CFRunLoopModeRef 類型結(jié)構(gòu)如下

struct __CFRunLoopMode {
    CFStringRef _name;
    CFMutableSetRef _sources0;
    CFMutableSetRef _sources1;
    CFMutableArrayRef _observers;
    CFMutableArrayRef _timers;
};

可以看到 runloop 的內(nèi)部結(jié)構(gòu)基本如圖

runloop結(jié)構(gòu)

runloop 的運行模式 CFRunLoopModeRef

  • CFRunLoopModeRef代表RunLoop的運行模式
  • 一個RunLoop包含若干個Mode废境,每個Mode又包含若干個Source0/Source1/Timer/Observer
  • RunLoop啟動時只能選擇其中一個Mode畜挨,作為currentMode
  • 如果需要切換Mode,只能退出當前Loop噩凹,再重新選擇一個Mode進入
    • 不同組的Source0/Source1/Timer/Observer能分隔開來巴元,互不影響
  • 如果Mode里沒有任何Source0/Source1/Timer/Observer,RunLoop會立馬退出
  • 常見的2種Mode
    • kCFRunLoopDefaultMode(NSDefaultRunLoopMode):App的默認Mode驮宴,通常主線程是在這個Mode下運行
    • UITrackingRunLoopMode:界面跟蹤 Mode逮刨,用于 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其他 Mode 影響

Runloop 的運行邏輯

說白了 runloop 就是在整循環(huán)中一直檢查當前運行的 mode 內(nèi)部有沒有需要處理的事件堵泽,有就處理修己,沒有就休眠

runloop運行邏輯

Runloop 的 mode 內(nèi)部主要有下面4類事件要處理

  • Source0

    • 觸摸事件處理
    • performSelector:onThread:
  • Source1

    • 基于Port的線程間通信
    • 系統(tǒng)事件捕捉
  • Timers

    • NSTimer
    • performSelector:withObject:afterDelay:
  • Observers

    • 用于監(jiān)聽RunLoop的狀態(tài)
    • UI刷新(BeforeWaiting)
    • Autorelease pool(BeforeWaiting)

一次循環(huán)處理事件的邏輯如下

一次runloop的循環(huán)邏輯

一個簡單的線程保活

@interface PermenantThread()
@property (strong, nonatomic) NSThread *innerThread;
@end

@implementation PermenantThread
#pragma mark - public methods
- (instancetype)init
{
    if (self = [super init]) {
        self.innerThread = [[NSThread alloc] initWithBlock:^{
            
            // 創(chuàng)建上下文(要初始化一下結(jié)構(gòu)體)
            CFRunLoopSourceContext context = {0};
            // 創(chuàng)建source
            CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
            // 往Runloop中添加source
            CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
            // 銷毀source
            CFRelease(source);
            
            // 啟動
            CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, false);
            
//            while (weakSelf && !weakSelf.isStopped) {
//                // 第3個參數(shù):returnAfterSourceHandled迎罗,設置為true睬愤,代表執(zhí)行完source后就會退出當前l(fā)oop
//                CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, true);
//            }
            
            NSLog(@"end----");
        }];
        
        [self.innerThread start];
    }
    return self;
}

- (void)executeTask:(PermenantThreadTask)task
{
    if (!self.innerThread || !task) return;
    
    [self performSelector:@selector(__executeTask:) onThread:self.innerThread withObject:task waitUntilDone:NO];
}

- (void)stop
{
    if (!self.innerThread) return;
    
    [self performSelector:@selector(__stop) onThread:self.innerThread withObject:nil waitUntilDone:YES];
}

- (void)dealloc
{
    NSLog(@"%s", __func__);
    
    [self stop];
}

#pragma mark - private methods
- (void)__stop
{
    CFRunLoopStop(CFRunLoopGetCurrent());
    self.innerThread = nil;
}

- (void)__executeTask:(PermenantThreadTask)task
{
    task();
}

@end

面試題

  • 講講 Runloop,項目中有用到嗎纹安?
1. 用到過
2. 線程庇热瑁活砂豌,對于頻繁需要在子線程做的操作,使用 runloo 進行線程惫舛剑活 -- 適用于一個串行線程阳距,非并發(fā)的情況。如需要一個后臺線程持續(xù)做否些計算结借,網(wǎng)絡請求娄涩。
3. 如 AFNetworking 2.0版本的時候就是后臺有一個常駐線程

1. 實例
    1. 控制線程聲明周期(線程保活)
    2. 解決NSTimer在滑動時停止工作的問題
    3. 監(jiān)控應用卡頓
    4. 性能優(yōu)化
  • runloop 內(nèi)部實現(xiàn)邏輯映跟?
1. runloop 的創(chuàng)建邏輯,從內(nèi)部的 dict 中獲取,沒有就新建扬虚,并保存努隙,目的是一條線程只對應一個runloop
2. runloop 的運行邏輯,本質(zhì)上就是一個 while() 循環(huán)辜昵。每次進入循環(huán)的時候處理如上圖事件荸镊。沒有事件的時候進入休眠(通過調(diào)用內(nèi)核接口實現(xiàn))。
  • runloop 和線程的關(guān)系堪置?
是一一對應的關(guān)系躬存。
其實保存在 __CFRunLoops 全局 dict 中,以線程指針為key,線程對應的runloop地址為value
  • timer 和 線程的關(guān)系舀锨?
  • 程序中添加每3秒響應一次的NSTimer岭洲,當拖動 tableView 時候 timer 可能無法響應怎么解決?
將 timer 添加到 commonMode中坎匿,這樣就可以處理普通模式和tracking 模式下的事件了
  • runloop 是怎么影響用戶操作的盾剩,具體流程是什么樣的?
由 source1 捕捉用戶的觸摸事件替蔬, 由 source0 處理用戶的觸摸事件告私。 V158T13
  • 說說 runloop 的幾種狀態(tài)?
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),
    kCFRunLoopBeforeTimers = (1UL << 1),
    kCFRunLoopBeforeSources = (1UL << 2),
    kCFRunLoopBeforeWaiting = (1UL << 5),
    kCFRunLoopAfterWaiting = (1UL << 6),
    kCFRunLoopExit = (1UL << 7),
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};
  • runloop 的 mode 作用是什么承桥?
不同 mode 將各自的 source驻粟、timer、observer 隔離開凶异,這樣同一時間只能執(zhí)行一個 mode 下的 source蜀撑、timer、observer唠帝,這樣在某種模式下就會比較流暢屯掖。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市襟衰,隨后出現(xiàn)的幾起案子贴铜,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,290評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件绍坝,死亡現(xiàn)場離奇詭異徘意,居然都是意外死亡,警方通過查閱死者的電腦和手機轩褐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評論 2 385
  • 文/潘曉璐 我一進店門椎咧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人把介,你說我怎么就攤上這事勤讽。” “怎么了拗踢?”我有些...
    開封第一講書人閱讀 156,872評論 0 347
  • 文/不壞的土叔 我叫張陵脚牍,是天一觀的道長。 經(jīng)常有香客問我巢墅,道長诸狭,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,415評論 1 283
  • 正文 為了忘掉前任君纫,我火速辦了婚禮驯遇,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蓄髓。我一直安慰自己叉庐,他們只是感情好,可當我...
    茶點故事閱讀 65,453評論 6 385
  • 文/花漫 我一把揭開白布会喝。 她就那樣靜靜地躺著眨唬,像睡著了一般。 火紅的嫁衣襯著肌膚如雪好乐。 梳的紋絲不亂的頭發(fā)上匾竿,一...
    開封第一講書人閱讀 49,784評論 1 290
  • 那天,我揣著相機與錄音蔚万,去河邊找鬼岭妖。 笑死,一個胖子當著我的面吹牛反璃,可吹牛的內(nèi)容都是我干的昵慌。 我是一名探鬼主播,決...
    沈念sama閱讀 38,927評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼淮蜈,長吁一口氣:“原來是場噩夢啊……” “哼斋攀!你這毒婦竟也來了癣疟?” 一聲冷哼從身側(cè)響起邢滑,我...
    開封第一講書人閱讀 37,691評論 0 266
  • 序言:老撾萬榮一對情侶失蹤蔚袍,失蹤者是張志新(化名)和其女友劉穎撤嫩,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體鹉梨,經(jīng)...
    沈念sama閱讀 44,137評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡讳癌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,472評論 2 326
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了存皂。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片晌坤。...
    茶點故事閱讀 38,622評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖旦袋,靈堂內(nèi)的尸體忽然破棺而出骤菠,到底是詐尸還是另有隱情,我是刑警寧澤疤孕,帶...
    沈念sama閱讀 34,289評論 4 329
  • 正文 年R本政府宣布娩怎,位于F島的核電站,受9級特大地震影響胰柑,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜爬泥,卻給世界環(huán)境...
    茶點故事閱讀 39,887評論 3 312
  • 文/蒙蒙 一柬讨、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧袍啡,春花似錦踩官、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至嗅剖,卻和暖如春辩越,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背信粮。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工黔攒, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人强缘。 一個月前我還...
    沈念sama閱讀 46,316評論 2 360
  • 正文 我出身青樓督惰,卻偏偏與公主長得像,于是被迫代替她去往敵國和親旅掂。 傳聞我的和親對象是個殘疾皇子赏胚,可洞房花燭夜當晚...
    茶點故事閱讀 43,490評論 2 348