Cocoa RunLoop 系列之基礎知識

博客地址

這篇博客主要結合Apple開發者文檔和個人的理解,寫的一篇關于Cocoa RunLoop基本知識點的文章。在文檔的基礎上,概況和梳理了RunLoop相關的知識點。

一、Event Loop & Cocoa RunLoop

宏觀上:Event Loop

  1. RunLoop是一個用于循環監聽和處理事件或者消息的模型,接收請求,然后派發給相關的處理模塊,wikipedia上有更為全面的介紹:Event_loop
  2. Cocoa RunLoop屬于Event Loop模型在Mac平臺的具體實現
  3. 其他平臺的類似實現:X Window程序,Windows程序 ,Glib庫等

微觀上: Cocoa RunLoop

  1. Cocoa RunLoop本質上就是一個對象,提供一個入口函數啟動事件循環,在滿足特點條件后才會退出。
  2. Cocoa RunLoop與普通while/for循環不同的是它能監聽處理事件和消息,能智能休眠和被喚醒,這些功能的其實現依賴于Mac Port。

二、 Cocoa RunLoop的內部結構

但凡說到Cocoa RunLoop內部結構,都離不開下面這張圖,來源于Apple開發者文檔

圖1-1 RunLoop結構圖

結合上圖,可將RunLoop架構劃分為四個部分:

  1. 事件源
  2. 運行模式
  3. 循環機制
  4. 執行反饋

1. 事件源

Cocoa RunLoop接受的事件源分為兩種類型:Input Sources 和 Timer Sources

1.1. Input Sources

Input Sources通過異步派發的方式將事件轉送到目標線程,事件類別分為兩大塊:

  • Port-Based Sources :

    基于Mach端口的事件源,Cocoa和Core Foundation這兩個框架已經提供了內部支持,只需要調用端口相關的對象或者函數就能提供端口進行通信。比如:將NSPort對象部署到RunLoop中,實現兩個線程的循環通信。

  • Custom Input Sources :

    • 用戶自定義的輸入源:使用Core Foundation框架中CFRunLoopSourceRef對象的相關函數實現。具體實現可以查看另外一篇博客:Cocoa RunLoop 系列之Configure Custom InputSource

    • Cocoa Perform Selector Sources:Cocoa框架內部實現的自定義輸入源,可以跨線程調用,實現線程見通信,有點類似于Port-Based事件源,不同的是這種事件源只在RunLoop上部署一次,執行結束后便會自動移除。如果目標線程中沒有啟動RunLoop也就意味著無法部署這類事件源,因此不會得到預期的結果。

      使用Cocoa自定義事件源的函數接口,如下:


   //部署在主線程
   //參數列表:Selector:事件源處理函數,Selector參數,是否阻塞當前線程,指定RunLoop模式
   performSelectorOnMainThread:withObject:waitUntilDone:
   performSelectorOnMainThread:withObject:waitUntilDone:modes:
   
   //部署在指定線程
   //參數列表:Selector:事件源處理函數,指定線程,Selector參數,是否阻塞當前線程,指定RunLoop模式
   permSelector:onThread:withObject:waitUntilDone:
   performSelector:onThread:withObject:waitUntilDone:modes:
   
   //部署在當前線程
   //參數列表:Selector:事件源處理函數,Selector參數,延時執行時間,指定RunLoop模式
   performSelector:withObject:afterDelay:
   performSelector:withObject:afterDelay:inModes:
    
   //撤銷某個對象通過函數performSelector:withObject:afterDelay:部署在當前線程的全部或者指定事件源
   cancelPreviousPerformRequestsWithTarget:
   cancelPreviousPerformRequestsWithTarget:selector:object:

綜上,Input Sources包括基于Mach端口的事件源和自定義的事件源,二者的唯一區別在于被觸發的方式:前者是由內核自動觸發,后者則需要在其他線程中手動觸發。

1.2. Timer Sources

不同于Input Sources的異步派發,Timer Source是通過同步派發的方式,在預設時間到達時將事件轉送到目標線程。這種事件源可用于線程的自我提醒功能,實現周期性的任務。

  • 如果RunLoop當前運行模式沒有添加Time Sources,則在RunLoop中部署的定時器不會被執行。
  • 設定的間隔時間與真實的觸發時間之間沒有必然聯系,定時器會根據設定的間隔時間周期性的派發消息到RunLoop,但是真實的觸發時間由RunLoop決定,假設RunLoop當前正在處理其一個長時間的任務,則觸發時間會被延遲,如果在最終觸發之前Timer已經派發了N個消息,RunLoop也只會當做一次派發對待,觸發一次對應的處理函數。

2. 運行模式

運行模式類似于一個過濾器,用于屏蔽那些不關心的事件源,讓RunLoop專注于監聽和處理指定的事件源和RunLoop Observer。

CFRunLoopMode 和 CFRunLoop 的數據結構大致如下:


    struct __CFRunLoop {
        CFMutableSetRef _commonModes;     // Set
        CFMutableSetRef _commonModeItems; // Set<Source/Observer/Timer>
        CFRunLoopModeRef _currentMode;    // Current Runloop Mode
        CFMutableSetRef _modes;           // Set
        ...
    };
    
    struct __CFRunLoopMode {
        CFStringRef _name;            // Mode Name, 例如 @"kCFRunLoopDefaultMode"
        CFMutableSetRef _sources0;    // Set
        CFMutableSetRef _sources1;    // Set
        CFMutableArrayRef _observers; // Array
        CFMutableArrayRef _timers;    // Array
        ...
    };

結合以上源碼,總結以下幾點:

  • 每種模式通過name屬性作為標識。
  • 一種運行模式(Run Loop Mode)就是一個集合,包含需要監聽的事件源Input Sources和Timer Soueces以及需要觸發的RunLoop observers。
  • Cocoa RunLoop包含若干個Mode,調用RunLoop是指定的Mode稱之為CurrentMode。RunLoop可以在不同的Mode下切換,切換時退出CurrentMode,并保存相關上下文,再進入新的Mode。
  • 在啟動Cocoa RunLoop是必須指定一種的運行模式,且如果指定的運行模式沒有包含事件源或者observers,RunLoop會立刻退出。
  • CFRunLoop結構中的commonModes是Mode集合,將某個Mode的name添加到commonModes集合中,表示這個Mode具有“common”屬性。
  • CFRunLoop結構中的commonModeItems則是共用源的集合,包括事件源和執行反饋。這些共用源會被自動添加到具有“common”屬性的Mode中。

** Note ** : 不同的運行模式區別在于事件源的不同,比如來源于不同端口的事件和端口事件與Timer事件。不能用于區分不同的事件類型,比如鼠標消息事件和鍵盤消息事件,因為這兩種事件都屬于基于端口的事件源。

以下是蘋果預定義好的一些運行模式:

  • NSDefaultRunLoopMode //默認的運行模式,適用于大部分情況
  • NSConnectionReplyMode //Cocoa庫用于監聽NSConnection對象響應,開發者很少使用
  • NSModalPanelRunLoopMode //模態窗口相關事件源
  • NSEventTrackingRunLoopMode //鼠標拖拽或者屏幕滾動時的事件源
  • NSRunLoopCommonModes //用于操作RunLoop結構中commonModes和commonModeItems兩個屬性

3. 循環機制

循環機制涉及兩方面:

3.1. RunLoop與線程之間的關系

Apple文檔中提到:開發者不需要手動創建RunLoop對象,每個線程包括主線程都關聯了一個RunLoop對象。除了主線程的RunLoop在程序啟動時被開啟,其他線程的RunLoop都需要手動開啟。

待解決的疑問:

  • 線程中的RunLoop是一直存在還是需要時再創建?
  • 線程與RunLoop的是如何建立聯系的?
  • 線程與RunLoop對象是否是一一對應的關系?
3.2. RunLoop事件處理流程

弄清楚RunLoop內部處理邏輯是理解RunLoop的關鍵,將單獨寫一篇博客進行分析。

待解決的疑問:

  • RunLoop如何處理不同事件源?
  • RunLoop不同模式切換是如何實現的?

以上兩方面,將在下一篇博客Cocoa RunLoop 系列之源碼解析中結合源代碼來找到答案。

4. 執行反饋

RunLoop Observers機制屬于RunLoop一個反饋機制,將RunLoop一次循環劃分成若干個節點,當執行到對應的節點調用相應的回調函數,將RunLoop當前的執行狀態反饋給用戶。

  • 用戶可以通過Core Foundation框架中的CFRunLoopObserverRef注冊 observers。

  • 監聽節點:

    • The entrance to the run loop. //RunLoop啟動
    • When the run loop is about to process a timer. //即將處理Timer事件源
    • When the run loop is about to process an input source. //即將處理Input事件源
    • When the run loop is about to go to sleep. //即將進入休眠
    • When the run loop has woken up, but before it has processed the event that woke it up. //重新被喚醒,且在處理喚醒事件之前
    • The exit from the run loop. //退出RunLoop
  • 監聽類別分為兩種:一次性和重復監聽。

三、何時使用RunLoop

由于主線程的RunLoop在程序啟動時被自動創建并執行,因此只有在其他線程中才需要手動啟動RunLoop。很多情況下,對于RunLoop的使用多數情況是在主線程中,包括進行RunLoop模式切換,設置RunLoop Observer等。

在非主線程中,以下幾種情況適用于RunLoop:

  • 使用基于端口或者自定義的事件源與其他線程進行通信。
  • 需要在當前線程中使用Timer,必須部署才RunLoop中才有效。
  • 在目標線程中調用performSelector… 函數,因為本質上使用了Cocoa自定義的事件源,依賴于RunLoop才能被觸發。
  • 線程需要進行周期性的任務,需要長時間存在,而非執行一次。

四、總結

一直以來,RunLoop對我來說都屬于一個比較模糊的概念,在實際編程中也有用到RunLoop的一些功能,確實感覺到很強大,但是僅僅停留在應用層面,并不是很理解具體含義。因此,為了更好的使用RunLoop,有必要研究和梳理RunLoop相關的知識點。

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

推薦閱讀更多精彩內容