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

首先祷杈,iOS 開(kāi)發(fā)中能遇到兩個(gè)線程對(duì)象: pthread_t 和 NSThread已旧。過(guò)去蘋(píng)果有份文檔標(biāo)明了 NSThread 只是 pthread_t 的封裝冠骄,但那份文檔已經(jīng)失效了裂允,現(xiàn)在它們也有可能都是直接包裝自最底層的 mach thread廓旬。蘋(píng)果并沒(méi)有提供這兩個(gè)對(duì)象相互轉(zhuǎn)換的接口际插,但不管怎么樣绝葡,可以肯定的是 pthread_t 和 NSThread 是一一對(duì)應(yīng)的。比如腹鹉,你可以通過(guò) pthread_main_thread_np() 或 [NSThread mainThread] 來(lái)獲取主線程藏畅;也可以通過(guò) pthread_self() 或 [NSThread currentThread] 來(lái)獲取當(dāng)前線程。CFRunLoop 是基于 pthread 來(lái)管理的功咒。

蘋(píng)果不允許直接創(chuàng)建 RunLoop愉阎,它只提供了兩個(gè)自動(dòng)獲取的函數(shù):CFRunLoopGetMain() 和 CFRunLoopGetCurrent()。 這兩個(gè)函數(shù)內(nèi)部的邏輯大概是下面這樣:

/// 全局的Dictionary力奋,key 是 pthread_t榜旦, value 是 CFRunLoopRef
static CFMutableDictionaryRef loopsDic;
/// 訪問(wèn) loopsDic 時(shí)的鎖
static CFSpinLock_t loopsLock;

/// 獲取一個(gè) pthread 對(duì)應(yīng)的 RunLoop。
CFRunLoopRef _CFRunLoopGet(pthread_t thread) {
    OSSpinLockLock(&loopsLock);
    
    if (!loopsDic) {
        // 第一次進(jìn)入時(shí)景殷,初始化全局Dic溅呢,并先為主線程創(chuàng)建一個(gè) RunLoop澡屡。
        loopsDic = CFDictionaryCreateMutable();
        CFRunLoopRef mainLoop = _CFRunLoopCreate();
        CFDictionarySetValue(loopsDic, pthread_main_thread_np(), mainLoop);
    }
    
    /// 直接從 Dictionary 里獲取。
    CFRunLoopRef loop = CFDictionaryGetValue(loopsDic, thread));
    
    if (!loop) {
        /// 取不到時(shí)咐旧,創(chuàng)建一個(gè)
        loop = _CFRunLoopCreate();
        CFDictionarySetValue(loopsDic, thread, loop);
        /// 注冊(cè)一個(gè)回調(diào)驶鹉,當(dāng)線程銷毀時(shí),順便也銷毀其對(duì)應(yīng)的 RunLoop铣墨。
        _CFSetTSD(..., thread, loop, __CFFinalizeRunLoop);
    }
    
    OSSpinLockUnLock(&loopsLock);
    return loop;
}

CFRunLoopRef CFRunLoopGetMain() {
    return _CFRunLoopGet(pthread_main_thread_np());
}

CFRunLoopRef CFRunLoopGetCurrent() {
    return _CFRunLoopGet(pthread_self());
}

/// 全局的Dictionary室埋,key 是 pthread_t, value 是 CFRunLoopRef
static CFMutableDictionaryRef loopsDic;
/// 訪問(wèn) loopsDic 時(shí)的鎖
static CFSpinLock_t loopsLock;
 
/// 獲取一個(gè) pthread 對(duì)應(yīng)的 RunLoop伊约。
CFRunLoopRef _CFRunLoopGet(pthread_t thread) {
    OSSpinLockLock(&loopsLock);
    
    if (!loopsDic) {
        // 第一次進(jìn)入時(shí)姚淆,初始化全局Dic,并先為主線程創(chuàng)建一個(gè) RunLoop屡律。
        loopsDic = CFDictionaryCreateMutable();
        CFRunLoopRef mainLoop = _CFRunLoopCreate();
        CFDictionarySetValue(loopsDic, pthread_main_thread_np(), mainLoop);
    }
    
    /// 直接從 Dictionary 里獲取腌逢。
    CFRunLoopRef loop = CFDictionaryGetValue(loopsDic, thread));
    
    if (!loop) {
        /// 取不到時(shí),創(chuàng)建一個(gè)
        loop = _CFRunLoopCreate();
        CFDictionarySetValue(loopsDic, thread, loop);
        /// 注冊(cè)一個(gè)回調(diào)超埋,當(dāng)線程銷毀時(shí)上忍,順便也銷毀其對(duì)應(yīng)的 RunLoop。
        _CFSetTSD(..., thread, loop, __CFFinalizeRunLoop);
    }
    
    OSSpinLockUnLock(&loopsLock);
    return loop;
}
 
CFRunLoopRef CFRunLoopGetMain() {
    return _CFRunLoopGet(pthread_main_thread_np());
}
 
CFRunLoopRef CFRunLoopGetCurrent() {
    return _CFRunLoopGet(pthread_self());
}

從上面的代碼可以看出纳本,線程和 RunLoop 之間是一一對(duì)應(yīng)的,其關(guān)系是保存在一個(gè)全局的 Dictionary 里腋颠。線程剛創(chuàng)建時(shí)并沒(méi)有 RunLoop繁成,如果你不主動(dòng)獲取,那它一直都不會(huì)有淑玫。RunLoop 的創(chuàng)建是發(fā)生在第一次獲取時(shí)巾腕,RunLoop 的銷毀是發(fā)生在線程結(jié)束時(shí)。你只能在一個(gè)線程的內(nèi)部獲取其 RunLoop(主線程除外)絮蒿。

關(guān)于GCD

實(shí)際上 RunLoop 底層也會(huì)用到 GCD 的東西,但同時(shí) GCD 提供的某些接口也用到了 RunLoop尊搬, 例如 dispatch_async()。

當(dāng)調(diào)用 dispatch_async(dispatch_get_main_queue(), block) 時(shí)土涝,libDispatch 會(huì)向主線程的 RunLoop 發(fā)送消息佛寿,RunLoop會(huì)被喚醒,并從消息中取得這個(gè) block但壮,并在回調(diào) CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE() 里執(zhí)行這個(gè) block冀泻。但這個(gè)邏輯僅限于 dispatch 到主線程,dispatch 到其他線程仍然是由 libDispatch 處理的蜡饵。

代碼示例:

#import "ViewController.h"
#import "PJThread.h"

@interface ViewController ()

//創(chuàng)建一個(gè)NSRunLoop屬性,用于在線程執(zhí)行完后能在訪問(wèn)他的內(nèi)容
@property (nonatomic, strong)NSRunLoop *threadRunLoop;

//GCD方式
@property (nonatomic, strong)NSRunLoop *threadRunLoop2;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    __weak typeof(self) weakSelf = self;
    //PJThread為自定義的繼承NSThread的線程,被銷毀時(shí)會(huì)打印"線程銷毀了!"
    PJThread *pjThread = [[PJThread alloc] initWithBlock:^{
        //線程剛創(chuàng)建時(shí)并沒(méi)有 RunLoop弹渔,如果你不主動(dòng)獲取,那它一直都不會(huì)有溯祸。RunLoop 的創(chuàng)建是發(fā)生在第一次獲取時(shí),這邊會(huì)創(chuàng)建一個(gè)與pjThread對(duì)應(yīng)的線程.你只能在一個(gè)線程的內(nèi)部獲取其 RunLoop(主線程除外)
        weakSelf.threadRunLoop = [NSRunLoop currentRunLoop];
        //為了能讓線程被銷毀weakSelf.threadRunLoop就不循環(huán)執(zhí)行(即不調(diào)用run)
        NSLog(@"在線程block內(nèi)打印:%@",weakSelf.threadRunLoop);
    }];
    
    [pjThread start];
    
    //到這邊時(shí)線程已經(jīng)執(zhí)行結(jié)束,被銷毀,他所對(duì)應(yīng)的runLoop也會(huì)被銷毀
    NSLog(@"在線程執(zhí)行后打印:%@",self.threadRunLoop);
    
    //和PJThread是一樣的效果
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        weakSelf.threadRunLoop2 = [NSRunLoop currentRunLoop];
        //為了能讓線程被銷毀weakSelf.threadRunLoop2就不循環(huán)執(zhí)行(即不調(diào)用run)
        NSLog(@"在dispatch_async block內(nèi)打印:%@",weakSelf.threadRunLoop2);
    });
    
    NSLog(@"在線程dispatch_async執(zhí)行后打印:%@",self.threadRunLoop2);
}

@end

PJThread代碼:

#import "PJThread.h"

@implementation PJThread

- (void)dealloc{
    NSLog(@"線程銷毀了!");
}

@end

最終的打印效果:


2017-06-10 14:35:37.966179 RunLoopTest[11826:3392880] 在線程執(zhí)行后打印:(null)
2017-06-10 14:35:37.966392 RunLoopTest[11826:3392880] 在線程dispatch_async執(zhí)行后打印:(null)
2017-06-10 14:35:37.967431 RunLoopTest[11826:3392903] 在dispatch_async block內(nèi)打印:<CFRunLoop 0x17017d040 [0x1aa9abbb8]>{wakeup port = 0x5f03, stopped = false, ignoreWakeUps = true, 
current mode = (none),
common modes = <CFBasicHash 0x1700520f0 [0x1aa9abbb8]>{type = mutable set, count = 1,
entries =>
    2 : <CFString 0x1a486d470 [0x1aa9abbb8]>{contents = "kCFRunLoopDefaultMode"}
}
,
common mode items = (null),
modes = <CFBasicHash 0x170052510 [0x1aa9abbb8]>{type = mutable set, count = 1,
entries =>
    2 : <CFRunLoopMode 0x170187df0 [0x1aa9abbb8]>{name = kCFRunLoopDefaultMode, port set = 0x6003, queue = 0x17017d100, source = 0x1701c6bd0 (not fired), timer port = 0x6203, 
    sources0 = (null),
    sources1 = (null),
    observers = (null),
    timers = (null),
    currently 518769338 (12655688486437) / soft deadline in: 7.68613809e+11 sec (@ -1) / hard deadline in: 7.68613809e+11 sec (@ -1)
},

}
}
2017-06-10 14:35:37.967823 RunLoopTest[11826:3392920] 在線程block內(nèi)打印:<CFRunLoop 0x17417c740 [0x1aa9abbb8]>{wakeup port = 0x5c03, stopped = false, ignoreWakeUps = true, 
current mode = (none),
common modes = <CFBasicHash 0x1740533e0 [0x1aa9abbb8]>{type = mutable set, count = 1,
entries =>
    2 : <CFString 0x1a486d470 [0x1aa9abbb8]>{contents = "kCFRunLoopDefaultMode"}
}
,
common mode items = (null),
modes = <CFBasicHash 0x1740532f0 [0x1aa9abbb8]>{type = mutable set, count = 1,
entries =>
    2 : <CFRunLoopMode 0x174187c50 [0x1aa9abbb8]>{name = kCFRunLoopDefaultMode, port set = 0x5d03, queue = 0x17417c800, source = 0x1741c6630 (not fired), timer port = 0x6303, 
    sources0 = (null),
    sources1 = (null),
    observers = (null),
    timers = (null),
    currently 518769338 (12655688502251) / soft deadline in: 7.68613809e+11 sec (@ -1) / hard deadline in: 7.68613809e+11 sec (@ -1)
},

}
}
2017-06-10 14:35:37.970469 RunLoopTest[11826:3392920] 線程銷毀了!


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末肢专,一起剝皮案震驚了整個(gè)濱河市舞肆,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌博杖,老刑警劉巖椿胯,帶你破解...
    沈念sama閱讀 212,718評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異欧募,居然都是意外死亡压状,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門跟继,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)种冬,“玉大人,你說(shuō)我怎么就攤上這事舔糖∮榱剑” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 158,207評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵金吗,是天一觀的道長(zhǎng)十兢。 經(jīng)常有香客問(wèn)我,道長(zhǎng)摇庙,這世上最難降的妖魔是什么旱物? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,755評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮卫袒,結(jié)果婚禮上宵呛,老公的妹妹穿的比我還像新娘。我一直安慰自己夕凝,他們只是感情好宝穗,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著码秉,像睡著了一般逮矛。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上转砖,一...
    開(kāi)封第一講書(shū)人閱讀 50,050評(píng)論 1 291
  • 那天贴膘,我揣著相機(jī)與錄音沽甥,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛崔挖,可吹牛的內(nèi)容都是我干的赌厅。 我是一名探鬼主播恢氯,決...
    沈念sama閱讀 39,136評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼靖苇,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了模捂?” 一聲冷哼從身側(cè)響起捶朵,我...
    開(kāi)封第一講書(shū)人閱讀 37,882評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤蜘矢,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后综看,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體品腹,經(jīng)...
    沈念sama閱讀 44,330評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評(píng)論 2 327
  • 正文 我和宋清朗相戀三年红碑,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了舞吭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,789評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡析珊,死狀恐怖羡鸥,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情忠寻,我是刑警寧澤惧浴,帶...
    沈念sama閱讀 34,477評(píng)論 4 333
  • 正文 年R本政府宣布,位于F島的核電站奕剃,受9級(jí)特大地震影響衷旅,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜纵朋,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評(píng)論 3 317
  • 文/蒙蒙 一柿顶、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧操软,春花似錦嘁锯、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,864評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)品山。三九已至胆建,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間肘交,已是汗流浹背笆载。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,099評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留涯呻,地道東北人凉驻。 一個(gè)月前我還...
    沈念sama閱讀 46,598評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像复罐,于是被迫代替她去往敵國(guó)和親涝登。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評(píng)論 2 351

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

  • http://www.cocoachina.com/ios/20150601/11970.html RunLoop...
    紫色冰雨閱讀 832評(píng)論 0 3
  • 原文地址:http://blog.ibireme.com/2015/05/18/runloop/ RunLoop ...
    大餅炒雞蛋閱讀 1,152評(píng)論 0 6
  • 轉(zhuǎn)載:http://www.cocoachina.com/ios/20150601/11970.html RunL...
    Gatling閱讀 1,436評(píng)論 0 13
  • 轉(zhuǎn)自http://blog.ibireme.com/2015/05/18/runloop 深入理解RunLoop ...
    飄金閱讀 976評(píng)論 0 4
  • 一連幾個(gè)晚上都?jí)舻街熳郧宓摹侗秤啊沸ё纾]上眼睛就是評(píng)委宣讀成績(jī)的畫(huà)面胀滚。我被逆襲了趟济,不到一分。失掉了我原本勝券在握的工...
    誤入塵世雨閱讀 128評(píng)論 0 0