iOS-RunLoop

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會自動處理之前未處理的事件,並且通知相關的觀察者。

具體的順序如下:

  1. 通知觀察者RunLoop已經啓動
  2. 通知觀察者即將要開始的定時器
  3. 通知觀察者任何即將啓動的非基於端口的源
  4. 啓動任何準備好的非基於端口的源
  5. 如果基於端口的源準備好並處於等待狀態,立即啓動;並進入步驟9
  6. 通知觀察者線程進入休眠狀態
  7. 將線程置於休眠知道任一下面的事件發生:
    • 某一事件到達基於端口的源
    • 定時器啓動
    • RunLoop設置的時間已經超時
    • RunLoop被顯示喚醒
  8. 通知觀察者線程將被喚醒
  9. 處理未處理的事件
    • 如果用戶定義的定時器啓動,處理定時器事件並重啓RunLoop。進入步驟2
    • 如果輸入源啓動,傳遞相應的消息
    • 如果RunLoop被顯示喚醒而且時間還沒超時,重啓RunLoop。進入步驟2
  10. 通知觀察者RunLoop結束。

待補充。。。

參考文章:https://blog.csdn.net/hherima/article/details/51746125

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章