RunLoop

RunLoop

顧名思義

  • 運行循環(huán)
  • 在程序運行過程中循環(huán)做一些事情
  • 一般來講,一個線程一次只能執(zhí)行一個任務奄薇,執(zhí)行完成后線程就會退出。如果我們需要一個機制,讓線程能隨時處理事件但并不退出舟茶,通常的代碼邏輯是這樣的:
function loop() {
    initialize();
    do {
        var message = get_next_message();
        process_message(message);
    } while (message != quit);
}

這種模型通常被稱作Event Loop
實現(xiàn)這種模型的關鍵點在于:如何管理事件/消息堵第,如何讓線程在沒有處理消息時休眠以避免資源占用吧凉、在有消息到來時立即被喚醒。

RunLoop實際上就是一個對象踏志,這個對象管理了其需要處理的事件和消息阀捅,并提供了一個入口函數(shù)來執(zhí)行上面的Event Loop的邏輯。線程執(zhí)行了這個函數(shù)后针余,就會一直處于正常函數(shù)內(nèi)部“接受消息->等待->處理”的循環(huán)中饲鄙,直到這個循環(huán)結(jié)束(比如傳入quit的消息),函數(shù)返回圆雁。


應用范疇

  • 定時器(Timer)傍妒、performSelector
  • GCD Async Main Queue
  • 事件響應、手勢識別摸柄、界面刷新
  • 網(wǎng)絡請求
  • AutoreleasePool

RunLoop的基本作用

  • 保持程序的持續(xù)運行
  • 處理App中的各種事件(比如觸摸事件颤练、定時器事件等)
  • 節(jié)省CPU資源,提高程序性能:該做事時做事驱负,該休息時休息

RunLoop對象

  • iOS中有2套API來訪問和使用RunLoop
    • Foundation:NSRunLoop
    • Core Foundation:CFRunLoopRef
  • NSRunLoop和CFRunLoopRef都代表著RunLoop對象
    • NSRunLoop是基于CFRunLoopRef的一層OC包裝(所以runloop的地址會不一樣
      • 提供了面向?qū)ο蟮腁PI嗦玖,但是這些API不是線程安全的
    • CFRunLoopRef是開源的
      • 提供了純C函數(shù)的API,所有這些API都是線程安全的
    // OC獲取當前的RunLoop
    NSRunLoop *currentRunloop = [NSRunLoop currentRunLoop];
    // OC獲取主線程RunLoop
    NSRunLoop *mainRunLoop = [NSRunLoop mainRunLoop];
    // C語言獲取當前的RunLoop
    CFRunLoopRef currentRunloop2 = CFRunLoopGetCurrent();
    // C語言獲取主線程的RunLoop
    CFRunLoopRef mainRunloop2 = CFRunLoopGetMain();

RunLoop與線程

  • 每條線程都有唯一的一個與之對應的RunLoop對象
  • RunLoop保存在一個全局的Dictionary里跃脊,線程作為key宇挫,RunLoop作為value
  • 線程剛創(chuàng)建時并沒有RunLoop對象,RunLoop會在第一次獲取它時創(chuàng)建
  • RunLoop會在線程結(jié)束時銷毀
  • 主線程的RunLoop已經(jīng)自動獲壤沂酢(創(chuàng)建)器瘪,子線程默認沒有開啟RunLoop
  • 只能在一個線程的內(nèi)部獲取其RunLoop(主線程除外)翠储。

RunLoop相關的類

  • Core Foundation中關于RunLoop的5個類
    • CFRunLoopRef
    • CFRunLoopModeRef
    • CFRunLoopSourceRef 事件產(chǎn)生的地方
    • CFRunLoopTimerRef
    • CFRunLoopObserverRef

CFRunLoopRef

typedef struct __CFRunLoop * CFRunLoopRef;
struct __CFRunLoop {
    pthread_t _pthread; // RunLoop所對應的線程
    CFMutableSetRef _commonModes;
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode; // _modes這個集合里面只有一個模式被稱為當前模式_currentMode
    CFMutableSetRef _modes; // 這個集合里面裝了很多CFRunLoopModeRef類型的對象
};  

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影響
  • 另外還有一種模式:kCFRunLoopCommonModes 默認包括kCFRunLoopDefaultMode、UITrackingRunLoopMode兩種模式
typedef struct __CFRunLoopMode * CFRunLoopModeRef;
struct __CFRunLoopMode {
    CFStringRef _name; // mode的名稱
    CFMutableSetRef _sources0;
    CFMutableSetRef _sources1; // source里面裝了很多CFRunLoopSourceRef類型的對象
    CFMutableArrayRef _observers;// 里面裝了很多CFRunLoopObserverRef類型的對象
    CFMutableArrayRef _timers; // 里面裝了很多CFRunLoopTimerRef類型的對象
};

CFRunLoopObserverRef

/* RunLoop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),// 即將進入Loop
    kCFRunLoopBeforeTimers = (1UL << 1),// 即將處理Timer
    kCFRunLoopBeforeSources = (1UL << 2), // 即將處理Source
    kCFRunLoopBeforeWaiting = (1UL << 5), // 即將進入休眠
    kCFRunLoopAfterWaiting = (1UL << 6), // 剛從休眠中喚醒
    kCFRunLoopExit = (1UL << 7), // 即將退出Loop
    kCFRunLoopAllActivities = 0X0FFFFFFFU
};
// 監(jiān)聽事件的狀態(tài)變化(只有c語言接口)
// 方法一
void observeRunLoopActivities(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){
    switch (activity) {
        case kCFRunLoopEntry:
            NSLog(@"kCFRunLoopEntry");
            break;
        case kCFRunLoopBeforeTimers:
            NSLog(@"kCFRunLoopBeforeTimers");
            break;
        case kCFRunLoopBeforeSources:
            NSLog(@"kCFRunLoopBeforeSources");
            break;
        case kCFRunLoopBeforeWaiting:
            NSLog(@"kCFRunLoopBeforeWaiting");
            break;
        case kCFRunLoopAfterWaiting:
            NSLog(@"kCFRunLoopAfterWaiting");
            break;
        case kCFRunLoopExit:
            NSLog(@"kCFRunLoopExit");
            break;
            
        default:
            break;
    }
}

    // 創(chuàng)建observer
    CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, observeRunLoopActivities, NULL);
    // 添加observer到RunLoop中
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
    // 釋放observer
    CFRelease(observer);
    
// 方法二
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        switch (activity) {
            case kCFRunLoopEntry: {
                CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
                NSLog(@"kCFRunLoopEntry - %@",mode);
                CFRelease(mode);
                break;
            }
            case kCFRunLoopExit:{
                CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
                NSLog(@"kCFRunLoopExit- %@",mode);
                CFRelease(mode);
                break;
            }
                
            default:
                break;
        }
    });
    // 添加observer到RunLoop中
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
    // 釋放observer
    CFRelease(observer);

根據(jù)源碼分析挽牢,得到如下關系圖

runloop

RunLoop的運行邏輯

  • Source0
    • 觸摸事件處理
    • performSelector: onThread:(提供一個線程谱煤,就可以讓對應方法去該線程去執(zhí)行)
  • Source1
    • 基于Port的線程間通信
    • 系統(tǒng)事件捕捉
  • Timers
    • NSTimer
    • performSelector: withObject: afterDelay:
  • Observers
    • 用于監(jiān)聽RunLoop的狀態(tài)
    • UI刷新(BeforeWaiting)
    • AutoReleasePool(BeforeWaiting)

如下圖所示


runloop流程

RunLoop休眠的實現(xiàn)原理

休眠的實現(xiàn)原理

實質(zhì):從用戶態(tài)切換到內(nèi)核態(tài),去等待消息禽拔,沒有消息就讓線程休眠趴俘,不在占用cpu,有消息就喚醒線程奏赘。

RunLoop在實際開發(fā)中的應用

  • 控制線程生命周期(線程绷壬粒活,比如AFNetWorking都是在子線程中等待的磨淌,所以是一直逼1铮活)

  • 解決NSTimer在滑動時停止工作的問題

static int count = 0;
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
        NSLog(@"%d",++count);
    }];
// NSDefaultRunLoopMode/UITrackingRunLoopMode才是真正存在的模式    
// NSRunLoopCommonModes并不是一個真的模式,它只是一個標記
// timer能在_commonModes數(shù)組中存放的模式下工作
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
//    [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
//        NSLog(@"%d",++count);
//    }];
  • 監(jiān)控應用卡頓
  • 性能優(yōu)化
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末梁只,一起剝皮案震驚了整個濱河市缚柳,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌搪锣,老刑警劉巖秋忙,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異构舟,居然都是意外死亡灰追,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進店門狗超,熙熙樓的掌柜王于貴愁眉苦臉地迎上來弹澎,“玉大人,你說我怎么就攤上這事努咐】噍铮” “怎么了?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵渗稍,是天一觀的道長佩迟。 經(jīng)常有香客問我团滥,道長,這世上最難降的妖魔是什么报强? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任灸姊,我火速辦了婚禮,結(jié)果婚禮上躺涝,老公的妹妹穿的比我還像新娘。我一直安慰自己扼雏,他們只是感情好坚嗜,可當我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著诗充,像睡著了一般苍蔬。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蝴蜓,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天碟绑,我揣著相機與錄音,去河邊找鬼茎匠。 笑死格仲,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的诵冒。 我是一名探鬼主播凯肋,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼汽馋!你這毒婦竟也來了侮东?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤豹芯,失蹤者是張志新(化名)和其女友劉穎悄雅,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體铁蹈,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡宽闲,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了握牧。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片便锨。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖我碟,靈堂內(nèi)的尸體忽然破棺而出放案,到底是詐尸還是另有隱情,我是刑警寧澤矫俺,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布吱殉,位于F島的核電站掸冤,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏友雳。R本人自食惡果不足惜稿湿,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望押赊。 院中可真熱鬧饺藤,春花似錦、人聲如沸流礁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽神帅。三九已至再姑,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間找御,已是汗流浹背元镀。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留霎桅,地道東北人栖疑。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像滔驶,于是被迫代替她去往敵國和親蔽挠。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 42,828評論 2 345

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