iOS RunLoop的使用及底層原理

”大師吏垮,近日我研讀線程操作之法哑姚,發(fā)現(xiàn)了一個(gè)問題倦青。我的子線程做完了任務(wù)之后就銷毀了瓮床,后續(xù)再來任務(wù),我需要重新開一個(gè)子線程产镐,但為什么我手中的這部iPhone18點(diǎn)開一個(gè)APP卻能一直運(yùn)轉(zhuǎn)呢?“

”哦踢步,少俠是說這個(gè)問題啊癣亚,簡單。為什么能一直運(yùn)轉(zhuǎn)呢获印?因?yàn)殡姵仉娏繘]用完述雾。“

”......“

“哈哈哈兼丰,少俠如此好學(xué)玻孟,老夫就授予你RunLoop使用大法和RunLoop內(nèi)功心法,融匯貫通這兩種秘籍鳍征,iPhone18的運(yùn)行機(jī)理會(huì)在你眼中變的越來越清晰黍翎。”

RunLoop使用大法

日常開發(fā)中主要使用在一下幾個(gè)方面:

  1. NSTimer相關(guān)使用
  2. 子線程毖薮裕活
  3. Perform Selector的使用
  4. 更深層次操作
  1. NSTimer在主線程時(shí)匣掸,列表滑動(dòng)時(shí)也可正常使用趟紊。
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(beginUpdateUI) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

創(chuàng)建NSTimer時(shí),默認(rèn)是運(yùn)行在主線程的NSRunLoopDefaultMode中,當(dāng)UIScrollView滾動(dòng)時(shí)碰酝,會(huì)切換到UITrackingRunLoopMode霎匈,此時(shí)定時(shí)器會(huì)停止調(diào)用。
為了讓定時(shí)器正確調(diào)用送爸,可以手動(dòng)將RunLoop加入到NSRunLoopCommonModes中铛嘱,此時(shí)定時(shí)器可以正確調(diào)用。

  1. 在子線程中使用NSTimer袭厂。
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
     NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2 target:self selector:@selector(beginUpdateUI) userInfo:nil repeats:YES];
     NSRunLoop *runloop = [NSRunLoop currentRunLoop];
     [runloop addTimer:timer forMode:NSDefaultRunLoopMode];
     [runloop run];
 });

子線程中的RunLoop在第一次獲取時(shí)創(chuàng)建弄痹,把timer加入到對(duì)應(yīng)的Mode之后,子線程中的timer正常工作嵌器。注意:如果想要取消子線程中的timer肛真,也要在對(duì)應(yīng)的子線程中取消。

  1. 子線程彼剑活蚓让。AFNetworking2.x中有一個(gè)示范性的應(yīng)用。
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
    [[NSThread currentThread] setName:@"AFNetworking"];

    NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
    [runLoop run];
  }
}

+ (NSThread *)networkRequestThread {
static NSThread *_networkRequestThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
    _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
    [_networkRequestThread start];
});

return _networkRequestThread;
}

主線程的RunLoop是默認(rèn)開啟的讥珍,子線程的RunLoop默認(rèn)是不開啟的历极,所以子線程執(zhí)行完一個(gè)任務(wù)之后,就會(huì)被銷毀衷佃。如果想讓子線程一直存活趟卸,就需要?jiǎng)?chuàng)建RunLoop。NSRunLoop *runLoop = [NSRunLoop currentRunLoop];氏义,如果只是這樣锄列,會(huì)發(fā)現(xiàn)子線程依然是做完任務(wù)就退出了。為什么出現(xiàn)這樣的情況呢惯悠?因?yàn)镽unLoop想要運(yùn)行在一個(gè)mode下邻邮,需要mode中有Source、Timer克婶、Observer筒严,當(dāng)這些都沒有時(shí)RunLoop會(huì)退出。所以添加了[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];情萤,相當(dāng)于添加了一個(gè)Source,這樣子線程的RunLoop就能一直運(yùn)行了鸭蛙,后臺(tái)線程保活成功筋岛。

  1. Perform Selector的使用
- (void)viewDidLoad {
   [super viewDidLoad];    
   [self performSelector:@selector(onMainThread) withObject:nil afterDelay:3];
 
 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
       [[NSThread currentThread] setName:@"gaga"];
       [self performSelector:@selector(onOtherThread) withObject:nil afterDelay:3];
   });

}

- (void)onMainThread {
   NSLog(@"%@", [NSThread currentThread]);
   NSLog(@"%@", NSStringFromSelector(_cmd));
}

- (void)onOtherThread {
   NSLog(@"%@", [NSThread currentThread]);
   NSLog(@"%@", NSStringFromSelector(_cmd));
}

在主線程和子線程中分別調(diào)用performSelector:withObject:afterDelay:娶视,發(fā)現(xiàn)主線程的正常執(zhí)行,但是子線程中調(diào)用的不能執(zhí)行泉蝌。

performSelector:withObject:afterDelay延時(shí)操作相當(dāng)于創(chuàng)建一個(gè)Timer添加到當(dāng)前線程的RunLoop中歇万,因?yàn)橹骶€程的RunLoop一直存在揩晴,所以可以正常執(zhí)行。而子線程的RunLoop在第一次獲取時(shí)才會(huì)創(chuàng)建贪磺,因?yàn)槲覀儧]有手動(dòng)獲取子線程的RunLoop硫兰,所以不能正常執(zhí)行。

將子線程修改為如下即可正常執(zhí)行

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [[NSThread currentThread] setName:@"gaga"];
        [self performSelector:@selector(onOtherThread) withObject:nil afterDelay:3];
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        [runLoop run];
    });

打印子線程的RunLoop可以看到Timer寒锚,證實(shí)了延時(shí)操作會(huì)加入Timer的想法劫映。

- (void)onOtherThread {
    NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    NSLog(@"%@", runLoop);
}

結(jié)果:
timers = <CFArray 0x600001fa6d60 [0x10da31ae8]>{type = mutable-small, count = 0, values = ()},
    currently 589343081 (174839084728530) / soft deadline in: 1.84465692e+10 sec (@ -1) / hard deadline in: 1.84465692e+10 sec (@ -1)

“少俠,想一想如果不用延時(shí)刹前,直接在主線程和子線程使用performSelector:withObject:泳赋,會(huì)正常調(diào)用嗎?”

“額...”

"答案是會(huì)正常調(diào)用喇喉,因?yàn)橹苯邮褂?code>performSelector:withObject:祖今,相當(dāng)于是個(gè)方法調(diào)用,不涉及Runloop拣技。"

  1. 其他操作

當(dāng)然還有其他的應(yīng)用千诬,比如在子線程中執(zhí)行performSelector這個(gè)方法時(shí),如果不創(chuàng)建RunLoop會(huì)發(fā)現(xiàn)方法無法調(diào)用膏斤。這個(gè)留給少俠徐绑,自己摸索了。

還有一種應(yīng)用在iOS性能優(yōu)化(中級(jí)+): 異步繪制有所體現(xiàn)莫辨,添加Observer,在RunLoop運(yùn)行中的合適時(shí)機(jī)執(zhí)行想要操作的代碼傲茄。

“少俠可看好了,以上這些使用方法涵蓋了RunLoop大部分的應(yīng)用層使用沮榜∨陶ィ”

“大師,這們武功修煉起來敞映,確實(shí)讓人經(jīng)絡(luò)通暢较曼,身心舒適≌裨福”

“但這些只是招式,少俠如果想要更好的了解iPhone的運(yùn)行機(jī)理弛饭,還需修煉對(duì)應(yīng)的內(nèi)功心法方方能發(fā)揮出本門武功的最大威力冕末。”

RunLoop內(nèi)功心法

線程與runloop一一對(duì)應(yīng)侣颂,其關(guān)系保存在一個(gè)全局的Dictionary里档桃。線程剛創(chuàng)建時(shí)沒有RunLoop,如果你不主動(dòng)獲取憔晒,那它就一直不會(huì)有藻肄。
RunLoop的創(chuàng)建是發(fā)生在第一次獲取時(shí)蔑舞,RunLoop的銷毀是發(fā)生在線程結(jié)束時(shí)。RunLoop不能直接創(chuàng)建嘹屯,只能獲取攻询。
提供了兩個(gè)自動(dòng)獲取的函數(shù):CFRunLoopGetMain()CFRunLoopGetCurrent(),且只能在一個(gè)線程的內(nèi)部獲取其RunLoop(主線程除外)州弟。以上這些關(guān)系钧栖,可以通過觀察CF源碼中,CFRunLoop.c 文件里婆翔,
_CFRunLoopGet0這個(gè)方法查看拯杠。

RunLoop

RunLoop主要涉及這幾個(gè)類
CFRunLoopRef CFRunLoopModeRef CFRunLoopSourceRef CFRunLoopTimerRef CFRunLoopObserverRef,一個(gè)RunLoop包含多個(gè)Mode啃奴,每個(gè)Mode又包含多個(gè)Source/Timer/Observer潭陪。每次調(diào)用RunLoop的主函數(shù)時(shí),只能指定其中一個(gè)Mode最蕾,這個(gè)Mode被稱作CurrentMode依溯,如果需要切換Mode,只能退出Loop揖膜,再重新指定一個(gè)Mode進(jìn)入誓沸。這樣做主要是為了分隔開不同組的Source/Timer/Observer,讓其互不影響。如果一個(gè)mode中一個(gè)item都沒有壹粟,RunLoop會(huì)直接退出拜隧,不進(jìn)入循環(huán)。

附一張RunLoop運(yùn)行圖

RunLoop運(yùn)行
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末趁仙,一起剝皮案震驚了整個(gè)濱河市洪添,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌雀费,老刑警劉巖干奢,帶你破解...
    沈念sama閱讀 218,941評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異盏袄,居然都是意外死亡忿峻,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門辕羽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來逛尚,“玉大人,你說我怎么就攤上這事刁愿〈履” “怎么了?”我有些...
    開封第一講書人閱讀 165,345評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長滤钱。 經(jīng)常有香客問我觉壶,道長,這世上最難降的妖魔是什么件缸? 我笑而不...
    開封第一講書人閱讀 58,851評(píng)論 1 295
  • 正文 為了忘掉前任铜靶,我火速辦了婚禮,結(jié)果婚禮上停团,老公的妹妹穿的比我還像新娘旷坦。我一直安慰自己,他們只是感情好佑稠,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評(píng)論 6 392
  • 文/花漫 我一把揭開白布秒梅。 她就那樣靜靜地躺著,像睡著了一般舌胶。 火紅的嫁衣襯著肌膚如雪捆蜀。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,688評(píng)論 1 305
  • 那天幔嫂,我揣著相機(jī)與錄音辆它,去河邊找鬼。 笑死履恩,一個(gè)胖子當(dāng)著我的面吹牛锰茉,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播切心,決...
    沈念sama閱讀 40,414評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼飒筑,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了绽昏?” 一聲冷哼從身側(cè)響起协屡,我...
    開封第一講書人閱讀 39,319評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎全谤,沒想到半個(gè)月后肤晓,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,775評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡认然,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評(píng)論 3 336
  • 正文 我和宋清朗相戀三年补憾,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片卷员。...
    茶點(diǎn)故事閱讀 40,096評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡余蟹,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出子刮,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,789評(píng)論 5 346
  • 正文 年R本政府宣布挺峡,位于F島的核電站葵孤,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏橱赠。R本人自食惡果不足惜尤仍,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望狭姨。 院中可真熱鬧宰啦,春花似錦、人聲如沸饼拍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽师抄。三九已至漓柑,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間叨吮,已是汗流浹背辆布。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評(píng)論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留茶鉴,地道東北人锋玲。 一個(gè)月前我還...
    沈念sama閱讀 48,308評(píng)論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像涵叮,于是被迫代替她去往敵國和親惭蹂。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評(píng)論 2 355

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

  • iOS刨根問底-深入理解RunLoop 概述 RunLoop作為iOS中一個(gè)基礎(chǔ)組件和線程有著千絲萬縷的關(guān)系围肥,同時(shí)...
    reallychao閱讀 821評(píng)論 0 6
  • 轉(zhuǎn)自bireme剿干,原地址:https://blog.ibireme.com/2015/05/18/runloop/...
    乜_啊_閱讀 1,374評(píng)論 0 5
  • 轉(zhuǎn)載:http://www.cocoachina.com/ios/20150601/11970.html RunL...
    Gatling閱讀 1,438評(píng)論 0 13
  • RunLoop的定義與概念RunLoop的主要作用main函數(shù)中的RunLoopRunLoop與線程的關(guān)系RunL...
    __silhouette閱讀 1,012評(píng)論 0 6
  • runtime 和 runloop 作為一個(gè)程序員進(jìn)階是必須的,也是非常重要的穆刻, 在面試過程中是經(jīng)常會(huì)被問到的置尔, ...
    SOI閱讀 21,808評(píng)論 3 63