# 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)系作者
  • 序言:七十年代末隧魄,一起剝皮案震驚了整個(gè)濱河市卓练,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌购啄,老刑警劉巖襟企,帶你破解...
    沈念sama閱讀 217,542評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異狮含,居然都是意外死亡顽悼,警方通過(guò)查閱死者的電腦和手機(jī)曼振,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)蔚龙,“玉大人冰评,你說(shuō)我怎么就攤上這事∧靖” “怎么了甲雅?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,912評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng)蹋凝,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,449評(píng)論 1 293
  • 正文 為了忘掉前任妖枚,我火速辦了婚禮,結(jié)果婚禮上苍在,老公的妹妹穿的比我還像新娘绝页。我一直安慰自己,他們只是感情好寂恬,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布抒寂。 她就那樣靜靜地躺著,像睡著了一般掠剑。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上郊愧,一...
    開(kāi)封第一講書(shū)人閱讀 51,370評(píng)論 1 302
  • 那天朴译,我揣著相機(jī)與錄音,去河邊找鬼属铁。 笑死眠寿,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的焦蘑。 我是一名探鬼主播盯拱,決...
    沈念sama閱讀 40,193評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼例嘱!你這毒婦竟也來(lái)了狡逢?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,074評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤拼卵,失蹤者是張志新(化名)和其女友劉穎奢浑,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體腋腮,經(jīng)...
    沈念sama閱讀 45,505評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡雀彼,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評(píng)論 3 335
  • 正文 我和宋清朗相戀三年壤蚜,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片徊哑。...
    茶點(diǎn)故事閱讀 39,841評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡袜刷,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出莺丑,到底是詐尸還是另有隱情著蟹,我是刑警寧澤,帶...
    沈念sama閱讀 35,569評(píng)論 5 345
  • 正文 年R本政府宣布窒盐,位于F島的核電站草则,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏蟹漓。R本人自食惡果不足惜炕横,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評(píng)論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望葡粒。 院中可真熱鬧份殿,春花似錦、人聲如沸嗽交。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,783評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)夫壁。三九已至拾枣,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間盒让,已是汗流浹背梅肤。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,918評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留邑茄,地道東北人姨蝴。 一個(gè)月前我還...
    沈念sama閱讀 47,962評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像肺缕,于是被迫代替她去往敵國(guó)和親左医。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評(píng)論 2 354

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

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