iOS RunLoop(一)

QiShare發表於2019-03-21

級別: ★★☆☆☆
標籤:「iOS」「RunLoop」「執行緒常駐」
作者: 陳彬
審校: QiShare團隊

前言:
這篇文章主要內容是介紹RunLoop的一些概念以及用法:使用RunLoop建立常駐執行緒、自定義輸入源進行執行緒通訊等。同時藉此機會希望能夠和大家一起討論RunLoop相關的知識,加深對RunLoop的理解。

一、RunLoop是什麼?

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

RunLoop結構以及事件來源:

iOS RunLoop(一)

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

iOS RunLoop(一)

Input Source:
  • Port-Based Sources:監聽AppMach Port,由核心發出訊號,輸入源收到訊號後,執行相關的例程。

  • Custom Input Sources:監聽自定義的輸入源,需要在其它執行緒手動傳送訊號,輸入源收到訊號後,執行相關的例程。

  • Cocoa Perform Selector Sources:Cocoa中自定義的輸入源,目的是在不同執行緒中執行任務,同一執行緒中的任務是順序執行的,當任務執行完成後系統會自動移除這個源。(注意:在目標執行緒中執行任務時,這個目標執行緒必須有活躍的RunLoop

Timer Source:

時間源會在預設的時間同步傳遞事件給對應的執行緒,計時器是執行緒通知自己做某事的一種方式。

計時器並不是真正的實時的,當計時器未處於RunLoop當前監聽的Mode,那麼計時器是不會計時排程任務的,只有RunLoop當前監聽的Mode是計時器關聯的Mode時,計時器才會開始執行任務,例如:NSTimer新增至主執行緒RunLoopDefaultMode中,此時滑動TableView/ScrollView時,RunLoop會切換至TrackMode,計時器是不會排程任務的。

如果RunLoop在執行一個例程時,計時器觸發了,那麼計時器會等待RunLoop將該例程執行完成,在下一次的迴圈中處理。在RunLoop未執行情況下,計時器永遠不會觸發任務。

二、RunLoop怎麼使用?

應用啟動時,會自動在主執行緒上設定執行RunLoop,所以不需要在主執行緒上顯示的啟動RunLoop,無需呼叫[[NSRunLoop currentRunLoop] runUntilDate:]這些方法。那麼如果我們顯示的在主執行緒中呼叫RunLooprun方法會出現什麼結果呢?通過Demo中顯示,主執行緒中顯示啟動RunLoop會影響當前事件處理,但是由於RunLoop並沒有停止,所以其他事件能夠正常接收和處理。

iOS RunLoop(一)

iOS RunLoop(一)

而子執行緒也不併是必須要設定執行RunLoop才能執行任務,比如說只是簡單在子執行緒中處理個耗時任務等,如下場景是需要啟動RunLoop的:

  1. 使用NSPort或者自定義輸入源與其它執行緒通訊。
  2. 線上程上使用計時器。
  3. 在一個Cocoa應用中使用performSelector相關方法。
  4. 使執行緒常駐,在該執行緒定期執行任務。

正如前言中所說,本文主要說明執行緒常駐和自定義輸入源執行緒通訊。

執行緒常駐:

方式一:無條件的啟動RunLoop是最簡單的選擇,但它也是最不可取的選擇,它會將執行緒置於永久迴圈中,這樣幾乎無法控制RunLoop本身,雖然可以新增和刪除輸入源和計時器,但停止RunLoop的唯一方法是殺死RunLoop。(以上內容是通過Google翻譯的官網內容可能理解有些偏差屆時還望指正,事實上我在做實驗的過程中,發現使用NSThreadcancel方法是無法停止RunLoop的,cancel方法是更改執行緒的取消狀態,指示它應該退出。在當前執行緒下執行[NSThread exit]方法,退出了該執行緒,但demo中的LongLifeThreadViewController仍然未被釋放)

iOS RunLoop(一)

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

iOS RunLoop(一)

自定義輸入源執行緒通訊:

定義輸入源:

  1. 提供輸入源要處理的資訊。
  2. 接收到事件時的執行例程。
  3. 輸入源加到RunLoop時的執行例程。
  4. 輸入源失效時的執行例程。

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

iOS RunLoop(一)

iOS RunLoop(一)

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

iOS RunLoop(一)

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

iOS RunLoop(一)

iOS RunLoop(一)


結語:
關於RunLoop的內容還有很多,比如:RunLoopModesRunLoopObserverNSPortNSTimer等等,當然還有RunLoop的原始碼,這些內容在此並未列出,如有感興趣的小夥伴可以先行花時間去探索、學習,到時可以一起交流、討論。

原始碼地址:QiRunLoopDemo1


小編微信:可加並拉入《QiShare技術交流群》。

iOS RunLoop(一)

關注我們的途徑有:
QiShare(簡書)
QiShare(掘金)
QiShare(知乎)
QiShare(GitHub)
QiShare(CocoaChina)
QiShare(StackOverflow)
QiShare(微信公眾號)

推薦文章:
iOS 常用除錯方法:LLDB命令
iOS 常用除錯方法:斷點
iOS 常用除錯方法:靜態分析
iOS訊息轉發
iOS 自定義拖拽式控制元件:QiDragView
iOS 自定義卡片式控制元件:QiCardView
iOS Wireshark抓包
iOS Charles抓包
奇舞週刊

相關文章