深入淺出 RunLoop

前言

文章主要分為四個部分

  • 一篷店、RunLoop 簡介
  • 二叶沛、RunLoop 相關(guān)接口
  • 三瘪弓、RunLoop 相關(guān)邏輯流程
  • 四劫映、RunLoop 休眠實(shí)現(xiàn)原理
  • 五乓旗、RunLoop 實(shí)際應(yīng)用

一府蛇、RunLoop 簡介

1.1 RunLoop 基本概念

RunLoop 顧名思義 運(yùn)行循環(huán),在程序運(yùn)行過程中循環(huán)做一些事情屿愚。比如定時器汇跨、GCD、事件響應(yīng)妆距、界面刷新穷遂、手勢識別、AutoreleasePool 等都是基于 RunLoop 的基礎(chǔ)之上娱据,沒有 RunLoop 任何事都無法做蚪黑。
一個線程一次只能執(zhí)行一個任務(wù),執(zhí)行完成后線程就會退出。RunLoop 機(jī)制能讓線程隨時處理事件但并不退出忌穿。這里說的隨時是指:程序需要運(yùn)行時就保持程序的持續(xù)運(yùn)行抒寂,不需要的時候就進(jìn)入休眠狀態(tài)。下述 1.2 是一個典型的案列伴网。

NSRunLoop 和 CFRunLoopRef 都是和RunLoop 機(jī)制相關(guān)的類蓬推。CFRunLoopRef 基于 CoreFoundation 框架內(nèi),是純 C 函數(shù)的 API澡腾,所有這些 API 都是線程安全的沸伏。CFRunLoopRef 的代碼是開源的。NSRunLoop 是基于 CFRunLoopRef 动分,提供了面向?qū)ο蟮?API毅糟,但是這些 API 不是線程安全的。

1.2 為什么 main 函數(shù)不會 return掉 澜公?

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

上面main函數(shù)同一般函數(shù)相比姆另,啟動程序后并不會立刻 return 掉。其中UIApplicationMain 函數(shù)內(nèi)部默認(rèn)開啟了主線程的 RunLoop 坟乾,并執(zhí)行了一段類似無限循環(huán)的代碼迹辐。UIApplicationMain 函數(shù)一直沒有返回,所以運(yùn)行程序之后會 保持持續(xù)運(yùn)行狀態(tài)甚侣,節(jié)省CPU資源明吩,提高程序性能,該做事時做事殷费,該休息時休息印荔。

//無限循環(huán)代碼模式
int main(int argc, char * argv[]) {        
    BOOL running = YES;
    do {
        // 執(zhí)行各種任務(wù),處理各種事件
        // ......
    } while (running);

    return 0;
}

1.3 RunLoop 和 線程的關(guān)系

iOS中有2套API來訪問和使用RunLoop:

  • Foundation:NSRunLoop
  • Core Foundation:CFRunLoopRef

關(guān)于RunLoop 和線程之間的關(guān)系要知道以下幾點(diǎn):

  • 1详羡、 線程和 RunLoop 是一一對應(yīng)的仍律,其關(guān)系是保存在一個全局的 Dictionary 里。線程作為 key实柠,RunLoop 作為 value
  • 2水泉、線程剛創(chuàng)建時并沒有RunLoop對象,RunLoop會在第一次獲取它時創(chuàng)建窒盐。4個自動獲取的函數(shù):CFRunLoopGetMain()草则、[NSrunLoop mainRunLoop] 和 CFRunLoopGetCurrent()、[NSrunLoop currentRunLoop] 登钥。主線程的RunLoop對象獲取是在 UIApplicationMain 內(nèi)部,而子線程的RunLoop對象需要我們自己去獲取娶靡。
  • 3牧牢、銷毀則是在線程結(jié)束的時候。
  • 4、只能在當(dāng)前線程中操作當(dāng)前線程的RunLoop塔鳍,而不能去操作其他線程的RunLoop伯铣。

二、RunLoop 相關(guān)接口

2.1 RunLoop 的結(jié)構(gòu)


從上圖可以看出RunLoop 中包含thread轮纫,即 RunLoop 和 線程一一對應(yīng)腔寡。

和 RunLoop 相關(guān)的主要涉及五個類:

  • CFRunLoopRef:RunLoop對象
  • CFRunLoopModeRef:運(yùn)行模式
  • CFRunLoopSourceRef:輸入源/事件源
  • CFRunLoopTimerRef:定時源
  • CFRunLoopObserverRef:觀察者
RunLoop的結(jié)構(gòu)
image.png

從上圖可以看出,RunLoop 對象中可以包含多個 Mode掌唾,每個 Mode 又包含多個 Source放前、Timer、Observer糯彬,RunLoop 運(yùn)行過程中實(shí)際上就是去處理當(dāng)前 mode 中的 source凭语、timer、observer撩扒。

2.2 RunLoop 中的 Mode

關(guān)于Mode首先要知道:

  • 一個RunLoop 對象中可能包含多個Mode似扔。
  • 每次調(diào)用 RunLoop 的主函數(shù)時,只能指定其中一個 Mode(CurrentMode)搓谆。如RunLoop啟動時只能選擇其中一個Mode炒辉,作為currentMode。
  • 如果需要切換Mode泉手,只能退出當(dāng)前Loop黔寇,再重新選擇一個Mode進(jìn)入。主要是為了分隔開不同的 Source螃诅、Timer啡氢、Observer,讓它們之間互不影響术裸。參考
  • RunLoop啟動時只能選擇其中一個Mode倘是,作為currentMode

總共是有五種Mode:

  • kCFRunLoopDefaultMode:默認(rèn)模式,主線程是在這個運(yùn)行模式下運(yùn)行
  • UITrackingRunLoopMode:跟蹤用戶交互事件(用于 ScrollView 追蹤觸摸滑動袭艺,保證界面滑動時不受其他Mode影響)
  • UIInitializationRunLoopMode:在剛啟動App時第進(jìn)入的第一個 Mode搀崭,啟動完成后就不再使用
  • GSEventReceiveRunLoopMode:接受系統(tǒng)內(nèi)部事件,通常用不到
  • kCFRunLoopCommonModes:偽模式猾编,不是一種真正的運(yùn)行模式瘤睹,實(shí)際是kCFRunLoopDefaultModeUITrackingRunLoopMode的結(jié)合。

有這樣一個場景答倡,假設(shè)自己封裝一個無限輪播視圖轰传,很有可能會出現(xiàn)這樣一種情況:當(dāng)你滑動輪播視圖時,輪播視圖的定時器不再起作用瘪撇,不能通過定時器調(diào)整UIScrollView的偏移值获茬。之所以會出項(xiàng)上述現(xiàn)象港庄,是因?yàn)橹骶€程的 RunLoop 里有兩個 Mode:kCFRunLoopDefaultModeUITrackingRunLoopMode。默認(rèn)情況下是defaultMode恕曲,但是當(dāng)滑動UIScrollView時鹏氧,RunLoop 會將 mode 切換為 TrackingRunLoopMode,這時 Timer 就不會被執(zhí)行佩谣。這樣區(qū)分 mode 分隔開不同的 Source把还、Timer、Observer茸俭,讓它們之間互不影響吊履。這樣做的好處是讓不同模式下專心做自己的事情,可以更好的提高應(yīng)用性能瓣履。當(dāng)然如果想在滑動的時候不讓定時器失效率翅,可以使用CommonMode來解決。

[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

參考
實(shí)際NSRunLoopCommonModes并不是一種模式袖迎,它只是一種標(biāo)記冕臭,它能夠讓timer在放在NSRunLoopCommonModes中的所有模式下運(yùn)行。而NSRunLoopCommonModes正好放了NSDefaultRunLoopMode和UITrackingRunLoopMode兩種模式燕锥,所以timer就能在兩種模式下運(yùn)行了辜贵。

2.3 Mode 中的 CFRunLoopSourceRef

CFRunLoopSourceRef是事件源,主要有兩種有Source0 和 Source1归形。

Source0 :非基于 Port托慨。
  • 觸摸事件處理
  • performSelector:onThread:
    只包含了一個回調(diào)(函數(shù)指針),不能主動觸發(fā)事件暇榴。使用時厚棵,需先調(diào)用 CFRunLoopSourceSignal(source),將 Source 標(biāo)記為待處理蔼紧,然后手動調(diào)用 CFRunLoopWakeUp(runloop)喚醒 RunLoop婆硬,讓其處理這個事件。
Source1:基于Port奸例。
  • 線程間通信彬犯。每個線程都有一個 port ,不同線程之前通過這個 port 進(jìn)行通信查吊。
  • 事件捕捉谐区。如點(diǎn)擊屏幕,Source1 先進(jìn)行事件捕捉逻卖,會放在隊(duì)列中宋列,然后再一次包裝為 Source0 觸摸事件處理。

函數(shù)調(diào)用棧分類舉例

如上圖评也,創(chuàng)建一個按鈕炼杖,添加點(diǎn)擊事件戈鲁,并在按鈕回調(diào)事件添加斷點(diǎn),當(dāng)執(zhí)行到斷點(diǎn)出左側(cè)會出現(xiàn)相關(guān)棧調(diào)用信息嘹叫。從上圖可以看出:點(diǎn)擊事件就是在Sources0中處理的。至于 Source1 主要是用來接收诈乒、分發(fā)系統(tǒng)事件罩扇,然后再分發(fā)到Sources0中處理。
Sources0:

2.4 Mode 中的 CFRunLoopTimerRef

NSTimerperformSelector:withObject:afterDelay: 都是通過CFRunLoopTimerRef處理的怕磨。CFRunLoopTimerRef 是定時源喂饥,你可以簡單把它理解為NSTimer。其包含一個時間點(diǎn)和一個回調(diào)(函數(shù)指針)肠鲫。當(dāng)被加入到 RunLoop 時员帮,RunLoop 會注冊對應(yīng)的時間點(diǎn),當(dāng)時間到時导饲,RunLoop 會執(zhí)行對應(yīng)時間點(diǎn)的回調(diào)捞高。

[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

2.5 Mode 中的 CFRunLoopObserverRef

RunLoop 的狀態(tài)UI界面的刷新(BeforeWaiting)渣锦、autorelease pool都是通過 CFRunLoopObserverRef 處理的硝岗。

CFRunLoopObserverRef是觀察者,主要用來監(jiān)聽RunLoop 的狀態(tài)袋毙,主要有以下幾種狀態(tài)型檀。

  • kCFRunLoopEntry : 即將進(jìn)入RunLoop
  • kCFRunLoopBeforeTimers :即將處理Timer
  • kCFRunLoopBeforeSources:即將處理Source
  • kCFRunLoopBeforeWaiting :即將進(jìn)入休眠
  • kCFRunLoopAfterWaiting:即將從休眠中喚醒
  • kCFRunLoopExit :即將從RunLoop中退出
  • kCFRunLoopAllActivities:監(jiān)聽全部狀態(tài)改變

可以通過以下代碼驗(yàn)證RunLoop的幾種狀態(tài):

    // 創(chuàng)建觀察者
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        NSLog(@"監(jiān)聽到RunLoop發(fā)生改變---%zd",activity);
    });
    // 添加觀察者到當(dāng)前RunLoop中
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
    // 釋放observer
    CFRelease(observer);

除了用于監(jiān)聽 RunLoop 的狀態(tài)之外,UI刷新(BeforeWaiting)听盖、Autorelease pool(BeforeWaiting)都與其有關(guān)系胀溺。

三、RunLoop 相關(guān)邏輯流程

RunLoop 邏輯流程

上圖是筆者從網(wǎng)上找到的一張 RunLoop 運(yùn)行的相關(guān)流程邏輯圖皆看。具體來說主要執(zhí)行邏輯是這樣的:

  • 1仓坞、通知觀察者 RunLoop 已經(jīng)啟動。
  • 2悬蔽、通知觀察者即將要開始定時器扯躺。
  • 3、通知觀察者任何即將啟動的非基于端口的源蝎困。
  • 4录语、啟動任何準(zhǔn)備好的非基于端口的源(Source0)。
  • 5禾乘、如果基于端口的源(Source1)準(zhǔn)備好并處于等待狀態(tài)澎埠,進(jìn)入步驟9。
  • 6始藕、通知觀察者線程進(jìn)入休眠狀態(tài)蒲稳。
  • 7氮趋、將線程置于休眠狀態(tài),知道下面的任一事件發(fā)生才喚醒線程江耀。
    . 某一事件到達(dá)基于端口的源
    . 定時器啟動剩胁。
    . RunLoop 設(shè)置的時間已經(jīng)超時。
    . RunLoop 被喚醒祥国。
  • 8昵观、通知觀察者線程將被喚醒。
  • 9舌稀、處理未處理的事件啊犬。
    .如果用戶定義的定時器啟動,處理定時器事件并重啟RunLoop壁查。進(jìn)入步驟2觉至。
    .如果輸入源啟動,傳遞相應(yīng)的消息睡腿。
    .如果RunLoop被顯示喚醒而且時間還沒超時语御,重啟RunLoop。進(jìn)入步驟2
  • 10席怪、通知觀察者RunLoop結(jié)束沃暗。

四、RunLoop 休眠實(shí)現(xiàn)原理

首先有一點(diǎn)肯定的是何恶,RunLoop 的休眠不是一個類似 while(1).... 一直在循環(huán)的代碼孽锥,因?yàn)檫@種屬于線程阻塞,是需要消耗資源的细层。 一般休眠的實(shí)現(xiàn)和操作系統(tǒng)層面(內(nèi)核層面)有關(guān)系惜辑。API 一般分為兩種內(nèi)核API和應(yīng)用層面API。應(yīng)用層面的 API 一般是提供給開發(fā)者直接使用疫赎,內(nèi)核 API 開發(fā)者不能直接調(diào)用盛撑。RunLoop 休眠實(shí)現(xiàn)原理是用戶態(tài)低調(diào)用 mach_msg, 繼而轉(zhuǎn)去調(diào)用內(nèi)核態(tài)的 mach_msg,實(shí)現(xiàn)真正的休眠捧搞。當(dāng)有消息需要處理時抵卫,內(nèi)核態(tài) mach_msg 會喚醒用戶態(tài),讓線程去處理任務(wù)胎撇。


五介粘、RunLoop 實(shí)際應(yīng)用

4.1 線程保活

借助RunLoop可以實(shí)現(xiàn)線程蓖硎鳎活的功能姻采,關(guān)鍵是在于兩行代碼,具體請看如下代碼爵憎。

- (void)viewDidLoad {
    [super viewDidLoad];
    self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(runOne) object:nil];
    [self.thread start];
}
- (void) runOne{
    NSLog(@"----任務(wù)1-----");
    // 下面兩句代碼可以實(shí)現(xiàn)線程笨祝活
    [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] run];
    // 測試是否開啟了RunLoop婚瓜,如果開啟RunLoop,則來不了這里刑棵,因?yàn)镽unLoop開啟了循環(huán)巴刻。
    NSLog(@"未開啟RunLoop");
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    // 利用performSelector,在self.thread的線程中調(diào)用run2方法執(zhí)行任務(wù)
    [self performSelector:@selector(runTwo) onThread:self.thread withObject:nil waitUntilDone:NO];
}

- (void) runTwo{
    NSLog(@"----任務(wù)2------");
}

實(shí)現(xiàn)了上述代碼之后蛉签,每次點(diǎn)擊屏幕都會打印----任務(wù)2------,這說明子線程處于活躍狀態(tài)冈涧。如果Mode里沒有任何Source0/Source1/Timer/Observer,RunLoop會立馬退出正蛙,上述代碼中的 [NSPort port] 相當(dāng)于往 RunLoop 中添加 Source1。[[NSRunLoop currentRunLoop] run] 相當(dāng)于開啟了一個無限循環(huán)营曼,默認(rèn)是 defaultMode乒验,相應(yīng)的線程永遠(yuǎn)也不會釋放。即使調(diào)用CFRunLoopStop(CFRunLoopGetCurrent) 也只能停止其中的一次 [[NSRunLoop currentRunLoop] run]蒂阱,并不能持續(xù)有效锻全。

在一些分析AFNetworking源碼的文章中,也經(jīng)常會出現(xiàn)如下這些代碼录煤。其核心也是為了實(shí)現(xiàn)線程后臺常駐鳄厌。

+ (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;
}

當(dāng)后臺線程執(zhí)行任務(wù)時,通過 performSelector:onThread:..方法將任務(wù)放在后臺線程的 RunLoop 中妈踊。正常來說了嚎,一個線程執(zhí)行完任務(wù)后就退出了。開啟runloop是為了防止線程退出廊营。一方面避免每次請求都要創(chuàng)建新的線程歪泳;另一方面,因?yàn)閏onnection 的請求是異步的露筒,如果不開啟runloop呐伞,線程執(zhí)行完代碼后不會等待網(wǎng)絡(luò)請求完的回調(diào)就退出了,這會導(dǎo)致網(wǎng)絡(luò)回調(diào)的代理方法不執(zhí)行慎式。

- (void)start {
    [self.lock lock];
    if ([self isCancelled]) {
        [self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
    } else if ([self isReady]) {
        self.state = AFOperationExecutingState;
        [self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
    }
    [self.lock unlock];
}

4.2 AutoreleasePool

應(yīng)用程序一旦啟動伶氢,主線程 RunLoop 里注冊了兩個 Observer。一個 Observer 監(jiān)聽即將進(jìn)入 Loop 事件瘪吏,回調(diào)內(nèi)會調(diào)用 _objc_autoreleasePoolPush() 創(chuàng)建自動釋放池癣防,并保證創(chuàng)建釋放池發(fā)生在其他所有回調(diào)之前。另外一個 Observer 監(jiān)視了兩個事件 (RunLoop即將進(jìn)入休眠和即將退出 RunLoop 事件) 掌眠,前者會調(diào)用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 釋放舊的池并創(chuàng)建新池劣砍;后者會調(diào)用 _objc_autoreleasePoolPop() 來釋放自動釋放池,并保證釋放自動釋放池事件發(fā)生在其它回調(diào)之后扇救。

4.3 卡頓監(jiān)測

所謂的卡頓一般是在主線程做了耗時操作刑枝,卡頓監(jiān)測的主要原理是在主線程的 RunLoop 中添加一個 observer香嗓。然后,創(chuàng)建一個持續(xù)的子線程專門用來監(jiān)控主線程的 RunLoop 狀態(tài)装畅。檢測是否一直處于 即將處理Source(kCFRunLoopBeforeSources) 狀態(tài)(一直處于忙碌狀態(tài)無法進(jìn)入休眠狀態(tài))靠娱、 是否一直處于即將從休眠中喚醒 (kCFRunLoopAfterWaiting)狀態(tài)(一直無法從休眠狀態(tài)喚醒去處理事件) 。如果花費(fèi)的時間大于某一個闕值掠兄,則認(rèn)為卡頓像云,此時可以輸出對應(yīng)的堆棧調(diào)用信息。觸發(fā)卡頓的時間閾值蚂夕,我們可以根據(jù) WatchDog 機(jī)制來設(shè)置迅诬。WatchDog 在不同狀態(tài)下設(shè)置的不同時間,如下所示:

  • 啟動(Launch):20s婿牍;
  • 恢復(fù)(Resume):10s侈贷;
  • 掛起(Suspend):10s;
  • 退出(Quit):6s等脂;
  • 后臺(Background):3min(在 iOS 7 之前俏蛮,每次申請 10min; 之后改為每次申請 3min上遥,可連續(xù)申請搏屑,最多申請到 10min)。

通過 WatchDog 設(shè)置的時間粉楚,我認(rèn)為可以把啟動的閾值設(shè)置為 10 秒辣恋,其他狀態(tài)則都默認(rèn)設(shè)置為 3 秒。其中的 3s 也可以理解為 3次 * 1s模软。

網(wǎng)上很多抓去卡頓堆椧值常或多或少存在問題,比較正確的解決方案可參考這篇文章
參考撵摆。

4.3 UI 刷新

在 iOS 和 macOS 中底靠,UI 刷新主要由主線程上的 RunLoop 管理,而 UI 刷新的具體執(zhí)行時機(jī)通常是在 RunLoop 的 beforeWaiting 階段特铝。這種設(shè)計(jì)的主要原因是為了確保 UI 更新的效率和響應(yīng)性暑中。

beforeWaiting 時機(jī)的優(yōu)勢

beforeWaiting 是 RunLoop 在即將進(jìn)入休眠之前的最后一個階段。這個時機(jī)特別適合 UI 刷新鲫剿,原因包括:

  • 避免不必要的重復(fù)刷新:UI 刷新操作是昂貴的操作鳄逾。如果 UI 刷新放在 RunLoop 的其他階段,可能會在同一個 RunLoop Cycle 中多次觸發(fā)刷新灵莲。而在 beforeWaiting 階段雕凹,由于 RunLoop 即將進(jìn)入休眠狀態(tài),這時觸發(fā) UI 刷新可以確保當(dāng)前所有的 UI 更新操作都已經(jīng)合并完成,不會浪費(fèi)資源進(jìn)行多次刷新枚抵。
  • 最大化響應(yīng)性:當(dāng) RunLoop 即將休眠時线欲,說明當(dāng)前沒有其他高優(yōu)先級的任務(wù)需要立即處理。這時觸發(fā) UI 刷新汽摹,能最大限度地利用主線程的空閑時間來更新 UI李丰,從而提升應(yīng)用的響應(yīng)性和用戶體驗(yàn)。
  • 避免 UI 更新被阻塞:如果 UI 刷新放在其他階段逼泣,例如定時器或輸入源階段趴泌,UI 刷新可能會被其他更高優(yōu)先級的事件(如用戶交互事件)阻塞。而放在 beforeWaiting 階段拉庶,則是確保所有輸入事件處理完畢后嗜憔,再進(jìn)行 UI 刷新,從而使 UI 刷新不被其他任務(wù)阻塞氏仗。

補(bǔ)充

是否可以自定義Model吉捶?

參考
可以自定義Model。
對于開發(fā)者而言經(jīng)常用到的Mode還有一個kCFRunLoopCommonModes(NSRunLoopCommonModes),其實(shí)這個并不是某種具體的Mode廓鞠,而是一種模式組合,在iOS系統(tǒng)中默認(rèn)包含了NSDefaultRunLoopMode和 UITrackingRunLoopMode(注意:并不是說Runloop會運(yùn)行在kCFRunLoopCommonModes這種模式下谣旁,而是相當(dāng)于分別注冊了 NSDefaultRunLoopMode和 UITrackingRunLoopMode床佳。當(dāng)然你也可以通過調(diào)用CFRunLoopAddCommonMode()方法將自定義Mode放到 kCFRunLoopCommonModes組合)。

我們常常還會碰到一些系統(tǒng)框架自定義Mode榄审,例如Foundation中NSConnectionReplyMode砌们。還有一些系統(tǒng)私有Mode,例如:GSEventReceiveRunLoopMode接受系統(tǒng)事件搁进,UIInitializationRunLoopMode App啟動過程中初始化Mode浪感。

在代碼中,可以通過名稱識別 mode饼问。Cocoa影兽、Cocoa Touch、Core Foundation 均提供了 default mode 和常用 mode莱革,也可以通過為 mode 名稱指定自定義字符串來創(chuàng)建自定義 mode峻堰。雖然 mode 名稱可以任意指定,但 mode 內(nèi)容不是任意的盅视,必須提供 input source捐名、timer、observer 中的一個或多個闹击。Mode 可用于過濾不需要的 source镶蹋。大部分情況下,使用系統(tǒng)定義的 default mode 即可。UIScrollView滑動時主線程會進(jìn)入U(xiǎn)ITrackingRunLoopMode贺归。 創(chuàng)建 input source 時淆两,可以將其分配給一種或多種 mode。mode 決定任一時刻監(jiān)控哪些輸入源牧氮。通常琼腔,在 default mode 運(yùn)行 run loop,但也可以指定自定義 mode踱葛。如果 input source 不在當(dāng)前監(jiān)控的 mode丹莲,其產(chǎn)生的事件將被保留,等 run loop 在該 mode 運(yùn)行時才傳遞尸诽。

source0和source1區(qū)別

參考

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末甥材,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子性含,更是在濱河造成了極大的恐慌洲赵,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件商蕴,死亡現(xiàn)場離奇詭異叠萍,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)绪商,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進(jìn)店門苛谷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人格郁,你說我怎么就攤上這事腹殿。” “怎么了例书?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵锣尉,是天一觀的道長。 經(jīng)常有香客問我决采,道長自沧,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任树瞭,我火速辦了婚禮暂幼,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘移迫。我一直安慰自己旺嬉,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布厨埋。 她就那樣靜靜地躺著邪媳,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上雨效,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天迅涮,我揣著相機(jī)與錄音,去河邊找鬼徽龟。 笑死叮姑,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的据悔。 我是一名探鬼主播传透,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼极颓!你這毒婦竟也來了朱盐?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤菠隆,失蹤者是張志新(化名)和其女友劉穎兵琳,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體骇径,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡躯肌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了破衔。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片清女。...
    茶點(diǎn)故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖运敢,靈堂內(nèi)的尸體忽然破棺而出校仑,到底是詐尸還是另有隱情忠售,我是刑警寧澤传惠,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站稻扬,受9級特大地震影響卦方,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜泰佳,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一盼砍、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧逝她,春花似錦浇坐、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春觉渴,著一層夾襖步出監(jiān)牢的瞬間介劫,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工案淋, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留座韵,地道東北人。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓踢京,卻偏偏與公主長得像誉碴,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子漱挚,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,037評論 2 355