App的啟動流程
App啟動分為冷啟動和熱啟動
- 冷啟動:從0開始啟動App
- 熱啟動:App已經在內存中,但是后臺還掛著,再次點擊圖標啟動App。
一般對App啟動的優化都是針對冷啟動。
App冷啟動可分為三個階段:
- dyld:加載鏡像、動態庫
- RunTime方法
- main函數初始化
動態庫vs靜態庫
靜態庫:一堆.o文件的集合(通常是.a后綴),還沒有被鏈接過,缺點是產物體積比較大,優點是鏈接到App之后體積比較小。
動態庫:一個已經鏈接完全的鏡像,優點是產物體積比較小,缺點是鏈接到App之后體積比較大。
兩者最大的區別就是靜態庫沒有被鏈接過,而動態庫被鏈接過。
一.dyld
dyld是app的動態鏈接器。主要可以用來裝載Mach-O文件(可執行文件、動態庫等)
啟動APP時,dyld所做的事情有:
加載過程從exec()函數開始,這是一個系統調用。操作系統首先為進程分配一段內存空間。然后執行以下操作:
- 1.把App的可執行文件加載到內存
- 2.把dyld加載到內存
- 3.dyld進行動態鏈接
具體內容:
- 1、加載動態庫
- Dyld從主執行文件的header獲取到需要加載的所依賴動態庫列表,然后它需要找到每個 dylib,而應用所依賴的 dylib 文件可能會再依賴其他 dylib,所以所需要加載的是動態庫列表一個遞歸依賴的集合
- 2、Rebase和Binding
- 1、Rebase(偏移修正)
? ? ? ?任何一個app生成的二進制文件,在二進制文件內部所有的方法、函數調用,都有一個地址,這個地址是在當前二進制文件中的偏移地址。一旦在運行時刻(即運行到內存中),每次系統都會隨機分配一個ASLR(Address Space Layout Randomization,地址空間布局隨機化)地址值(是一個安全機制,會分配一個隨機的數值,插入在二進制文件的開頭),例如,二進制文件中有一個 test方法,偏移值是0x0001,而隨機分配的ASLR是0x1f00,如果想訪問test方法,其內存地址(即真實地址)變為 ASLR+偏移值 = 運行時確定的內存地址(即0x1f00+0x0001 = 0x1f01)
- 2、Binding(綁定)
? ? ? ?例如NSLog方法,在編譯時期生成的mach-o文件中,會創建一個符號!NSLog(目前指向一個隨機的地址),然后在運行時(從磁盤加載到內存中,是一個鏡像文件),會將真正的地址給符號(即在內存中將地址與符號進行綁定,是dyld做的,也稱為動態庫符號綁定),一句話概括:綁定就是給符號賦值的過程
- 1、Rebase(偏移修正)
二.RunTime階段
dyld階段結束之后就進入RunTime階段,這個階段主要進行如下內容:
1、Objc setup
- 1、注冊Objc類 (class registration)
- 2、把category的定義插入方法列表 (category registration)
- 3、保證每一個selector唯一 (selector uniquing)
2、Initializers
- 1、Objc的+load()函數
- 2、C++的構造函數屬性函數
- 3、非基本類型的C++靜態全局變量的創建(通常是類或結構體)
三.main()函數初始化
App的啟動由dyld主導,把可執行文件加載到內存,并且加載所有依賴的動態庫,并由RunTime負責加載成objc定義的結構,所有初始化工作結束后,dyld就會調用mainn函數 接下來就是UIApplicationMain函數,AppDelegate的application:didFinishLaunchingWithOptions:方法
這個里面往往是最占用啟動時間的地方,同時也是我們最為可控的地方。
? 進入 main() 函數,啟動應用。
? 執行 UIApplicationMain() 函數,創建 UIApplication 對象并設置 AppDelegate。
? 加載應用的主 UI,包括 storyboard 或 xib 文件,以及 AppDelegate 的各種生命周期方法,如 application:didFinishLaunchingWithOptions:。
四.首屏渲染階段
初始化rootViewController,加載和渲染界面
渲染完成后,用戶將看到應用的首屏。
?
App冷啟動流程總結:
1. dyld 加載階段:
? 動態鏈接器 dyld 負責加載應用的可執行文件及其依賴的動態庫。此時,系統將會做如下工作:
? 查找應用的可執行文件和動態庫
? 將它們加載到內存中
? 進行符號解析和綁定
? 執行初始化函數(如 +load 方法和靜態構造函數)
2. runtime 初始化階段:
? ObjC 運行時對類和分類進行注冊。
? 執行各類 +load 方法,這個階段還會進行一些 Swift 類的初始化。
3. main() 函數執行階段:
? 進入 main() 函數,啟動應用。
? 執行 UIApplicationMain() 函數,創建 UIApplication 對象并設置 AppDelegate。
? 加載應用的主 UI,包括 storyboard 或 xib 文件,以及 AppDelegate 的各種生命周期方法,如 application:didFinishLaunchingWithOptions:。
4. 首屏渲染階段:
? 初始化 rootViewController,加載和渲染界面。
? 渲染完成后,用戶將看到應用的首屏。
+load與+initialize
1、+load
(1)+load
方法是一定會在runtime中被調用的。只要類被添加到runtime中了,就會調用+load
方法,因此+load
方法總是在main函數之前調用。
(2)+load
方法不會覆蓋。也就是說,如果子類實現了+load
方法,那么會先調用父類的+load
方法(無需手動調用super),然后又去執行子類的+load
方法。
(3)+load方法只會調用一次。
(4)+load方法執行順序是:類 -> 子類 ->分類。而不同分類之間的執行順序不一定,依據在Compile Sources
中出現的順序(先編譯,則先調用,列表中在下方的為“先”)。
(5)+load方法是函數指針調用,即遍歷類中的方法列表,直接根據函數地址調用。如果子類沒有實現+load方法,子類也不會自動調用父類的+load方法。
2、+initialize
(1)+initialize
方法是在類或它的子類收到第一條消息之前被調用的,這里所指的消息包括實例方法和類方法的調用。因此+initialize
方法總是在main函數之后調用。
(2)+initialize
方法只會調用一次。
(3)+initialize
方法實際上是一種惰性調用,如果一個類一直沒被用到,那它的+initialize
方法也不會被調用,這一點有利于節約資源。
(4)+initialize
方法會覆蓋。如果子類實現了+initialize
方法,就不會執行父類的了,直接執行子類本身的。如果分類實現了+initialize
方法,也不會再執行主類的。
(5)+initialize
方法的執行覆蓋順序是:分類 -> 子類 ->類。且只會有一個+initialize
方法被執行。
(6)+initialize
方法是發送消息(objc_msgSend()),如果子類沒有實現+initialize
方法,也會自動調用其父類的+initialize
方法。
3、兩者的異同
(1)相同點
- load和initialize會被自動調用,不能手動調用它們。
- 子類實現了load和initialize的話,會隱式調用父類的load和initialize方法。
- load和initialize方法內部使用了鎖,因此它們是線程安全的。
(2)不同點
- 調用順序不同,以main函數為分界,
+load
方法在main函數之前執行,+initialize
在main函數之后執行。(存疑) - 子類中沒有實現
+load
方法的話,子類不會調用父類的+load
方法;而子類如果沒有實現+initialize
方法的話,也會自動調用父類的+initialize
方法。 +load
方法是在類被裝在進來的時候就會調用,+initialize
在第一次給某個類發送消息時調用(比如實例化一個對象),并且只會調用一次,是懶加載模式,如果這個類一直沒有使用,就不回調用到+initialize
方法。
4、使用場景
(1)+load
一般是用來交換方法,由于它是線程安全的,而且一定會調用且只會調用一次,通常在使用UrlRouter的時候注冊類的時候也在+load
方法中注冊。
(2)+initialize
方法主要用來對一些不方便在編譯期初始化的對象進行賦值,或者說對一些靜態常量進行初始化操作。
冷啟動時間優化
1.減少動態庫(dyld階段)
一般不多于6個,多余需要進行合并,動態庫越多,dyld階段加載時間越長。
2.減少類和方法的數量
3.延遲初始化(rebase/Binging階段)
盡量延遲一些不必要的初始化工作,不要在啟動時立即初始化所有對象。可以使用懶加載將一些初始化放到用戶需要時再進行,以減輕啟動階段的負擔。
4. 避免 +load 方法的使用(Initializers階段)
+load 方法會在 dyld 加載階段執行,建議用 +initialize 或者在合適的地方延遲執行初始化邏輯,避免阻塞啟動流程。
5.優化 AppDelegate(main()階段)
application:didFinishLaunchingWithOptions: 方法應保持精簡,避免在這里進行耗時的操作。將一些耗時任務放到后臺隊列中異步執行。
6.減少主線程阻塞
啟動階段盡量避免主線程的耗時操作,如文件 I/O、網絡請求等。將這些操作放到子線程處理,以免阻塞界面渲染。
7.預編譯和瘦身
移除未使用的代碼、圖片等資源,精簡應用的體積,從而減少加載時間。
盡量減少 storyboard 的使用,尤其是大而復雜的 storyboard,可以分解成多個小的 storyboard 或者使用純代碼實現界面。
8.啟動時的網絡請求
盡量避免在啟動時進行同步的網絡請求,如果必須請求,可以在啟動完成后或在后臺進行異步請求,以減少對啟動時間的影響。
?
參考:
iOS--App啟動過程及優化_ios啟動優化-CSDN博客
https://juejin.cn/post/6951591401528229895?searchId=202502182003101C89E818EB9B45117D0A
JHBlog/iOS知識點/iOS大雜燴/APP啟動優化/App啟動時間優化.md at master · SunshineBrother/JHBlog · GitHub
?
?