iOS runloop 學習筆記(一) - 官方文檔

先貼下 apple doc, 本文基本是對照 doc 的翻譯:
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html

RunLoops 是 thread 線程的底層基礎.它的本質是和它的意思一樣運行著的循環,更加確切的說是線程中的循環.它用來接受循環事件和安排線程的工作,并且在線程中沒有工作時,讓線程進入睡眠狀態.

RunLoops 并非完全自動管理的. 你可以在自己開辟的新thread 中使用runloop 來幫助你處理incoming 事件. iOS 中 Cocoa 和 CoreFoundation 框架中各有完整的一套關于 runloop 對象的操作api.在主線程中RunLoop 是系統創建的,在子線程中你必須手動去生成一個 runloop.

相關內容: NSRunLoop CFRunLoop

什么是 RunLoops

在新建 xcode 生產的工程中有如下代碼塊:

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

當程序啟動時,以上代碼會被調用,主線程也隨之開始運行,RunLoop 也會隨著啟動.
UIApplicationMain()方法里面完成了程序初始化,并設置程序的Delegate任務,而且隨之開啟主線程的 RunLoop,就開始接受事件處理.

RunLoop 是一個循環,在里面它接受線程的輸入,通過事件處理函數來處理事件.你的代碼中應該提供 while or for 循環來驅動 runloop.在你的循環中,用 runloop 對象驅動事件處理相關的內容,接受事件,并做響應的處理.

RunLoop 接受的事件源有兩種大類: 異步的input sources, 同步的 Timer sources. 這兩種事件的處理方法,系統所規定.

官方文檔中有如下貼圖,解釋了 runloop 的基本結構



從圖中可以看出,RunLoop 是線程中的一個循環,并且對接受到的事件進行處理.我們的代碼可以通過提供 while 或者 for 循環來驅動 RunLoop.在循環中,Run Loop 對象來負責事件處理的代碼(接受事件,并調用相應的事件處理方法).

RunLoop 從以下兩個不同的事件源中接受消息:

  • InputSources : 用來投遞異步消息,通常消息來自另外的線程或者程序.在接受到消息并調用指定的方法時,線程對應的 NSRunLoop 對象會通過執行 runUntilDate:方法來退出.
  • Timer Source: 用來投遞 timer 事件(Schedule 或者 Repeat)中的同步消息.在消息處理時,并不會退出 RunLoop.
  • RunLoop 除了處理以上兩種 Input Soruce,它也會在運行過程中生成不同的 notifications,標識 runloop 所處的狀態,因此可以給 RunLoop 注冊觀察者 Observer,以便監控 RunLoop 的運行過程,并在 RunLoop 進入某些狀態時候進行相應的操作, Apple 只提供了 Core Foundation 的 API來給 RunLoop 注冊觀察者Observer.

后面兩部分內容主要是: RunLoop 的組成, RunLoop的 modes, 以及 RunLoop 運行過程中產生的 notifications

RunLoopModes-- RunLoop 運行的模式

RunLoopMode 可以理解成為一個集合, 包括所有要監視的事件源(前面提到的兩種源)和要通知的 RunLoop 中注冊的觀察者.每次運行 RunLoop 時,都需要顯示或者隱式的指定其運行在哪一種 Mode(RunLoop 每次只能運行在一個 mode中).在設置 RunLoopMode 以后,你的 RunLoop 就會自動過濾和其他 Mode 相關的事件源,而只監視和當前設置 Mode 相關的源(以及通知相關的觀察者).大多數時候 RunLoop 都運行在系統定義的默認的模式上.

在代碼中,你可以通過mode 名稱區分不同的 mode. Cocoa & CoreFoundation 框架通過不同名稱(NSString,CFString)定義了缺省 mode 和一系列其他的 mode.你也可以使用不同的名稱,定義自己的 mode,然后在這個 mode 中添加一些 source 以及 observer.

使用這些 modes 可以從不想要的事件源中過濾事件.大多數情況下,我們都將 runloop 設置成default mode.

RunLoopMode 是基于事件的source源頭,而不是事件的type類型去區分的.比如你不能通過 RunLoopMode 去只選擇鼠標點擊事件或者鍵盤輸入事件.你可以使用 RunLoopMode 去監聽端口,暫停計時器或者或者改變添加或刪除一些 mode 中關注的 sources or observers.

Cocoa 和 CoreFoundation 為我們定義了默認和常用的 Mode.RunLoopMode 的名稱可以使用字符串來標識,我們也可以使用字符串指定一個 Mode 名稱來自定義 Model.

下面列出iOS 中已經定義的 RunLoopMode:

  • NSDefaultRunLoopMode,kCFRunLoopDefaultMode: 大多數工作中默認的運行方式。
  • NSConnectionReplyMode: 使用這個Mode去監聽NSConnection對象的狀態,我們很少需要自己使用這個Mode。
  • NSModalPanelRunLoopMode: 使用這個Mode在Model Panel情況下去區分事件(OS X開發中會遇到)。
  • UITrackingRunLoopMode: 使用這個Mode去跟蹤來自用戶交互的事件(比如UITableView上下滑動)。
  • GSEventReceiveRunLoopMode: 用來接受系統事件,內部的Run Loop Mode。
  • NSRunLoopCommonModes, kCFRunLoopCommomModes: 這是一個偽模式,其為一組run loop mode的集合。如果將Input source加入此模式,意味著關聯Input source到Common Modes中包含的所有模式下。在iOS系統中NSRunLoopCommonMode包含NSDefaultRunLoopMode、NSTaskDeathCheckMode、UITrackingRunLoopMode.同時,我們可以使用 CoreFoundation 中的 CFRunLoopAddCommomMode()函數,將自定義的 mode 加入其中。

注意 RunLoop 運行時只能以一種固定的 Mode運行,只會監控這個 Mode 下添加的 Timer source 和 Input source.如果這個 Mode下沒有添加時間源,RunLoop 就會立即返回.
RunLoop 不能運行在 NSRunLoopCommonModes,因為 NSRunLoopModes 是個 Mode 的集合,而不是一個具體的 Mode.我們可以在添加事件源的時候使用 NSRunLoopCommomModes,只要 RunLoop 運行在 NSRunLoopModes 中任何一個 Mode,這個事件源就會被觸發.

RunLoop 的事件源

Input soruces 將 event 異步的發送給 threads.而event 的 source 是基于input source 的類型.Input source 有兩種不同的種類: Port-Based SourcesCustom Input sources. Port-Based Sources監聽 Mach Port ,Custom Input Source類型 監控著 custom source 發出的 event.這兩種不同類型的 Input source 區別在于: Port-Based Sources由內核自動發送,custom sources 必須手動的從其他 thread 發出

當你創建一個 input soruce, 你需要將它放入到一個或者多個 runloop modes 中.如果一個 input source 不在當前的被監控的 mode 中, 那么這個 input source 產生的事件 Runloop 是不會收到的,除非 這個 input source 被放到了正確的 mode 中.

下文描述了幾種不同的 input sources.

Port-Based Sources

Cocoa 和 CoreFoundation 框架 對 port-based input soruces 相關的對象和函數提供了內置的支持.比如Cocoa 中,你永遠不必直接創建一個 input source,你可以直接通過方法創建一個 NSPort,然后直接將這個 port 對象加入到 runloop 中. NSPort 對象會負責自己創建和配置 input source.

在CoreFoundation 中,你必須手動創建 port 和與它對應的 runloop source.使用 CFMachPortRef,CFMessagePortRef,CFSocketRef 去創建合適的對象.

具體的創建 port-based soruce 的實例見:

https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html#//apple_ref/doc/uid/10000057i-CH16-131281

Custom Input Source

我們可以使用 CoreFoundation 中的 CFRunLoopSourceRef 類型相關的函數來創建 custom input sources. 你可以使用幾個 callback 函數來配置 custom input source. CoreFoundation 會在幾個不同的事件觸發的節點來調用注冊的 callback 函數,具體的節點: configuration, handle incoming event以及銷毀 source.

同時,你必須定義指定當 event 到來時,custom source的行為,以及事件的傳遞的形式.

具體的調用實例:
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html#//apple_ref/doc/uid/10000057i-CH16-SW3

Cocoa Perform Selector Sources

Cocoa 框架為我們定義了一些 Custom Input Sources, 允許我們在任何thread中執行Selector方法.

當在一個目標 thread 中 perform selector,需要該 thread 的 runloop 必須是運行狀態的.這意味著,如果這個 thread 是你創建的,那么 selector 的內容直到 runloop 啟動以后才會執行,而之前通過 perform selector 加入的就會被加入到一個queue中等待執行.和Port-Based Sources一樣,這些 selector 的請求會在目標線程中加入到 queue 中序列化,以減緩線程中多個方法執行帶來的同步問題.
Port-Based Sources不一樣的是,一個 selector 方法執行完成以后會自動從當前RunLoop 中移除.

下面是 NSObject 中定義的 perform selector 的方法

//在主線程的 RunLoop 下執行指定的 @selector 方法
performSelectorOnMainThread:withObject:waitUntilDone:
performSelectorOnMainThread:withObject:waitUntilDone:modes:

//在當前線程的 RunLoop 下延遲加載指定的 @selector 方法
performSelector:onThread:withObject:waitUntilDone:
performSelector:onThread:withObject:waitUntilDone:modes:

//在當前線程的 RunLoop 下延遲加載執行的 @selector 方法
performSelector:withObject:afterDelay:
performSelector:withObject:afterDelay:inModes:

//取消當前線程的調用
cancelPreviousPerformRequestsWithTarget:
cancelPreviousPerformRequestsWithTarget:selector:object:

Timer sources

Timer sources在預設的時間點synchronously同步的給 thread發送 evet.Timer 是thread 通知自己做某事的一種方式. 例如, search filed 可以使用一個 timer 來每隔一段時間自動去查詢用戶輸入的信息.

盡管, Timer sources 會生成 time-based notifications, 但是 timer 并非完全真正的基于時間點的.同 input source 一樣, Timer 也是和特定的RunLoopMode 有關的.如果Timer并不在當前 RunLoop 的 mode 的監控范圍內,那么 Timer 就不會被觸發,直到 RunLoop 切換到 Timer 所在的 mode 中.相似的是,如果 Timer 在當前 mode 觸發了,但是 RunLoopMode 又被改變了,那么后面 Timer 就仍然不會被觸發.

我們可以設置 Timer 的觸發方式是once 一次性 或者 repeat 重復. 一個 repeating timer 重復觸發依賴的時間是基于上一次的 fire time 的,并非實際的時間(有可能中間 runloopmode 有改變).例如, 一個 timer 設定的觸發時間是每隔5s 觸發一次,那么每一次激活都是前一次再加5s,即使前一次 fire 時間有 delay.即使有一次 fire time 延遲了很久,可能是13s,錯過了兩次 fire time,那么后面仍然只 fire 一次,在timer 這次 fire 過后, timer 又重新規劃下次 fire 時間在5s 后.

Cocoa 和 CoreFoundation 中 NSTimer,CFRunLoopTimer 提供了相關方法來設置 Timer sources.需要注意的是除了scheduledTimerWithTimeInterval開頭的方法創建的 Timer 都需要手動添加到當前RunLoop中.(scheduledTimerWithTimeInterval 創建的 timer 會自動以 default mode 加載到當前 RunLoop 中)

Timer 在選擇使用一次后,在執行完成時,會從 RunLoop 中移除.選擇循環時候,會一直保存在當前 RunLoop 中,直到 invalidated 方法執行.

具體使用 timer 的實例:
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html#//apple_ref/doc/uid/10000057i-CH16-SW6

RunLoop Observers, RunLoop 運行過程中的狀態觀察者

上文中提到的激活 RunLoop 的 input source 事件源, 他們會產生 同步(synchronous "timer")或者異步(asynchronous custom source等),與之對應, runloop 的 observers 將會在 runloop 運行到某個狀態時候自動觸發. 你可以在 runloop 即將處理某個事件時使用observers 進行某項操作,或者使用 observers 對 runloop 進入睡眠時候去控制 thread. 簡而言之, RunLoop Observer 則在 RunLoop 本身進入某個狀態時候得到通知

你可以創建一個 Observer 對 RunLoop 的如下狀態進行觀察:

  • RunLoop 進入時候
  • RunLoop 將要處理一個 Timer 時候
  • RunLoop 將要處理一個 Input Source時候
  • RunLoop 將要進入睡眠的時候
  • RunLoop 將要被喚醒的時候,在喚醒它的事件被處理之前
  • RunLoop 停止的時候

你可以用 CoreFoundation 的 API 給 RunLoop 添加 observers.使用CFRunLoopObserverRef 創建一個 runloop observer 的實例. 在創建實例時候, 給 observer 配置 callbacks 來跟蹤 runloop的狀態.

與 timer 類似, runloop observers 可以被聲明成 once 一次 或者 repeat 重復監控. 一次性的 observer 會在它被觸發,完成相關操作就從 runloop 中移除監控, repeat 類型還會繼續監聽. 當你創建一個 observers 你就應該指定是once 還是 repeat.

代碼實例:
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html#//apple_ref/doc/uid/10000057i-CH16-SW18

RunLoop 事件的序列

每次, RunLoop 會操作輸入的 events(各種 input source 產生的) 并產生 notifications(給 observers).
RunLoop 的處理 events 并且發出 notifications 的具體的順序如下:
1> 發 notification,通知 Observer,當 RunLoop 開始進入循環的時候
2> 發 notification,通知 Observer, timers 即將被觸發(處理 timers 的event)
3> 發 notification,通知 Observer,有其他的非 Port-Based Input Source 即將被觸發(non-port-based input source 的 event)
4> 啟動非 Port-Based Input Soruce 的事件源的處理函數
5> 如果基于Port的Input Source事件源即將觸發時,立即處理該事件,并跳轉到9
6> 發 notification,通知 Observer,當前 thread 即將進入睡眠狀態
7> 使線程進入睡眠狀態直到有以下事件發生:
1.Port-Based Input Source event
2.Timer fires
3.RunLoop 設置的時間超時
4.RunLoop 被代碼顯示喚醒
8> 發 notification,通知 Observer, thread將要被喚醒
9> 處理被觸發的事件
1.如果是用戶自定義的Timer觸發的,處理Timer事件的函數后,重啟Run Loop.直接進入步驟2
2.如果是 input source 事件源有事觸發 event,直接傳遞這個消息
3.如果runloop 是顯示被喚醒并且沒有超時,重啟 RunLoop. 直接進入步驟2
10> 發 notification,通知 Observer,Run Loop已經退出

由于 timer 和 input source導致 runloop 給 observer 發送 notification 是在這些事件之前的, 因此可能 notification 發出以后到事件真正的發生中間會有一小段間隔時間, 可以通過 awake-from-sleep 的 notification 來修復確切的時間.

因為 timers 和其他與時間片相關的事件會在runloop 運行時候傳遞, 有時候會使得循環傳遞這些事件失靈.典型例子就是, 你在進入 runloop 時候,監聽了鼠標的移動路線,然后不斷在 app 中請求一些事件.因為你的代碼中已經抓住這些事件,并直接處理了,而不是讓 app 正常的去 dispatch事件,活動著的 timers 講將不能被fire,直到你的鼠標追蹤事件停止,并把控制權交給 app.

RunLoop 可以使用 runLoop 對象顯示的喚醒. 其他的事件也能夠喚醒 runloop.例如,給 runloop 添加一個 non-port-based input source,就可以喚醒它,而不必等到其他的事件發生.

何時使用 RunLoop

我們應該只在創建輔助線程的時候,才顯示的運行一個 RunLoop.iOS app 會在應用啟動的時候幫我 run 一個 runloop,而我們自己新建的輔助線程不會.
對于輔助線程,我們仍然需要判斷是否需要啟動一個 RunLoop.比如我們使用一個線程去處理一個預先定義的長時間的任務,我們應該避免啟動 RunLoop.下面是官方document 提供的使用 RunLoop 的幾個場景:

  • 需要使用 Port-Based Input Source或者 Custom InputSource 和其他thread通訊時
  • 需要在線程中使用 Timer
  • 需要在線程中使用上文中提到的selector相關的方法
  • 需要讓線程周期性的執行某些工作

如果你選擇使用 RunLoop, runloop 的設置和啟動是比較直觀的. 同時,你需要實現什么情況下從輔助線程中退出 runloop, 最好不要直接關閉線程,而是先退出 runloop.

如何創建和設置 runloop.代碼:
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html#//apple_ref/doc/uid/10000057i-CH16-SW5

使用 RunLoop 對象

RunLoop 對系為增加 input source,timers,添加 observers 提供了主要的接口.每一個 thread 都有且僅有一個 runloop.Cocoa 中使用 NSRunLoop, CoreFoundation 中使用 CFRunLoopRef.

從線程中獲取 RunLoop 對象

為了從當前 thread 中獲取runloop 對象,具體步驟如下:

  • 在 Cocoa中, 使用 [NSRunLoop currentRunLoop] ,就會返回當前線程的 runLoop 對象.
  • CoreFoundation 中使用 CFRunLoopRef.

CFRunLoopRef和 NSRunLoop 可以轉化, NSRunLoop 使用getCFRunLoop方法就可以得到 CFRunLoopRef 對象

配置 RunLoop 對象

在輔助線程啟動 runloop 之前,你必須至少在其中添加一個 input source 或者 timer.如果一個 runloop 中沒有一個事件源sources, runloop 會在你啟動它以后立即退出.

在添加了 source 以后,你可以給 runloop 添加 observers 來監測 runloop 的不同的執行的狀態.為了加入 observer, 你應該創建一個 CFRunLoopObserverRef,使用 CFRunLoopAddObserver 函數添加 observer 到你的 runloop.

下面代碼塊顯示了, 如何給 RunLoop 添加一個 observer .

- (void)threadMain
{
    // The application uses garbage collection, so no autorelease pool is needed.
    NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];
 
    // Create a run loop observer and attach it to the run loop.
    CFRunLoopObserverContext  context = {0, self, NULL, NULL, NULL};
    CFRunLoopObserverRef    observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
            kCFRunLoopAllActivities, YES, 0, &myRunLoopObserver, &context);
 
    if (observer)
    {
        CFRunLoopRef    cfLoop = [myRunLoop getCFRunLoop];
        CFRunLoopAddObserver(cfLoop, observer, kCFRunLoopDefaultMode);
    }
 
    // Create and schedule the timer.
    [NSTimer scheduledTimerWithTimeInterval:0.1 target:self
                selector:@selector(doFireTimer:) userInfo:nil repeats:YES];
 
    NSInteger    loopCount = 10;
    do
    {
        // Run the run loop 10 times to let the timer fire.
        [myRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
        loopCount--;
    }
    while (loopCount);
}

啟動 RunLoop

在輔助線程中,啟動一個 runloop 是必須的.runloop 中必須有一個 input source 或者 timer事件源.如果 runloop 啟動時候,內部沒有監聽任何一個 input source 或者 timer, runloop 會立即 exit.

有以下幾種啟動 RunLoop 的方法:

  • 沒有設置特定的條件啟動
  • 設置一個時間限制
  • 在一個特定的 mode

最簡單的是一個無條件的啟動 runloop,但是這也是最差的選擇.如果沒有設置任何條件,就會將 runloop 所在的 thread 進行永久的事件循環.你可以增加或者減少 input sources, timers,但是只有一種方法能夠 kill 掉它.同時這種方式沒辦法讓 runloop 在自定義的 mode 中運行.

替代無條件進入run loop更好的辦法是用預設超時時間來運行runloop,這樣runloop運作直到某一事件到達或者規定的時間已經到期。如果是事件到達,消息會被傳遞給相應的處理程序來處理,然后runloop退出。你可以重新啟動runloop來等待下一事件。如果是規定時間到期了,你只需簡單的重啟runloop或使用此段時間來做任何的其他工作。

除了超時機制,你也可以使用特定的模式來運行你的runloop。模式和超時不是互斥的,他們可以在啟動runloop的時候同時使用。模式限制了可以傳遞事件給run loop的輸入源的類型,這在”Run Loop模式”部分介紹。

描述了線程的主要例程的架構。本示例的關鍵是說明了runloop的基本結構。本質上講你添加自己的輸入源或定時器到runloop里面,然后重復的調用一個程序來啟動runloop。每次runloop返回的時候,你需要檢查是否有使線程退出的條件成立。示例中使用了Core Foundation的run loop例程,以便可以檢查返回結果從而確定run loop為何退出。若是在Cocoa程序,你也可以使用NSRunLoop 的方法運行run loop,無需檢查返回值。

- (void)threadMain
{
    // The application uses garbage collection, so no autorelease pool is needed.
    NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];
 
    // Create a run loop observer and attach it to the run loop.
    CFRunLoopObserverContext  context = {0, self, NULL, NULL, NULL};
    CFRunLoopObserverRef    observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
            kCFRunLoopAllActivities, YES, 0, &myRunLoopObserver, &context);
 
    if (observer)
    {
        CFRunLoopRef    cfLoop = [myRunLoop getCFRunLoop];
        CFRunLoopAddObserver(cfLoop, observer, kCFRunLoopDefaultMode);
    }
 
    // Create and schedule the timer.
    [NSTimer scheduledTimerWithTimeInterval:0.1 target:self
                selector:@selector(doFireTimer:) userInfo:nil repeats:YES];
 
    NSInteger    loopCount = 10;
    do
    {
        // Run the run loop 10 times to let the timer fire.
        [myRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
        loopCount--;
    }
    while (loopCount);
}

可以遞歸的運行run loop。換句話說你可以使用CFRunLoopRun,CFRunLoopRunInMode或者任一NSRunLoop的方法在輸入源或定時器的處理程序里面啟動run loop。這樣做的話,你可以使用任何模式啟動嵌套的run loop,包括被外層run loop使用的模式。

退出 RunLoop

有兩種方法可以讓 runloop 退出:

  • 給 runloop 設置超時事件
  • 通知 runloop 停止
    如果可以配置的話,推薦使用第一種方法。指定一個超時時間可以使run loop退出前完成所有正常操作,包括發送消息給run loop觀察者。

使用CFRunLoopStop來顯式的停止runloop和使用超時時間產生的結果相似。Runloop把所有剩余的通知發送出去再退出。與設置超時的不同的是你可以在無條件啟動的run loop里面使用該技術。

盡管移除runloop的輸入源和定時器也可能導致run loop退出,但這并不是可靠的退出run loop的方法。一些系統例程會添加輸入源到runloop里面來處理所需事件。因為你的代碼未必會考慮到這些輸入源,這樣可能導致你無法沒從系統例程中移除它們,從而導致退出runloop。

線程安全和 RunLoop 對象

線程是否安全取決于你使用那些API來操縱你的runloop。CoreFoundation 中的函數通常是線程安全的,可以被任意線程調用。但是如果你修改了runloop的配置然后需要執行某些操作,任何時候你最好還是在run loop所屬的線程執行這些操作。

至于Cocoa的NSRunLoop類則不像CoreFoundation具有與生俱來的線程安全性。如果你想使用NSRunLoop類來修改你的runloop,你應用在runloop所屬的線程里面完成這些操作。給屬于不同線程的runloop添加輸入源和定時器有可能導致你的代碼崩潰或產生不可預知的行為。(不要在當前線程操作其他線程的 runloop)

配置 RunLoop sources

以下部分列舉了在Cocoa和Core Foundation里面如何設置不同類型的輸入源的例子

自定義的 input sources

創建自定義的輸入源包括定義以下內容:

  • 輸入源需要處理的信息
  • 使感興趣的客戶端(可理解為其他線程)知道如何和輸入源交互的調度程序
  • 處理其他任何客戶端(可理解為其他線程)發送請求的程序
  • 使輸入源失效的取消程序

由于你自己創建輸入源來處理自定義消息,實際配置選是靈活配置的。調度程序,處理程序和取消程序都是你創建自定義輸入源時最關鍵的例程。然而輸入源其他的大部分行為都發生在這些例程的外部。比如,由你決定數據傳輸到輸入源的機制,還有輸入源和其他線程的通信機制也是由你決定。

ps: custom 源很少用...具體見
http://www.dreamingwish.com/article/ios-multithread-program-runloop-the.html

配置Timer source

為了創建一個定時源,你所需要做只是創建一個定時器對象并把它調度到你的runloop。Cocoa程序中使用NSTimer類來創建一個新的定時器對象,而Core Foundation中使用CFRunLoopTimerRef不透明類型。本質上,NSTimer類是CoreFoundation的簡單擴展,它提供了便利的特征,例如能使用相同的方法創建和調配定時器。
Cocoa中可以使用以下NSTimer類方法來創建并調配一個定時器:

scheduledTimerWithTimeInterval:target:selector:userInfo:repeats:
 
scheduledTimerWithTimeInterval:invocation:repeats:

上述方法創建了定時器并以默認模式把它們添加到當前線程的run loop。你可以手工的創建NSTimer對象,并通過NSRunLoop的addTimer:forMode:把它添加到run loop。兩種方法都做了相同的事,區別在于你對定時器配置的控制權。例如,如果你手工創建定時器并把它添加到run loop,你可以選擇要添加的模式而不使用默認模式。下面的嗲嗎顯示了如何使用這這兩種方法創建定時器。第一個定時器在初始化后1秒開始運行,此后每隔0.1秒運行。第二個定時器則在初始化后0.2秒開始運行,此后每隔0.2秒運行。

NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];
 
// Create and schedule the first timer.
NSDate* futureDate = [NSDate dateWithTimeIntervalSinceNow:1.0];
NSTimer* myTimer = [[NSTimer alloc] initWithFireDate:futureDate
                        interval:0.1
                        target:self
                        selector:@selector(myDoFireTimer1:)
                        userInfo:nil
                        repeats:YES];
[myRunLoop addTimer:myTimer forMode:NSDefaultRunLoopMode];
 
// Create and schedule the second timer.
[NSTimer scheduledTimerWithTimeInterval:0.2
                        target:self
                        selector:@selector(myDoFireTimer2:)
                        userInfo:nil
                        repeats:YES];

下面的代碼顯示了使用Core Foundation函數來配置定時器的代碼。盡管這個例子中并沒有把任何用戶定義的信息作為上下文結構,但是你可以使用這個上下文結構傳遞任何你想傳遞的信息給定時器。關于該上下文結構的內容的詳細信息,參閱CFRunLoopTimer Reference。

CFRunLoopRef runLoop = CFRunLoopGetCurrent();
CFRunLoopTimerContext context = {0, NULL, NULL, NULL, NULL};
CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault, 0.1, 0.3, 0, 0,
                                        &myCFTimerCallback, &context);
 
CFRunLoopAddTimer(runLoop, timer, kCFRunLoopCommonModes);

配置基于 port-based source

Cocoa和Core Foundation都提供了基于端口的對象用于線程或進程間的通信。以下部分顯示如何使用幾種不同類型的端口對象建立端口通信。

配置NSMachPort對象

為了和NSMachPort對象建立穩定的本地連接,你需要創建端口對象并將之加入相應的線程的run loop。當運行輔助線程的時候,你傳遞端口對象到線程的主體入口點。輔助線程可以使用相同的端口對象將消息返回給原線程。
ps: 實際進程間通信用的比較少,AFNetworking 2.x里面有使用.防止 runloop 停止,在啟動 runloop 之前就添加了一個 NSPort source.

參考文檔:
https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html
http://chun.tips/blog/2014/10/20/zou-jin-run-loopde-shi-jie-%5B%3F%5D-:shi-yao-shi-run-loop%3F/
http://www.dreamingwish.com/frontui/article/default/ios-multithread-program-runloop-the.html
https://github.com/wuyunfeng/LightWeightRunLoop
https://github.com/yechunjun/RunLoopDemo
http://blog.ibireme.com/2015/05/18/runloop/

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,663評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,125評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,506評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,614評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,402評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,934評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,021評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,168評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,690評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,596評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,784評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,288評論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,027評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,404評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,662評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,398評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,743評論 2 370

推薦閱讀更多精彩內容

  • 什么是Run Loops RunLoops是與線程相關聯的基礎部分,一個Run Loop就是事件處理循環,他是用來...
    傻傻小蘿卜閱讀 979評論 0 5
  • 這是一篇對Run Loop開發文檔《Threading Program Guide:Run Loops》的翻譯,來...
    鴻雁長飛光不度閱讀 3,660評論 3 29
  • 接RunLoop深度探究(四) 我的博客鏈接:http://superyang.gitcafe.io/blog/2...
    superYang0033閱讀 1,105評論 1 5
  • Run Loops 下面會用一些陌生的或者容易讓人混淆的字符,我們先來統一概念再繼續,這樣能夠讓你更加愉快的閱讀:...
    董二千閱讀 594評論 0 1
  • 邊灣在酒泉往北十幾公里處,它的東邊是酒泉的懷茂鄉,西邊是嘉峪關的新城鄉,北邊連著巴丹吉林沙漠。 懷茂鄉地下水...
    馬少軍閱讀 678評論 0 4