iOS RunLoop

      網友投稿 804 2025-04-05

      為何要有 RunLoop


      理解 RunLoop 的首要前提就是要明白它為何會存在,它存在的目的是什么。

      相信大家都學過 C 語言,C 語言當中最簡單的程序就是 Hello World 了。整個程序就只打印出一句 Hello World,然后立馬結束運行。這也是大多數命令行程序的運行方法,一次執行一個任務,執行完后就退出。

      但是一個 iOS 應用一旦啟動起來后就會一直處于等待用戶事件(類似點擊或者觸摸事件)的狀態,除非用戶手動關閉它,不然它是不會退出的。那它是怎么做到的呢,這就是 RunLoop 起到的作用了。

      RunLoop 是一種事件驅動(Event Driven)模型,這種模型并非是 iOS 特有的。Android 中的 Looper 和 Windows SDK 開發中的消息循環機制都是屬于這種模型。

      RunLoop 何時啟動

      既然 RunLoop 可以讓應用保持等待接受用戶事件的狀態,那么它是何時啟動的呢。似乎我們在開發中并沒有寫過任何有關啟動 RunLoop 的代碼。

      答案是,我們并不需要手動啟動 RunLoop。

      當我們使用 Xcode 創建一個新的 iOS 應用時,它會自動幫我們生成 main 方法(這個方法在 main.m 文件中),而在 main 方法里面會調用?UIApplicationMain?方法。UIApplicationMain?會將 AppDelegate 設置為應用程序的代碼,同時會為主線程創建一個 RunLoop 并啟動。

      關于更詳細的 iOS 應用啟動流程,可以參考這篇文章。

      因此任何一個 iOS 應用一旦啟動了,就至少存在一個 RunLoop,并且這個 RunLoop 是屬于主線程的。之所以說屬于主線程,是因為 RunLoop 與線程的關系是一一對應的,關于這點,在后面會講到。

      RunLoop 對象

      其實事件驅動模型的本質就是一個死循環,然后在循環中不斷等待消息的到來,然后對其進行處理。因此,這種模型的關鍵點就在于:如何管理事件/消息,如何讓線程在沒有處理消息時休眠以避免占用過多的資源,并且在有消息到來時能夠立即進行響應。

      而上面提到的這些關鍵點就是 RunLoop 的工作。RunLoop 實際上就是一個對象,它為我們提供了一個進入事件循環的入口函數,線程執行這個函數后就會處于消息循環中,直到循環結束(比如接收到退出消息)。

      在 iOS 中提供了兩個 RunLoop 對象:NSRunLoop 與 CFRunLoopRef。

      其中,NSRunLoop 是基于 CFRunLoopRef 的簡單封裝。NSRunLoop 的 API 是非線程安全的,而 CFRunLoopRef 的 API 是線程安全的。更重要的一點是 CFRunLoopRef 的代碼是開源的。所謂源碼之前無秘密,能看到源碼意味著我們能徹底搞懂它是如何工作的。因此,如果需要對 RunLoop 進行更加精細的操作,建議使用?CFRunLoopRef?而非?NSRunLoop。

      RunLoop 與線程的關系

      查看下 NSRunLoop 與 CFRunLoopRef 的文檔,你會發現蘋果并沒有提供直接創建 RunLoop 的方法。它只提供了兩個自動獲取的函數:CFRunLoopGetMain()?和?CFRunLoopGetCurrent()。

      事實上,RunLoop 在其內部維護了一個全局的字典,這個字典以線程為鍵,以 RunLoop 為值,表明了線程與 RunLoop 是一一對應的。當我們用這兩個方法獲取 RunLoop 的時候,如果該線程的 RunLoop 不存在,則它會幫我們自動創建一個并添加到字典中。

      所以線程在剛創建時是沒有 RunLoop 的,并且如果我們不主動獲取,它就一直不會有。除了主線程,其它的線程我們都只能在其內部獲取 RunLoop。

      RunLoop 中的事件

      從剛剛開始我們就一直在說 RunLoop 可以讓 iOS 應用啟動后不退出,而處于接受事件的狀態。那么這里所謂的事件到底是什么呢。

      從上層來講,事件就是所謂的按鈕點擊,屏幕點擊,以及手勢識別等。

      但是,從 RunLoop 的角度來講,它只接受兩種事件,一種就是?Input Source,這種 Source 事件是異步的,它通常是來自于其它線程或者其它程序的消息(按鈕與屏幕點擊就屬于這一種事件)。另外一種是?Timer Source,這種 Source 我們就很熟悉了,就是我們平常開發中用到的計時器,這種 Source 事件是同步的。

      上述兩種事件在 RunLoop 對象中對應的類為?CFRunLoopSourceRef?和?CFRunLoopTimerRef。

      RunLoop 除了可以處理輸入事件外,還能在它的行為發生變化時產生通知。我們可以通過?CFRunLoopObserverRef?來為 RunLoop 添加觀察者并注冊回調,只要 RunLoop 的狀態發生變化,觀察者就能接受到這個變化并利用這些信息在線程中做進一步的處理。

      RunLoop 中可以觀察的點有如下幾個:

      typedef?CF_OPTIONS(CFOptionFlags,?CFRunLoopActivity)?{ ????kCFRunLoopEntry?????????=?(1UL?<

      上面的 Source/Timer/Observer 被統稱為?mode item,這就涉及到了 RunLoop 中的另一個概念?RunLoop Mode,它的類是?CFRunLoopModeRef。一個 RunLoop Mode 就是一系列?Input Source、Timer Source?以及?Observer?的集合。

      每次我們要啟動一個 RunLoop 的時候,都需要指定一個 Mode。當我們使用某個 Mode 啟動 RunLoop 后,在本次啟動的 RunLoop 中只會監聽與處理與該 Mode 相關聯的事件,類似地,只有以該 Mode 相關聯的觀察者會接到通知。這樣做的目的主要是為了分隔開不同組的 Source/Timer/Observer,讓其互不影響。

      RunLoop 每次啟動時只能指定一個 Mode,如果需要切換 Mode,需要退出 RunLoop 再重新指定一個 Mode 進入。蘋果公開提供的 Mode 有兩個: kCFRunLoopDefaultMode 和 UITrackingRunLoopMode。同時,這里還有一個特殊的概念叫?CommonModes:一個 Mode 可以將自己標記為?Common?屬性(通過將其 ModeName 添加到 RunLoop 的?commonModes?中)。每當 RunLoop 的內容發生變化時,RunLoop 都會自動將 _commonModeItems 里的 Source/Observer/Timer 同步到具有?Common?票房的所有 Mode 里。

      CommonModes 最典型的一個應用就是在讓我們可以在拖動 UIScrollView 的同時使定時器不要失效,具體的內容可以參考這篇文章中的?RunLoop 的 Mode?小節。

      RunLoop 中事件順序

      RunLoop 一旦啟動后,它就會開始處理各種等待事件,并向其觀察者發送通知。而 RunLoop 做這些事是有順序的,具體的順序如下:

      通知觀察者:即將進入 RunLoop

      通知觀察者:即將處理 Timer

      通知觀察者:即將處理所有非基于 Port 的 Input Source

      處理所有非基于 Port 的 Input Source

      如果有基于 Port 的 Input Source 可供處理,跳到步驟 9

      iOS RunLoop

      通知觀察者:線程即將休眠

      線程休眠,等待如下事件將喚醒:

      基于 Port 的事件到來

      計時器啟動

      RunLoop 超時

      RunLoop 被手動喚醒

      通知觀察者:線程被喚醒

      處理喚醒時收到的事件,并跳回 2

      通知觀察者:即將退出 RunLoop

      針對這個順序,也可以在直接在源碼中看到。

      實際上,RunLoop 內部就是一個 do-while 循環。當我們調用 CFRunLoopRun() 時,線程就會一直停留在這個循環里;直到超時或者被手動停止,該函數才會返回。

      source0,source1

      首先這個源事件分為兩種,一種是不基于端口的source0,一直是基于端口的source1。

      Source0 只包含了一個回調(函數指針),它并不能主動觸發事件。使用時,你需要先調用 CFRunLoopSourceSignal(source),將這個 Source 標記為待處理,然后手動調用 CFRunLoopWakeUp(runloop) 來喚醒 RunLoop,讓其處理這個事件。

      source0呢主要處理App內部事件、App自己負責管理(觸發),如UIEvent、CFSocket source1呢主要有Runloop和內核管理,Mach?port驅動,如CFMahPort、CFMessagePort ————引自孫源runLoop線下分享會視頻

      什么時候使用 RunLoop

      正如前面所講的,一個 iOS 應用在啟動時就會自動為主線程創建一個 RunLoop,因此我們大多數情況下都是不需要手動去創建 RunLoop。

      根據官方文檔,我們只有在下面提到的這些情況下才會去為子線程啟動一個 RunLoop:

      使用 port 或者自定義輸入源與其它線程進行交流時

      要在線程上使用計時器時

      要使用?performSelector...?類的方法時

      使用線程執行周期性的任務時

      當我們為子線程啟動 RunLoop 時,需要規劃好在什么情況下退出這個線程,而非讓它永遠地運行下去。手動停止線程并做好清理工作永遠比直接強制關閉線程來得好。

      小結

      作為開發者,雖然平常跟 RunLoop 直接進行接觸的機會不是很多,但是理解它對我們了解 iOS 應用的運行機制也會有很大的幫助。

      當然,本篇小結只總結了一些比較基礎的知識,旨在對 RunLoop 機制進行一下梳理,讓自己對其有更清晰的認識。關于更加詳細及深入的內容,可以參考下面的參考資料。

      參考資料

      深入理解RunLoop

      Friday Q&A 2010-01-01: NSRunLoop Internals

      Run Loops 官方文檔

      軟件開發云

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:WPS表格辦公—利用粘貼選項將表格轉換為圖片(圖片表格怎么轉換成wps里的表格)
      下一篇:Excel2010通過兩端對齊功能快速合并單元格中的文本內容(excel2010文本對齊方式)
      相關文章
      久久久亚洲欧洲日产国码农村| 国产精品xxxx国产喷水亚洲国产精品无码久久一区| 亚洲综合精品香蕉久久网97| 亚洲精品黄色视频在线观看免费资源 | 久久精品国产精品亚洲艾草网| 久久久久国产成人精品亚洲午夜| 国产精品亚洲精品日韩动图| 亚洲精品成a人在线观看夫| 亚洲国产一区二区三区青草影视| 亚洲色欲久久久综合网| 中文字幕亚洲一区二区va在线| 亚洲欧洲精品成人久久曰影片 | 亚洲一区二区影院| 婷婷亚洲久悠悠色悠在线播放 | 亚洲欧洲日产国码久在线| 亚洲中文字幕无码mv| 亚洲精品成a人在线观看☆| 亚洲精品人成网线在线播放va| 亚洲日韩国产欧美一区二区三区 | 亚洲码欧美码一区二区三区| 亚洲成在人线aⅴ免费毛片| 久久久亚洲精华液精华液精华液 | 亚洲av无码成人影院一区| 99亚洲精品卡2卡三卡4卡2卡| 老牛精品亚洲成av人片| 亚洲成年看片在线观看| 国产精品亚洲综合一区| 亚洲熟妇av一区二区三区| 亚洲AV无码精品色午夜果冻不卡| 午夜影视日本亚洲欧洲精品一区| 亚洲影院在线观看| 亚洲精品无码专区2| 亚洲精品99久久久久中文字幕| 亚洲一级Av无码毛片久久精品| 国产亚洲精aa成人网站| 亚洲AV无码久久寂寞少妇| 亚洲人成网站18禁止久久影院 | 亚洲va久久久久| 精品久久久久亚洲| 国产成人精品日本亚洲语音| 亚洲免费日韩无码系列|