iOS RunLoop(一)
RunLoop
是與執行緒相關的基礎架構中的一部分,它是一個處理事件的迴圈(執行緒進入這個迴圈,執行事件處理程式來響應傳入的事件), RunLoop
的目的是當有事件需要處理時,執行緒是活躍的、忙碌的,當沒有事件後,執行緒進入休眠。
RunLoop結構以及事件來源:

一個 RunLoop
包含若干個 Mode
,每個 Mode
包含若干個 Source
/ Timer
/ Observer
/ Port
。當啟動一個 RunLoop
時會先指定一個 Mode
,檢查指定 Mode
是否存在以及 Mode
中是否含有 Source
和 Timer
,如果 Mode
不存在或者 Mode
中無 Source
和 Timer
,認為該 Mode
是一個空的 Mode
, RunLoop
就直接退出。

Input Source:
-
Port-Based Sources:監聽
App
的Mach Port
,由核心發出訊號,輸入源收到訊號後,執行相關的例程。 -
Custom Input Sources:監聽自定義的輸入源,需要在其它執行緒手動傳送訊號,輸入源收到訊號後,執行相關的例程。
-
Cocoa Perform Selector Sources:
Cocoa
中自定義的輸入源,目的是在不同執行緒中執行任務,同一執行緒中的任務是順序執行的,當任務執行完成後系統會自動移除這個源。( 注意:在目標執行緒中執行任務時,這個目標執行緒必須有活躍的RunLoop )
Timer Source:
時間源會在預設的時間同步傳遞事件給對應的執行緒,計時器是執行緒通知自己做某事的一種方式。
計時器並不是真正的實時的,當計時器未處於 RunLoop
當前監聽的 Mode
,那麼計時器是不會計時排程任務的,只有 RunLoop
當前監聽的 Mode
是計時器關聯的 Mode
時,計時器才會開始執行任務,例如: NSTimer
新增至主執行緒 RunLoop
的 DefaultMode
中,此時滑動 TableView
/ ScrollView
時, RunLoop
會切換至 TrackMode
,計時器是不會排程任務的。
如果 RunLoop
在執行一個例程時,計時器觸發了,那麼計時器會等待 RunLoop
將該例程執行完成,在下一次的迴圈中處理。在 RunLoop
未執行情況下,計時器永遠不會觸發任務。
二、RunLoop怎麼使用?
應用啟動時,會自動在主執行緒上設定執行 RunLoop
,所以不需要在主執行緒上顯示的啟動 RunLoop
,無需呼叫 [[NSRunLoop currentRunLoop] runUntilDate:]
這些方法。那麼如果我們顯示的在主執行緒中呼叫 RunLoop
的 run
方法會出現什麼結果呢?通過 Demo
中顯示,主執行緒中顯示啟動 RunLoop
會影響當前事件處理,但是由於 RunLoop
並沒有停止,所以其他事件能夠正常接收和處理。


而子執行緒也不併是必須要設定執行 RunLoop
才能執行任務,比如說只是簡單在子執行緒中處理個耗時任務等,如下場景是需要啟動 RunLoop
的:
- 使用
NSPort
或者自定義輸入源與其它執行緒通訊。 - 線上程上使用計時器。
- 在一個
Cocoa
應用中使用performSelector
相關方法。 - 使執行緒常駐,在該執行緒定期執行任務。
正如前言中所說,本文主要說明執行緒常駐和自定義輸入源執行緒通訊。
執行緒常駐:
方式一:無條件的啟動 RunLoop
是最簡單的選擇,但它也是最不可取的選擇,它會將執行緒置於永久迴圈中,這樣幾乎無法控制 RunLoop
本身,雖然可以新增和刪除輸入源和計時器,但停止 RunLoop
的唯一方法是殺死 RunLoop
。(以上內容是通過Google翻譯的官網內容可能理解有些偏差屆時還望指正,事實上我在做實驗的過程中,發現使用 NSThread
的 cancel
方法是無法停止 RunLoop
的, cancel
方法是更改執行緒的取消狀態,指示它應該退出。在當前執行緒下執行 [NSThread exit]
方法,退出了該執行緒,但 demo
中的 LongLifeThreadViewController
仍然未被釋放)

方式二:啟動 RunLoop
時設定時限, RunLoop
將一直執行直到事件到達或分配的時間到期。如果事件到達,則將該事件分派給處理程式進行處理,然後退出此次 RunLoop
。可以通過重新啟動 RunLoop
處理下一個事件。同樣如果分配的時間到期,也可以重新啟動 RunLoop
來處理。這種方式可以指定 RunLoopMode
,官網力薦。

自定義輸入源執行緒通訊:
定義輸入源:
RunLoop
個人感覺可以根據個人需求決定是否實現第3、4兩條內容。(注意定義輸入源只能通過CoreFoundation提供的對應API實現,其中的回撥例程由C語言實現)


在 RunLoop
上安裝輸入源:如果實現了上述的第3條內容時,將自定義的輸入源新增到 RunLoop
時,就會回撥輸入源對應的 schedule
實現例程。

向輸入源傳送訊號:輸入源在接收到訊號後,會執行對應的 perform
例程, perform
例程就是對應事件處理程式。(注意如果執行緒處於休眠狀態,要喚醒執行緒,否則該事件無法被處理。


結語:
關於 RunLoop
的內容還有很多,比如: RunLoopModes
、 RunLoopObserver
、 NSPort
、 NSTimer
等等,當然還有 RunLoop
的原始碼,這些內容在此並未列出,如有感興趣的小夥伴可以先行花時間去探索、學習,到時可以一起交流、討論。
小編微信:可加並拉入《QiShare技術交流群》。

關注我們的途徑有:
QiShare(微信公眾號)