RunLoop概念:
運行循環。實際上是一個對象,這個對象在循環中用來處理程序運行過程中出現的各種事件(比如說觸摸事件、UI刷新事件、定時器事件、Selector事件),從而保持程序的持續運行。
在沒有事件處理時,會進入睡眠狀態,節省CPU資源,直到有事件將它喚醒。
RunLoop與線程的關係:
RunLoop與線程是一一對應的,每一個線程都有唯一一個對應的RunLoop。即使在當前線程中開闢出子線程,也會有與子線程對應的RunLoop。但是在線程剛創建的時候並沒有RunLoop,RunLoop的創建發生在第一次獲取時[NSRunLoop currentRunLoop],銷燬在線程結束時。(主線程的RunLoop是開啓的)
線程(創建)-->runloop將進入-->最高優先級OB創建釋放池-->runloop將睡-->最低優先級OB銷燬舊池創建新池-->runloop將退出-->最低優先級OB銷燬新池-->線程(銷燬)
RunLoop 並不保證線程安全。我們只能在當前線程內部操作當前線程的 RunLoop 對象,而不能在當前線程內部去操作其他線程的 RunLoop 對象方法。
【自動釋放池】自動釋放池寄生於Runloop:程序啓動後,蘋果在主線程 RunLoop 裏註冊了兩個 Observer,其回調都是 _wrapRunLoopWithAutoreleasePoolHandler(),一是,監測Entry(即將進入Loop)狀態,其回調內會調用 _objc_autoreleasePoolPush() 創建自動釋放池,其 order 是-2147483647,優先級最高,保證創建釋放池發生在其他所有回調之前;另外一個,Observer 監視了兩個事件: BeforeWaiting(準備進入休眠) 時調用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 釋放舊的池並創建新池;Exit(即將退出Loop) 時調用 _objc_autoreleasePoolPop() 來釋放自動釋放池。這個 Observer 的 order 是 2147483647,優先級最低,保證其釋放池子發生在其他所有回調之後。
RunLoop數據結構:
runloop相關的數據結構:
- CFRunLoop
-
struct __CFRunLoop { CFMutableSetRef _commonModes; // Set<CFStringRef> CFMutableSetRef _commonModeItems; // Set<Source/Observer/Timer> CFRunLoopModeRef _currentMode; // Current Runloop Mode CFMutableSetRef _modes; // Set<CFRunLoopModeRef> ... };
-
- CFRunLoopMode
-
struct __CFRunLoopMode { CFStringRef _name; // Mode名字,默認是NSDefaultRunLoopMode CFMutableSetRef _sources0; // Set<CFRunLoopSourceRef> CFMutableSetRef _sources1; // Set<CFRunLoopSourceRef> CFMutableArrayRef _observers; // Array<CFRunLoopObserverRef> CFMutableArrayRef _timers; // Array<CFRunLoopTimerRef> ... };
Source:
- source0: event事件,只含有回調,需要標記待處理(signal),然後手動將runloop喚醒(wakeup)
source0 :執行performSelectors方法,假如你在主線程performSelectors一個任務到子線程,這時候就是在代碼中發送事件到子線程的runloop,這時候如果子線程開啓了runloop就會執行該任務,注意該performSelector方法只有在你子線程開啓runloop才能執行,如果你沒有在子線程中開啓runloop,那麼該操作會無法執行
- source1:包含一個 mach_port 和一個回調,被用於通過內核和其他線程發送的消息,能主動喚醒runloop。
-
source1 :蘋果創建用來接受系統發出事件,當手機發生一個觸摸,搖晃或鎖屏等系統,這時候系統會發送一個事件到app進程(進程通信),這也就是爲什麼叫基於port傳遞source1的原因,port就是進程端口,該事件可以激活進程裏線程的runloop,比如你點擊一下app的按鈕或屏幕,runloop就醒過來處理觸摸事件,處理結束後進入休眠。
Timer
- 基於事件的定時器
- 與NSTimer 免費橋接轉換
Observer
觀測時間點,共有六種狀態
- kCFRunLoopEntry:即將進入Loop
- kCFRunLoopBeforeTimers: 即將處理 Timer
- kCFRunLoopBeforeSources: 即將處理 Source
- kCFRunLoopBeforeWaiting: 即將進入休眠
- kCFRunLoopAfterWaiting: 剛從休眠中喚醒
- kCFRunLoopExit: 即將退出Loop
-
RunLoop、Model、Source/Timer/Observer關係
RunLoop和Model是一對多的關係(從CFRunLoop的數據結構modes)。
Model和Source/Timer/Observer是一對多的關係。
Model類型:
- kCFRunLoopDefaultMode: 默認 mode,通常主線程在這個 Mode 下運行。
- UITrackingRunLoopMode: 追蹤mode,保證Scrollview滑動順暢不受其他 mode 影響。
- UIInitializationRunLoopMode: 啓動程序後的過渡mode,啓動完成後就不再使用。
- GSEventReceiveRunLoopMode: Graphic相關事件的mode,通常用不到。
- NSRunLoopCommonModes: 佔位mode,作爲標記DefaultMode和CommonMode用。CommonMode不是實際存在的一種Mode。是同步Source/Timer/Observer到多個Model中的一種技術方案。
-
對於最後一個commonModes:
-
一個 Mode 可以將自己標記爲"Common"屬性(通過將其 ModeName 添加到 RunLoop 的 "commonModes" 中)。每當 RunLoop 的內容發生變化時,RunLoop 都會自動將 _commonModeItems 裏的 Source/Observer/Timer 同步到具有 "Common" 標記的所有Mode裏。
應用場景舉例:主線程的 RunLoop 裏有兩個預置的 Mode:kCFRunLoopDefaultMode 和 UITrackingRunLoopMode。這兩個 Mode 都已經被標記爲"Common"屬性。DefaultMode 是 App 平時所處的狀態,TrackingRunLoopMode 是追蹤 ScrollView 滑動時的狀態。當你創建一個 Timer 並加到 DefaultMode 時,Timer 會得到重複回調,但此時滑動一個TableView時,RunLoop 會將 mode 切換爲 TrackingRunLoopMode,這時 Timer 就不會被回調,並且也不會影響到滑動操作。
有時你需要一個 Timer,在兩個 Mode 中都能得到回調,一種辦法就是將這個 Timer 分別加入這兩個 Mode。還有一種方式,就是將 Timer 加入到commonMode 中。那麼所有被標記爲commonMode的mode(defaultMode和TrackingMode)都會執行該timer。這樣你在滑動界面的時候也能夠調用timer。
多個model能夠起到屏蔽事件的作用。
循環機制:
在每次運行開啓RunLoop的時候,所在線程的RunLoop會自動處理之前未處理的事件,並且通知相關的觀察者。
具體的順序如下:
- 通知觀察者RunLoop已經啓動
- 通知觀察者即將要開始的定時器
- 通知觀察者任何即將啓動的非基於端口的源
- 啓動任何準備好的非基於端口的源
- 如果基於端口的源準備好並處於等待狀態,立即啓動;並進入步驟9
- 通知觀察者線程進入休眠狀態
- 將線程置於休眠知道任一下面的事件發生:
- 某一事件到達基於端口的源
- 定時器啓動
- RunLoop設置的時間已經超時
- RunLoop被顯示喚醒
- 通知觀察者線程將被喚醒
- 處理未處理的事件
- 如果用戶定義的定時器啓動,處理定時器事件並重啓RunLoop。進入步驟2
- 如果輸入源啓動,傳遞相應的消息
- 如果RunLoop被顯示喚醒而且時間還沒超時,重啓RunLoop。進入步驟2
- 通知觀察者RunLoop結束。
待補充。。。