前言:
這篇文章主要內容是介紹RunLoop的一些概念以及用法:使用RunLoop建立常駐執行緒、自定義輸入源進行執行緒通訊等。同時藉此機會希望能夠和大家一起討論RunLoop相關的知識,加深對RunLoop的理解。
一、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
的原始碼,這些內容在此並未列出,如有感興趣的小夥伴可以先行花時間去探索、學習,到時可以一起交流、討論。
原始碼地址:QiRunLoopDemo1
小編微信:可加並拉入《QiShare技術交流群》。
關注我們的途徑有:
QiShare(簡書)
QiShare(掘金)
QiShare(知乎)
QiShare(GitHub)
QiShare(CocoaChina)
QiShare(StackOverflow)
QiShare(微信公眾號)
推薦文章:
iOS 常用除錯方法:LLDB命令
iOS 常用除錯方法:斷點
iOS 常用除錯方法:靜態分析
iOS訊息轉發
iOS 自定義拖拽式控制元件:QiDragView
iOS 自定義卡片式控制元件:QiCardView
iOS Wireshark抓包
iOS Charles抓包
奇舞週刊