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

什么是 RunLoop?

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

RunLoop對(duì)象

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

RunLoop和線(xiàn)程

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

獲取 RunLoop 對(duì)象

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

RunLoop相關(guān)類(lèi)

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

CFRunLoopModeRef

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

蘋(píng)果公開(kāi)提供的mode有倆個(gè): NSDefaultRunLoopMode (kCFRunLoopDefaultMode), NSRunLoopCommonModes (kCFRunLoopCommonModes)

CFRunLoopTimerRef

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

CFRunLoopSourceRef

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

CFRunLoopObserverRef

  • CFRunLoopObserverRef 是觀察者, 能夠箭筒 RunLoop 的改變狀態(tài)
  • 可以監(jiān)聽(tīng)的時(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: 線(xiàn)程即將休眠(6)
  • 休眠, 等待喚醒: (7)
    1. Source0(port).
    2. timer 啟動(dòng)
    3. RunLoop 設(shè)置的 Timer 已經(jīng)超時(shí)
    4. RunLoop 被外部手動(dòng)喚醒
  • 通知 Observer: 線(xiàn)程將被喚醒 (8)
  • 處理未處理的時(shí)間 (9)
    1. 如果用戶(hù)定義的定時(shí)器啟動(dòng), 處理定時(shí)器時(shí)間并重啟 RunLoop. 進(jìn)入步驟 (2)
    2. 如果輸入源啟動(dòng), 傳遞相應(yīng)的消息.
    3. 如果 RunLoop 被顯示喚醒二時(shí)間還沒(méi)有超時(shí), 重啟 RunLoop, 進(jìn)入步驟 (2)
  • 通知 Observer: 即將退出 Loop

RunLoop的應(yīng)用

  • NSTimer
  • ImageView 顯示
  • PerformSelector
  • 常駐線(xiàn)程
  • 自動(dòng)釋放池

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

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

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

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

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

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

autorelease 對(duì)象在什么情況下會(huì)被釋放?

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

測(cè)試, RunLoop 的理解不正確的是?

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

RunLoop 的 Mode 作用是什么?

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

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

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

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

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

思路同上, 自己作答.

測(cè)試, 為什么 UIScrollView 的滾動(dòng)會(huì)導(dǎo)致 NSTimer 失效?

思路同上, 自己作答.

測(cè)試, 在滑動(dòng)頁(yè)面上的列表, timer 會(huì)暫?;卣{(diào), 為什么? 如何解決?

思路同上, 自己作答.

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

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

你在開(kāi)發(fā)過(guò)程中常用到哪些定時(shí)器,定時(shí)器時(shí)間會(huì)有誤差嗎,如果有,為什么會(huì)有誤差?

iOS中常NSTimer、CADisplayLink、GCD定時(shí)器,其中NSTimer、CADisplayLink基于NSRunLoop實(shí)現(xiàn),故存在誤差,GCD定時(shí)器只依賴(lài)系統(tǒng)內(nèi)核,相對(duì)一前兩者是比較準(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)超過(guò)了定時(shí)器的間隔時(shí)間,故會(huì)存在誤差。

參考《iOS常見(jiàn)三種定時(shí)器-NSTimer、CADisplayLink、GCD定時(shí)器》

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

如果直接使用,會(huì)產(chǎn)生循環(huán)引用問(wèn)題??梢栽黾右粋€(gè)中間類(lèi),給這個(gè)類(lèi)添加一個(gè)用weak修飾的id 類(lèi)型target屬性,并重寫(xiě)中間類(lèi)的消息轉(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)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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

友情鏈接更多精彩內(nèi)容