《iOS知識點(diǎn)梳理-Runloop》

什么是 RunLoop?

  • 運(yùn)行循環(huán)
  • 內(nèi)部就是一個(gè) do-while 循環(huán), 在這個(gè)循環(huán)里面不斷的處理各種任務(wù)
  • 一個(gè)線程對應(yīng)有一個(gè) RunLoop, 主線程的 RunLoop 默認(rèn)已經(jīng)啟動, 子線程的 RunLoop 需要手動去啟動 (調(diào)用 run 方法)
  • RunLoop 只能選擇一個(gè) Mode 啟動, 如果當(dāng)前 Mode 中沒有任何Source(Sources0饰抒、Sources1)撩银、Timer, 那么就直接退出 RunLoop.
  • 基本的作用就是保持程序的持續(xù)運(yùn)行, 處理 app 中的各種事件. 通過 RunLoop, 有事運(yùn)行, 沒事就休息, 可以節(jié)省 cpu 資源, 提高程序性能.

RunLoop對象

iOS 中有2套API來訪問和使用RunLoop
  • Foundation: NSRunLoop
  • Core Foundation: CFRunLoopRef
  • NSRunLoop 和 CFRunLoopRef 都代表著 RunLoop對象
  • NSRunLoop 是基于 CFRunLoopRef 的一層 OC 包裝, 所以要了解 RunLoop內(nèi)部結(jié)構(gòu), 需要多研究 CFRunLoopRef 層面的 API

RunLoop和線程

  • 每條線程都有唯一的一個(gè)與之對應(yīng)的 RunLoop 對象
  • 主線程的 RunLoop 已經(jīng)自動創(chuàng)建好了, 子線程的 RunLoop 需要主動創(chuàng)建
  • RunLoop 在第一次獲取時(shí)創(chuàng)建, 在線程結(jié)束時(shí)銷毀

獲取 RunLoop 對象

  • Foundation
    [NSRunLoop currentRunLoop]; // 獲取當(dāng)前 RunLoop 對象
    [NSRunLoop mainRunLoop]; // 獲取主線程 RunLoop 對象
  • Core Foundation
    CFRunLoopGetCurrent(): // 獲取當(dāng)前 RunLoop 對象
    CFRunLoopGetMain(); // 獲取主線程 RunLoop 對象

RunLoop相關(guān)類

Core Foundation 中關(guān)于 RunLoop 的5個(gè)類
  • CFRunLoopRef
  • CFRunLoopModeRef
  • CFRunLoopSourceRef
  • CFRunLoopTimerRef
  • CFRunLoopObserverRef

CFRunLoopModeRef

  • CFRunLoopModeRef 代表 RunLoop 的運(yùn)行模式.
  • 一個(gè) RunLoop 包含若干個(gè) Mode, 每個(gè) Model 又包含若干個(gè)(set)Source/(array)Timer/(array)Observer
  • 每次 RunLoop 啟動時(shí), 只能制定其中一個(gè) Mode, 這個(gè) Mode 被稱作 CurrentMode
  • 如果需要切換 Mode, 只能退出 Loop, 重新制定一個(gè) Mode 再進(jìn)入
  • mode 主要是用來制定事件在運(yùn)行循環(huán)中的優(yōu)先級, 分為:
    1. NSDefaultRunLoopMode (kCFRunLoopDefaultMode): 默認(rèn), 空閑狀態(tài)
    2. UITrackingRunLoopMode: ScrollView 滑動時(shí)會切換到這個(gè)Mode
    3. UIInitializationRunLoopMode: run loop 啟動時(shí), 會切換到該 Mode
    4. NSRunLoopCommonModes (kCFRunLoopCommonModes) : mode 集合

蘋果公開提供的mode有倆個(gè): NSDefaultRunLoopMode (kCFRunLoopDefaultMode), NSRunLoopCommonModes (kCFRunLoopCommonModes)

CFRunLoopTimerRef

  • CFRunLoopTimerRef 是基于時(shí)間的觸發(fā)器
  • CFRunLoopTimerRef 基本上說的就是 NSTimer, 它受 RunLoop 的 Mode 的影響

CFRunLoopSourceRef

  • CFRunLoopSourceRef 是事件源 (輸入源)
  • 按照官方的文檔, Source 的分類
    1. Port-Based Source
    2. Custom Input Sources
    3. Cocoa Perform Selector
  • 按照函數(shù)調(diào)用棧, Source 的分類
    1. Source0: 非基于 Port 的
    2. Source1: 基于 Port 的, 通過內(nèi)核和其他線程通信, 接受、分發(fā)系統(tǒng)事件

CFRunLoopObserverRef

  • CFRunLoopObserverRef 是觀察者, 能夠箭筒 RunLoop 的改變狀態(tài)
  • 可以監(jiān)聽的時(shí)間點(diǎn)有以下幾個(gè):
    1. kcfRunLoopEntry (即將進(jìn)入 loop ) // 1
    2. kcfRunLoopBeforeTimers (即將處理 Timer) // 2
    3. kcfRunLoopBeforeSource (即將處理 source) // 4
    4. kcfRunLoopBeforeWaiting (即將進(jìn)入休眠) // 32
    5. kcfRunLoopAfterWaiting (剛從休眠中喚醒) // 64
    6. kcfRunLoopExit (即將退出 loop) // 128
  • 添加觀察者
CFRunLoopObserverRef observer =
CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(),
kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer,
CFRunLoopActivity activity) {
NSLog(@"----??????RunLoop????????????---%zd", activity);
});
// ????????????????RunLoop??????
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer,
kCFRunLoopDefaultMode);
// ????Observer
CFRelease(observer);

RunLoop處理邏輯

  • 通知 Observer: 即將進(jìn)入 Loop (1)
  • 通知 Observer: 將要處理 Timer (2)
  • 通知 Observer: 將要處理 Source0 (3)
  • 處理 Source0 (4)
  • 如果有 Source0, 跳到第9步(5)
  • 通知 Observer: 線程即將休眠(6)
  • 休眠, 等待喚醒: (7)
    1. Source0(port).
    2. timer 啟動
    3. RunLoop 設(shè)置的 Timer 已經(jīng)超時(shí)
    4. RunLoop 被外部手動喚醒
  • 通知 Observer: 線程將被喚醒 (8)
  • 處理未處理的時(shí)間 (9)
    1. 如果用戶定義的定時(shí)器啟動, 處理定時(shí)器時(shí)間并重啟 RunLoop. 進(jìn)入步驟 (2)
    2. 如果輸入源啟動, 傳遞相應(yīng)的消息.
    3. 如果 RunLoop 被顯示喚醒二時(shí)間還沒有超時(shí), 重啟 RunLoop, 進(jìn)入步驟 (2)
  • 通知 Observer: 即將退出 Loop

RunLoop的應(yīng)用

  • NSTimer
  • ImageView 顯示
  • PerformSelector
  • 常駐線程
  • 自動釋放池

RunLoop 定時(shí)源和輸入源

  • RunLoop 處理的輸入事件有倆種不同的來源: 輸入源 (input source) 和定時(shí)源 (timer source).
  • 輸入源傳遞異步消息, 通常來自于其他線程或程序.
  • 定時(shí)源則傳遞同步消息, 在特定時(shí)間或者一定時(shí)間間隔發(fā)生.

NSRunLoop 的實(shí)現(xiàn)機(jī)制, 以及在多線程中如何使用

  • 實(shí)現(xiàn)機(jī)制: RunLoop 的基本作用, 處理邏輯.
  • 程序創(chuàng)建子程序的時(shí)候, 才需要手動啟動 runLoop. 主線程的 runLoop 已經(jīng)默認(rèn)啟動.
  • 在多線程中, 你需要判斷是否需要 RunLoop. 如果需要 RunLoop, 那么你要負(fù)責(zé)配置 RunLoop 并啟動. 你不需要在任何情況下都去啟動 RunLoop. 比如, 你使用線程去處理一個(gè)預(yù)先定義好的耗時(shí)極長的任務(wù)時(shí), 你就可以無需啟動 RunLoop. RunLoop 只在你要和線程有交互事才需要.

RunLoop和線程有什么關(guān)系?

  • 主線程的 RunLoop 默認(rèn)是啟動的
    iOS的應(yīng)用程序里面, 程序啟動后會有一個(gè)如下的main () 函數(shù)
    int main(int argc, char * argv[]) {
      @autoreleasepool {
          return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
        }
    }
    
    重點(diǎn)的是 UIApplicationMain()函數(shù), 這個(gè)方法會為 mainThread 設(shè)置一個(gè) RunLoop 對象.
    這就解釋了: 為什么我們的應(yīng)用可以在無人操作的時(shí)候休息, 需要讓它干活的時(shí)候又能立馬響應(yīng).
  • 對其他的線程來說, RunLoop 默認(rèn)是沒有啟動的, RunLoop 只有你在要和線程有交互的時(shí)候才有需要.
  • 在任何一個(gè) coco 程序中, 都可以通過下面的代碼來獲取當(dāng)前的 RunLoop.
    NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    

autorelease 對象在什么情況下會被釋放?

  • 分倆種情況: 手動干預(yù)釋放和系統(tǒng)自動釋放.
  • 手動干預(yù)釋放就是指定 autoreleasePool, Autorelease 對象會在當(dāng)前的 runLoop 迭代結(jié)束時(shí)釋放.
  • kCFRunLoopEntry(1): 第一次進(jìn)去自動創(chuàng)建一個(gè) autorelease
  • kCFRunLoopBeforeWaiting(32): 進(jìn)入休眠狀態(tài)前會自動銷毀一個(gè) autorelease, 然后重新創(chuàng)建一個(gè)新的 autorelease
  • kCFRunLoopExit(128): 退出 RunLoop 時(shí)會自動銷毀最后一個(gè)創(chuàng)建的 autorelease

測試, RunLoop 的理解不正確的是?

 A 每一個(gè)線程都有其對應(yīng)的RunLoop
 B 默認(rèn)非主線程的RunLoop是沒有運(yùn)行的
 C 在一個(gè)單獨(dú)的線程中沒有必要去啟用RunLoop
 D 可以將NSTimer添加到runloop中
  • 參考答案: C
  • 理由: RunLoop, 它是多線程的法寶, 通常來說一個(gè)線程一次只執(zhí)行一次任務(wù), 執(zhí)行完任務(wù)會退出線程. 但是, 對于主線程是不能退出的, 因此我們需要讓主線程即時(shí)任務(wù)執(zhí)行完畢, 也可以繼續(xù)等待接受事件而不退出,那么 RunLoop 就成關(guān)鍵法寶了. 但是非主線程通常來說就是為了執(zhí)行某一任務(wù)的, 執(zhí)行完畢冀需要?dú)w還資源, 因此默認(rèn)是不運(yùn)行 RunLoop 的. NSRunLoop 提供了一個(gè)添加 NSTimer 的方法, 這個(gè)方法是正常狀態(tài)下就會回調(diào).

RunLoop 的 Mode 作用是什么?

mode 主要是用來指定時(shí)間在運(yùn)行循環(huán)中的優(yōu)先級, 分為:
  • NSDefaultRunLoopMode (kCFRunLoopDefaultMode): 默認(rèn), 空閑狀態(tài)
  • UITrackingRunLoopMode: ScrollView 滑動的時(shí)候會切換到這個(gè) mode
  • UIInitializationRunLoopMode: RunLoop 啟動時(shí), 會切換到該 mode
  • NSRunLoopCommonModes (kCFRunLoopCommonModes) : Mode 集合
蘋果公開提供的 Mode 有倆個(gè):
  • NSDefaultRunLoopMode (kCFRunLoopDefaultMode)
  • NSRunLoopCommonModes (kCFRunLoopCommonModes)
    如果我們把一個(gè) NSTimer 對象以 NSDefaultRunLoopMode (kCFRunLoopDefaultMode) 添加到主運(yùn)行循環(huán)中的時(shí)候, ScrollView 的滑動會導(dǎo)致 Mode 的切換, 而導(dǎo)致 NSTimer 將不再被調(diào)度, 如果希望滑動的時(shí)候也能夠被調(diào)度, 我們就可以是用 NSRunLoopCommonMode (包含, NSDefaultRunLoopMode 和 NSTrackingRunLoopMode 倆個(gè)狀態(tài))

測試, 請寫出 NSTimer 使用時(shí)的注意事項(xiàng)

思路: 如果想要銷毀 timer , 應(yīng)該先把 timer 置為失效, 否則 timer 就一直占用內(nèi)存而不會釋放. 造成邏輯上的內(nèi)存泄漏. 而且這種泄漏不能用 Xcode 和 instruments 測出來. 未將 timer 置為失效, 每次創(chuàng)建一次, 則之前的不能得到釋放, 那么同時(shí)存在多個(gè) timer 的實(shí)例在內(nèi)存中.

參考答案:
  • 注意 timer 添加到 runloop 時(shí)應(yīng)該設(shè)置什么 mode.
  • 注意timer 在不需要時(shí), 一定要調(diào)用 invalidate 方法使定時(shí)器失效, 否則得不到釋放.

測試, UITableViewCell 上有個(gè) UILabel, 顯示 NSTimer 實(shí)現(xiàn)的秒表時(shí)間, 手指滾動 cell 過程中, label 是否刷新, 為什么?

思路同上, 自己作答.

測試, 為什么 UIScrollView 的滾動會導(dǎo)致 NSTimer 失效?

思路同上, 自己作答.

測試, 在滑動頁面上的列表, timer 會暫推酉拢回調(diào), 為什么? 如何解決?

思路同上, 自己作答.

在開發(fā)中如何使用 RunLoop? 什么應(yīng)用場景?

  • 開啟一個(gè)常駐線程 (讓一個(gè)子線程不進(jìn)入消亡狀態(tài), 等待其他線程發(fā)來消息, 處理其他事情)
  • 在子線程開啟一個(gè)定時(shí)器
  • 在子線程中進(jìn)行一些長期監(jiān)控
  • 可以控制定時(shí)器在特定模式下執(zhí)行
  • 可以讓某些事件 (行為, 任務(wù)) 在特定模式下執(zhí)行
  • 可以添加 Observer 監(jiān)聽 RunLoop 的狀態(tài), 比如監(jiān)聽點(diǎn)擊事件的處理 (在所有點(diǎn)擊事件之前做一些事情)

你在開發(fā)過程中常用到哪些定時(shí)器计盒,定時(shí)器時(shí)間會有誤差嗎卧抗,如果有禀倔,為什么會有誤差熬芜?

iOS中常NSTimer战坤、CADisplayLink曙强、GCD定時(shí)器,其中NSTimer途茫、CADisplayLink基于NSRunLoop實(shí)現(xiàn)碟嘴,故存在誤差,GCD定時(shí)器只依賴系統(tǒng)內(nèi)核囊卜,相對一前兩者是比較準(zhǔn)時(shí)的娜扇。

誤差原因是:與NSRunLoop機(jī)制有關(guān), 因?yàn)镽unLoop每跑完一次圈再去檢查當(dāng)前累計(jì)時(shí)間是否已經(jīng)達(dá)到定時(shí)設(shè)置的間隔時(shí)間栅组,如果未達(dá)到雀瓢,RunLoop將進(jìn)入下一輪任務(wù),待任務(wù)結(jié)束之后再去檢查當(dāng)前累計(jì)時(shí)間玉掸,而此時(shí)的累計(jì)時(shí)間可能已經(jīng)超過了定時(shí)器的間隔時(shí)間刃麸,故會存在誤差。

參考《iOS常見三種定時(shí)器-NSTimer司浪、CADisplayLink泊业、GCD定時(shí)器》

2. NSTimer、CADisplayLink會產(chǎn)生循環(huán)引用嗎啊易?如果會吁伺,你是如何解決的?

如果直接使用租谈,會產(chǎn)生循環(huán)引用問題篮奄。可以增加一個(gè)中間類,給這個(gè)類添加一個(gè)用weak修飾的id 類型target屬性宦搬,并重寫中間類的消息轉(zhuǎn)發(fā)方法牙瓢。實(shí)現(xiàn)如下代碼:

聲明文件.h:

#import <Foundation/Foundation.h>

@interface LXProxy : NSProxy
+ (instancetype)proxyWithTarget:(id)target;

@end

復(fù)制代碼

實(shí)現(xiàn)文件.m

#import "LXProxy.h"

@interface LXProxy ()

/** weak target*/
@property (nonatomic, weak) id target;

@end

@implementation LXProxy

+ (instancetype)proxyWithTarget:(id)target{
LXProxy *proxy = [LXProxy alloc];
proxy.target = target;

return proxy;

}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)sel{
return [self.target methodSignatureForSelector:sel];

}

- (void)forwardInvocation:(NSInvocation *)invocation{
[invocation invokeWithTarget:self.target];

}

@end
復(fù)制代碼

調(diào)用代碼:

 _timer = [NSTimer scheduledTimerWithTimeInterval:2 target:[LXProxy proxyWithTarget:self] selector:@selector(test) userInfo:nil repeats:YES];

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市间校,隨后出現(xiàn)的幾起案子矾克,更是在濱河造成了極大的恐慌,老刑警劉巖憔足,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件胁附,死亡現(xiàn)場離奇詭異,居然都是意外死亡滓彰,警方通過查閱死者的電腦和手機(jī)控妻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來揭绑,“玉大人弓候,你說我怎么就攤上這事∷耍” “怎么了菇存?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長邦蜜。 經(jīng)常有香客問我依鸥,道長,這世上最難降的妖魔是什么悼沈? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任贱迟,我火速辦了婚禮,結(jié)果婚禮上絮供,老公的妹妹穿的比我還像新娘衣吠。我一直安慰自己,他們只是感情好壤靶,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布蒸播。 她就那樣靜靜地躺著,像睡著了一般萍肆。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上胀屿,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天塘揣,我揣著相機(jī)與錄音,去河邊找鬼宿崭。 笑死亲铡,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播奖蔓,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼赞草,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了吆鹤?” 一聲冷哼從身側(cè)響起厨疙,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎疑务,沒想到半個(gè)月后沾凄,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡知允,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年撒蟀,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片温鸽。...
    茶點(diǎn)故事閱讀 40,040評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡保屯,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出涤垫,到底是詐尸還是另有隱情姑尺,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布雹姊,位于F島的核電站股缸,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏吱雏。R本人自食惡果不足惜敦姻,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望歧杏。 院中可真熱鬧镰惦,春花似錦、人聲如沸犬绒。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽凯力。三九已至茵瘾,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間咐鹤,已是汗流浹背拗秘。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留祈惶,地道東北人雕旨。 一個(gè)月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓扮匠,卻偏偏與公主長得像,于是被迫代替她去往敵國和親凡涩。 傳聞我的和親對象是個(gè)殘疾皇子棒搜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評論 2 355

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

  • Run loop 剖析:Runloop 接收的輸入事件來自兩種不同的源:輸入源(intput source)和定時(shí)...
    Mitchell閱讀 12,432評論 17 111
  • 前言 最近離職了,可以盡情熬夜寫點(diǎn)總結(jié),不用擔(dān)心第二天上班爽并蛋疼著活箕,這篇的主角 RunLoop 一座大山力麸,涵蓋的...
    zerocc2014閱讀 12,376評論 13 67
  • 轉(zhuǎn)自http://blog.ibireme.com/2015/05/18/runloop 深入理解RunLoop ...
    飄金閱讀 983評論 0 4
  • 一、什么是runloop 字面意思是“消息循環(huán)讹蘑、運(yùn)行循環(huán)”末盔。它不是線程,但它和線程息息相關(guān)座慰。一般來講陨舱,一個(gè)線程一次...
    WeiHing閱讀 8,140評論 11 111
  • 文/白衡 我努力保持微笑的樣子 只是因?yàn)?不想讓眼淚有空隙可鉆 當(dāng)我習(xí)慣了微笑 竟忘記了還有哭 這個(gè)真實(shí)而又簡單的...
    白衡閱讀 248評論 2 2