iOS -- RunLoop認(rèn)識(shí)以及常用的場(chǎng)景

一、runLoop是什么

字面意思:運(yùn)行循環(huán),程序運(yùn)行過程中循環(huán)的處理事件
它的實(shí)際:實(shí)際是一個(gè)對(duì)象,這個(gè)對(duì)象提供一個(gè)入口函數(shù)胡诗,執(zhí)行這個(gè)入口函數(shù)后,程序會(huì)進(jìn)入一個(gè)do..while循環(huán)淌友,循環(huán)的處理一些事情煌恢。

二、runLoop有什么用震庭?

2.1 如果沒有runLoop症虑?

int main(int argc, char * argv[]){
    
    @autoreleasepool {
        
        NSLog(@“%s”, __func__);
    }
    return 0;
}

結(jié)果:程序執(zhí)行完就會(huì)退出。
2.2 如果有runLoop?

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

結(jié)果:程序一直執(zhí)行沒有退出归薛。

三谍憔、runLoop在程序中的例子

3.1 觸摸

觸摸

3.2 定時(shí)器
定時(shí)器

3.3 PerformSelector
performSelector

3.4 GCD異步操作回到主線程中
異步操作回到主線程中

總結(jié)

runLoop基本作用:

1匪蝙、保持程序的持續(xù)運(yùn)行
2、處理App中的各種事件(觸摸习贫、定時(shí)器逛球、PerformSelector
3、節(jié)省CPU資源苫昌、提高程序性能:該做事的時(shí)候做事颤绕,該休息的時(shí)候休息。

四祟身、拓展關(guān)于PerformSelector

- (void)viewDidLoad {
    [super viewDidLoad];

    BOOL isNoFire = [self respondsToSelector:@selector(fire)];
    if (isNoFire) {
        
        [self performSelector:@selector(fire)];
    }
}

- (void)fire {
    NSLog(@"%s", __func__);
}

總結(jié):下面講講performSelector調(diào)用方法和直接調(diào)用方法的區(qū)別
4.1奥务、performSelector是運(yùn)行時(shí)系統(tǒng)負(fù)責(zé)去找方法的,在編譯時(shí)候不做任何校驗(yàn)袜硫;如果直接調(diào)用編譯是會(huì)自動(dòng)校驗(yàn)氯葬;
如果fire不存在,那么直接調(diào)用:在編譯時(shí)候就能夠發(fā)現(xiàn)(借助Xcode可以寫完就發(fā)現(xiàn))婉陷,但是使用performSelector的話一定是在運(yùn)行時(shí)候才能發(fā)現(xiàn)(此時(shí)程序崩潰)帚称;
Cocoa支持在運(yùn)行時(shí)向某個(gè)類添加方法,即方法編譯時(shí)不存在秽澳,但是運(yùn)行時(shí)候存在闯睹,這時(shí)候必然需要使用performSelector去調(diào)用。所以有時(shí)候如果使用了performSelector担神,為了程序的健壯性楼吃,會(huì)使用檢查方法respondsToSelector
4.2妄讯、直接調(diào)用方法時(shí)候孩锡,一定要在頭文件中聲明該方法的使用,也要將頭文件import進(jìn)來捞挥。而使用performSelector時(shí)候, 可以不用import頭文件包含方法的對(duì)象忧吟,直接用performSelector調(diào)用即可砌函。
4.3、performSelector是在iOS中的一種方法調(diào)用方式溜族。他可以向一個(gè)對(duì)象傳遞任何消息讹俊,而不需要在編譯的時(shí)候聲明這些方法。所以這也是runtime的一種應(yīng)用方式煌抒。
所以performSelector和直接調(diào)用方法的區(qū)別就在與runtime仍劈。
直接調(diào)用編譯是會(huì)自動(dòng)校驗(yàn)。如果方法不存在寡壮,那么直接調(diào)用 在編譯時(shí)候就能夠發(fā)現(xiàn)贩疙,編譯器會(huì)直接報(bào)錯(cuò)讹弯。
但是使用performSelector的話一定是在運(yùn)行時(shí)候才能發(fā)現(xiàn),如果此方法不存在就會(huì)崩潰这溅。所以如果使用performSelector的話他就會(huì)有個(gè)最佳伴侶respondsToSelector:來在運(yùn)行時(shí)判斷對(duì)象是否響應(yīng)此方法组民。

五、runLoop與線程的關(guān)系悲靴?

總結(jié):
5.1 線程與runLoop是一一對(duì)應(yīng)的
5.2 線程創(chuàng)建的時(shí)候臭胜,并沒有創(chuàng)建runLoop對(duì)象,runLoop會(huì)在第一次獲取的時(shí)候自動(dòng)創(chuàng)建癞尚。
5.3 主線程默認(rèn)開啟了runLoop耸三,子線程默認(rèn)沒有開啟。
源碼如下:

// 全局的Dictionary浇揩,key 是 pthread_t仪壮, value 是 CFRunLoopRef
static CFMutableDictionaryRef __CFRunLoops = NULL;
// 訪問 loopsDic 時(shí)的鎖
static CFLock_t loopsLock = CFLockInit;

// 獲取一個(gè) pthread 對(duì)應(yīng)的 RunLoop。
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
   
    if (!__CFRunLoops) {

        __CFUnlock(&loopsLock);
        // 第一次進(jìn)入時(shí)临燃,初始化全局Dic睛驳,并先為主線程創(chuàng)建一個(gè) RunLoop。
        CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
        CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
        CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
        
    }
    // 直接從 Dictionary 里獲取膜廊。
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFUnlock(&loopsLock);
    if (!loop) {
        // 取不到時(shí)乏沸,創(chuàng)建一個(gè)
        CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        __CFLock(&loopsLock);
        loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        if (!loop) {
            CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
            loop = newLoop;
        }
   
    return loop;
}

從上面的代碼可以看出,線程和 runLoop 之間是一一對(duì)應(yīng)的爪瓜,其關(guān)系是保存在一個(gè)全局的 Dictionary 里蹬跃。線程剛創(chuàng)建時(shí)并沒有 RunLoop,如果你不主動(dòng)獲取铆铆,那它一直都不會(huì)有蝶缀。RunLoop 的創(chuàng)建是發(fā)生在第一次獲取時(shí),RunLoop 的銷毀是發(fā)生在線程結(jié)束時(shí)薄货。你只能在一個(gè)線程的內(nèi)部獲取其 RunLoop(主線程除外)翁都。

六、runLoop執(zhí)行過程谅猾?

//
//  RunLoop內(nèi)部執(zhí)行過程.c
//  TZRunloop001-初識(shí)
//
//  Created by hzg on 2018/8/25.
//  Copyright ? 2018年 tz. All rights reserved.
//


SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
    __CFRunLoopLock(rl);
    
    /// 首先根據(jù)modeName找到對(duì)應(yīng)mode
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
    
    
    /// 通知 Observers: RunLoop 即將進(jìn)入 loop柄慰。
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    
    /// 內(nèi)部函數(shù),進(jìn)入loop
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    
    /// 通知 Observers: RunLoop 即將退出税娜。
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
    
    return result;
}

/// 核心函數(shù)
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    
    int32_t retVal = 0;
    do {
        
        /// 通知 Observers: 即將處理timer事件
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        
        /// 通知 Observers: 即將處理Source事件
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources)
        
        /// 處理Blocks
        __CFRunLoopDoBlocks(rl, rlm);
        
        /// 處理sources0
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        
        /// 處理sources0返回為YES
        if (sourceHandledThisLoop) {
            /// 處理Blocks
            __CFRunLoopDoBlocks(rl, rlm);
        }
        
        
        /// 判斷有無端口消息(Source1)
        if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
            /// 處理消息
            goto handle_msg;
        }
        
        /// 通知 Observers: 即將進(jìn)入休眠
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        __CFRunLoopSetSleeping(rl);
        
        /// 等待被喚醒
        __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
        
        
        // user callouts now OK again
        __CFRunLoopUnsetSleeping(rl);
        
        /// 通知 Observers: 被喚醒坐搔,結(jié)束休眠
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
        
    handle_msg:
        if (被Timer喚醒) {
            /// 處理Timers
            __CFRunLoopDoTimers(rl, rlm, mach_absolute_time());
        } else if (被GCD喚醒) {
            /// 處理gcd
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
        } else if (被Source1喚醒) {
            /// 被Source1喚醒敬矩,處理Source1
            __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply)
        }
        
        /// 處理block
        __CFRunLoopDoBlocks(rl, rlm);
        
        
        if (sourceHandledThisLoop && stopAfterHandle) {
            retVal = kCFRunLoopRunHandledSource;
        } else if (timeout_context->termTSR < mach_absolute_time()) {
            retVal = kCFRunLoopRunTimedOut;
        } else if (__CFRunLoopIsStopped(rl)) {
            __CFRunLoopUnsetStopped(rl);
            retVal = kCFRunLoopRunStopped;
        } else if (rlm->_stopped) {
            rlm->_stopped = false;
            retVal = kCFRunLoopRunStopped;
        } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
            retVal = kCFRunLoopRunFinished;
        }
        
    } while (0 == retVal);
    
    return retVal;
}

// main  dispatch queue
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__

// __CFRunLoopDoObservers
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__

// __CFRunLoopDoBlocks
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__

// __CFRunLoopDoSources0
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__

// __CFRunLoopDoSource1
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__

// __CFRunLoopDoTimers
__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__

七概行、休眠的原理?

RunLoop實(shí)現(xiàn)休眠的原理, 真正的原因是:

  1. 調(diào)用了內(nèi)核的API(mach_msg), 進(jìn)入內(nèi)核態(tài)弧岳,由內(nèi)核來將線程置于休眠
  2. 有消息凳忙,就喚醒線程业踏,回到用戶態(tài),來處理消息.


    休眠的原理

八消略、GCD Timer與NSTimer區(qū)別堡称?

8.1、NSTimer不準(zhǔn)時(shí)的原因艺演?

1.runLoop循環(huán)處理的這個(gè)時(shí)間
2.受runLoop模式的影響

8.2却紧、GCD Timer與NSTimer不同的原因?

  1. 都是源胎撤,一個(gè)是runLoop的源晓殊,一個(gè)Dispatch的源
  2. GCD timer不需要加入mode

代碼如下

#import "ViewController.h"

@interface ViewController ()

@property (nonatomic, strong) dispatch_source_t timer;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self gcdTimerTest];
}

- (void) gcdTimerTest {
    
    // 隊(duì)列
    dispatch_queue_t queue = dispatch_get_main_queue(); // 主線程隊(duì)列,會(huì)受到runLoop影響
//    dispatch_queue_t queue = dispatch_queue_create("timer_serial_label", DISPATCH_QUEUE_SERIAL); // 子線程隊(duì)列伤提,不會(huì)受到runLoop影響
    
    // 創(chuàng)建定時(shí)器
    dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    
    // 設(shè)置定時(shí)的開始時(shí)間巫俺、間隔時(shí)間
    dispatch_source_set_timer(timer, dispatch_time(DISPATCH_TIME_NOW, 0), 1*NSEC_PER_SEC, 0);
    
    // 設(shè)置定時(shí)器回調(diào)
    dispatch_source_set_event_handler(timer, ^{
        NSLog(@"你好");
    });
    
    // 啟動(dòng)定時(shí)器,默認(rèn)是關(guān)閉的
    dispatch_resume(timer);
    self.timer = timer;
}

總結(jié):GCD Timer總結(jié)肿男?

1.GCD Timer精度高
2.GCD Timer主線程執(zhí)行會(huì)受runLoop影響介汹,子線程不受影響
3.GCD Timer不受模式切換的影響

參考

iOS performSelector(參考)

推薦好文

深入理解RunLoop

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市舶沛,隨后出現(xiàn)的幾起案子嘹承,更是在濱河造成了極大的恐慌,老刑警劉巖如庭,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件叹卷,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡坪它,警方通過查閱死者的電腦和手機(jī)骤竹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來往毡,“玉大人蒙揣,你說我怎么就攤上這事】t!?“怎么了懒震?”我有些...
    開封第一講書人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長惩阶。 經(jīng)常有香客問我挎狸,道長扣汪,這世上最難降的妖魔是什么断楷? 我笑而不...
    開封第一講書人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮崭别,結(jié)果婚禮上冬筒,老公的妹妹穿的比我還像新娘恐锣。我一直安慰自己,他們只是感情好舞痰,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開白布土榴。 她就那樣靜靜地躺著,像睡著了一般响牛。 火紅的嫁衣襯著肌膚如雪玷禽。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,784評(píng)論 1 290
  • 那天呀打,我揣著相機(jī)與錄音矢赁,去河邊找鬼。 笑死贬丛,一個(gè)胖子當(dāng)著我的面吹牛撩银,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播豺憔,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼额获,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了恭应?” 一聲冷哼從身側(cè)響起抄邀,我...
    開封第一講書人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎暮屡,沒想到半個(gè)月后撤摸,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡褒纲,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年准夷,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片莺掠。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡衫嵌,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出彻秆,到底是詐尸還是另有隱情楔绞,我是刑警寧澤,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布唇兑,位于F島的核電站酒朵,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏扎附。R本人自食惡果不足惜蔫耽,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望留夜。 院中可真熱鬧匙铡,春花似錦图甜、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至钦讳,卻和暖如春矿瘦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背愿卒。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來泰國打工匪凡, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人掘猿。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓病游,卻偏偏與公主長得像,于是被迫代替她去往敵國和親稠通。 傳聞我的和親對(duì)象是個(gè)殘疾皇子衬衬,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348

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

  • Runloop是iOS和OSX開發(fā)中非常基礎(chǔ)的一個(gè)概念改橘,從概念開始學(xué)習(xí)滋尉。 RunLoop的概念 -般說,一個(gè)線程一...
    小貓仔閱讀 985評(píng)論 0 1
  • 概述 RunLoop作為iOS中一個(gè)基礎(chǔ)組件和線程有著千絲萬縷的關(guān)系飞主,同時(shí)也是很多常見技術(shù)的幕后功臣狮惜。盡管在平時(shí)多...
    sumrain_cloud閱讀 943評(píng)論 0 5
  • Run loop 剖析:Runloop 接收的輸入事件來自兩種不同的源:輸入源(intput source)和定時(shí)...
    Mitchell閱讀 12,422評(píng)論 17 111
  • 通過前面幾篇文章可以知道RunLoop實(shí)際上是一個(gè)事件處理的循環(huán).只要一個(gè)線程啟動(dòng)了RunLoop,在它沒有收到事...
    brownfeng閱讀 1,937評(píng)論 0 7
  • This is a terrible year 2016 ,if l could l hope everythin...
    Alrin閱讀 98評(píng)論 0 0