iOS 如何優化 App
App 啟動時間優化
優化的時候,我們將啟動時間分為 pre-main
時間和 main
函式到第一個介面渲染完成時間這兩個部分。
1. pre-main階段
- 1.1. 載入應用的可執行檔案
- 1.2. 載入動態連結庫載入器dyld(dynamic loader)
- 1.3. dyld遞迴載入應用所有依賴的dylib(dynamic library 動態連結庫)
2. main()階段
- 2.1. dyld呼叫main()
- 2.2. 呼叫UIApplicationMain()
- 2.3. 呼叫applicationWillFinishLaunching
- 2.4. 呼叫didFinishLaunchingWithOptions
1.pre-main介紹
此階段主要做的事情:
- 載入APP的可執行檔案
- 載入動態連結庫載入器的dyld
- dyld遞迴載入所有依賴的動態連結庫dylib,包括iOS系統的以及APP依賴的第三方庫
dyld載入主要分四步:
載入dylib
分析每個dylib(大部分是iOS系統的),找到其Mach-O檔案,開啟並讀取驗證有效性,找到程式碼簽名註冊到核心,最後對dylib的每個segment呼叫mmap()。
優化思路是
- 儘量減少dylib的使用個數,謹慎使用第三方SDK
rebase/bind
dylib載入完成之後,它們處於相互獨立的狀態,需要繫結起來。
優化思路是:
- 減少OC類、selector、category的數量。
- 減少C++虛擬函式的數量。
- 使用Swift struct,減少符號的數量。
OC setup
OC的runtime需要維護一張類名與類的方法列表的全域性表。
dyld做了如下操作:
- 對所有宣告過的OC類,將其註冊到這個全域性表中(class registration)
- 將category的方法插入到類的方法列表中(category registration)
- 檢查每個selector的唯一性(selector uniquing)
initializer
這是pre-main階段最耗時的部分。dyld執行APP的初始化函式,呼叫每個OC類的+load方法,呼叫C++的構造器函式(attribute((constructor))修飾),建立非基本型別的C++靜態全域性變數,然後執行main函式。
優化思路是
- 儘量避免在+load方法裡執行的操作,可以推遲到+initialize方法中。
- 減少C++構造器函式個數。
- 減少C++靜態全域性變數的個數。
其中 pre-main 蘋果提供了內建的測量方法, Xcode 中 Edit scheme -> Run -> Auguments 將環境變數 DYLD_PRINT_STATISTICS 設為 1
//結果為
Total pre-main time: 2.4 seconds (100.0%)
dylib loading time: 433.13 milliseconds (17.8%)
rebase/binding time: 1.8 seconds (77.8%)
ObjC setup time: 71.79 milliseconds (2.9%)
initializer time: 31.60 milliseconds (1.3%)
slowest intializers :
libSystem.B.dylib : 3.78 milliseconds (0.1%)
//解讀
1、main()函式之前總共使用了2.4s
2、載入動態庫用了433.13ms,指標重定位使用了1.8s,ObjC類初始化使用了71.79ms,各種初始化使用了31.60ms。
3、在初始化耗費的31.60ms中,用時最多的初始化是libSystem.B.dylib。
main()函式之前耗時的影響因素
-
動態庫載入越多,啟動越慢。
-
ObjC類越多,啟動越慢
-
C的constructor函式越多,啟動越慢
-
C++靜態物件越多,啟動越慢
-
ObjC的+load越多,啟動越慢
優化思路總結是
1. 移除不需要用到的動態庫(如:未使用的第三方SDK)
2. 移除不需要用到的類(如:無用檔案及資源刪除,圖片資源:去除無用的圖片; 適當進行圖片壓縮)
3. 合併功能類似的類和擴充套件
4. 儘量避免在+load方法裡執行的操作,可以推遲到+initialize方法中。
2.main()階段
AppDelegate的didFinishLaunchingWithOptions
該方法中有很多初始化操作,如日誌,統計,SDK配置等。
儘量做到只放必需的,其他的可以延遲到HomeViewController展示完成(viewDidAppear)以後。
微信分享SDK初始化
[WXApi registerApp:WXApi_APPKEY]
放到首頁載入之後(進一步優化:在儲存分享頁真正需要的時候再去初始化也行,如對應ViewController的initialize中)
HomeVC的viewDidLoad到viewDidAppear之間
如果在AppDelegate構建ViewController(不管是採用storyboard還是程式碼構建),該ViewController的viewDidLoad會立即執行。
Home介面去掉storyboard,改為純程式碼構建
因為storyboard需要先解碼,雖然開發效率高,但涉及到對執行速度比較敏感的啟動階段,最好還是使用純程式碼構建。並且,程式碼構建對於多人協作開發也是更加友好的。
啟動建立的相關檢視
儘量使用程式碼建立,避免使用xib,理由同storyboard。
viewDidAppear之前的耗時操作調整
對於一些不必要的初始化操作,可以調整到viewDidAppear執行時進行。
通過標記確保viewDidAppear中的一次性的初始化操作不要重複執行多次。
這裡,把超出0.01s的都做了調整,如果可以,儘量放到viewDidAppear之後執行,主要是一些邏輯上的初始化、一些非必需介面的構建等操作。必要時,可以使用一些placeholder的方式來填充介面。
到viewDidAppear開始執行的時候,使用者已經看到了APP的首屏,即宣告啟動結束。
使用快取或placeholder
對於一些需要在首頁上展示資料或圖片的APP,可以優先使用本地快取資料或採用placeholder的方式,之後再將網路請求資料展示並更新本地快取。
NSLog
每次使用NSLog都會隱式建立一個calendar,比較耗時,因此一般僅針對測試版本進行log列印。
NSUserDefaults
NSUserDefaults的操作物件是Library資料夾下的一個plist檔案,若該檔案很大,一次性讀取到記憶體中會有較大耗時,可以考慮精簡或拆分。但實際上的影響並不大。
優化思路總結
同時一般來說,優化應該在專案完成穩定之後進行,避免過早優化。
價效比最高的優化階段就是main函式之後的一些邏輯整理,儘量將不需要的耗時操作延遲到首屏展示之後執行。
梳理各個三方庫,找到可以延遲載入的庫,做延遲載入處理,比如放到首頁控制器的viewDidAppear方法裡。
梳理業務邏輯,把可以延遲執行的邏輯,做延遲執行處理。比如檢查新版本、註冊推送通知等邏輯。
避免複雜/多餘的計算。
避免在首頁控制器的viewDidLoad和viewWillAppear做太多事情,這2個方法執行完,首頁控制器才能顯示,部分可以延遲建立的檢視應做延遲建立/懶載入處理。
採用效能更好的API。
首頁控制器用純程式碼方式來構建。
參考: