【iOS 底層】主線程 & 主Runloop

Pre

  • macOS:Catalina 10.15.7
  • Xcode:12.3
  • objc4:objc4-787.1

基本概念

CFRunloop

  1. CFRunLoop對象監視任務的輸入源,並在它們準備好進行處理時分派控制。
  2. 運行循環能夠監視三種類型的對象:CFRunLoopSourceCFRunLoopTimerCFRunLoopObserver
  3. 添加到運行循環中的每一個源、計時器和觀察者必須與一個或多個運行循環模式相關聯。
  4. Core Foundation定義了一種特殊的僞模式,稱爲common modes,它容許您將多個模式與給定的source、timer或observer關聯起來。
  5. 每一個線程只有一個運行循環。你既不建立也不銷燬線程的運行循環。Core Foundation會根據須要自動爲您建立它。
  6. 運行循環能夠遞歸地運行。您能夠在任何運行循環調用中調用CFRunLoopRunCFRunLoopRunInMode,並在當前線程的調用堆棧上建立嵌套的運行循環激活。
  7. Cocoa應用程序構建在CFRunLoop之上,實現它們本身的高級事件循環。在編寫應用程序時,能夠將源代碼、計時器和觀察者添加到它們的運行循環對象和模式中。而後,您的對象將做爲常規應用程序事件循環的一部分被監視。使用NSRunLoop的gettcfrunloop方法能夠獲得對應的CFRunLoopRef類型。

NSRunloop

  • NSRunLoop是對Core Fundation中的CFRunloop的封裝
  • NSRunLoop對象處理來自窗口系統的鼠標和鍵盤事件、NSPort對象和NSConnection對象等源的輸入。NSRunLoop對象也會處理NSTimer事件。
  • 你的應用程序既不建立也不顯式管理NSRunLoop對象。每一個NSThread對象(包括應用程序的主線程)都有一個根據須要自動建立的NSRunLoop對象。若是須要訪問當前線程的運行循環,可使用類方法currentRunLoop來實現。
  • 注意,從NSRunLoop的角度來看,NSTimer對象不是「輸入」——它們是一種特殊的類型,這意味着當它們觸發時,不會致使運行循環返回。
  • NSRunLoop類一般被認爲是線程不安全的,它的方法應該只在當前線程的上下文中被調用。永遠不要嘗試調用運行在不一樣線程中的NSRunLoop對象的方法,由於這樣作可能會致使意想不到的結果。

線程 & Runloop

先看結論!劃重點!

  • CocoaCore Foundation都提供了運行循環對象(NSRunloopCFRunloop)來幫助配置和管理線程的運行循環。
  • 應用程序不須要顯式地建立這些對象;每一個線程(包括主線程)都有一個關聯的Runloop對象。
  • 做爲應用程序啓動過程的一部分,應用程序框架自動在主線程上設置並運行運行循環。而子線程須要顯式地運行它們的運行循環。
  • 關係歸納:App啓動後,蘋果在主線程建立了其關聯的Runloop,並在該Runloop中註冊兩個Observer
    • 第一個Observer(優先級最高)監控的事件:Entry(即將進入Loop),回調內會調用_objc_autoreleasePoolPush()建立自動釋放池
    • 第二個Observer(優先級最低)監控兩個事件:
      • 第一個事件:BeforeWaiting(準備進入休眠),回調內會調用_objc_autoreleasePoolPop()釋放舊的池並調用_objc_autoreleasePoolPush()建立新的池
      • 第二個事件:Exit(即將退出Loop),回調內會調用_objc_autoreleasePoolPop()銷燬自動釋放池

關係分析

  • 根據調用堆棧咱們能夠看出來,當調用UIApplicationMain()方法後,系統會自動爲其建立相關聯的Runloop
  • 經過在main()函數首行即獲取主線程與主runloop能夠看到:此時主線程的runloop已經存在了。咱們能夠推測出主線程建立後即會建立對應的runloop,也就是說,主runloop在程序一啓動就默認建立好了。 可是此時的主runloop中,還未添加相關觀察者等等。
  • 當代碼從main()開始執行,此時的runloop依然是一個空的結構體
  • 查看進入applicationDidLaunchingWithOptions:以前的調用堆棧 能夠看到會爲此時的主Runloop添加Source等相關信息
  • 走進applicationDidLaunchingWithOptions時,再經過po CFRunloopGetMain()獲取此時的主Runloop信息,能夠看到觀察者、source等都已添加完成
// 截取部分
current mode = kCFRunLoopDefaultMode,
common modes = <CFBasicHash 0x6000014e5260 [0x7fff8002e8c0]>{type = mutable set, count = 2,
entries =>
	0 : <CFString 0x7fff806610e0 [0x7fff8002e8c0]>{contents = "UITrackingRunLoopMode"}
	2 : <CFString 0x7fff801ab7e8 [0x7fff8002e8c0]>{contents = "kCFRunLoopDefaultMode"}
}
相關文章
相關標籤/搜索