iOS APP啓動優化

App啓動過程

iOS應用的啓動可以分爲pre-main階段和main()階段,其中系統做的事情一次是:

在這裏插入圖片描述
無論對於系統的動彈鏈接還是對於APP本身的可執行文件而言,它們都算是image(鏡像),而每個APP都是以image(鏡像)爲單位進行加載的

什麼是image(鏡像)

  1. Executable:應用的主要二進制(比如.o文件)
  2. Dylib:動態鏈接庫(dynamic library,又稱DSO或DLL)
  3. Bundle:資源文件,不能被鏈接的dylib,只能在運行時使用dlopen()加載

pre-main階段

  1. 加載應用的可只能文件(自身APP所有的.o文件的集合)
  2. 加載動態鏈接(dynamic loader,是一個專門用來加載動態鏈接庫的庫)
  3. dyld遞歸加載應用所有依賴的動態鏈接庫dylib

main()階段

  1. dyld調用main()
  2. 調用UIApplicationMain()
  3. 調用applicationWillFinishLaunching
  4. 調用didFinishLaunchingWithOptions

pre-main階段的過程和優化項

對於pre-main階段的耗時優化,我們需要知道dyld加載的過程,蘋果在2016年WWDC上介紹,dyld的加載過程主要分爲4步:

1、 Load dylibs

分析應用依賴的dylib(xcode7以後.dylib已改爲名.tbd),找到其mach-o文件,打開和讀取這些文件並驗證其有效性,接着會找到代碼簽名註冊到內核,最後對dylib的每一segment調用mmap()
一般情況下,iOS應用會加載100-400個dylibs,其中大部分是系統庫,這部分dylib的加載系統已經做了優化。
所以,依賴的dylib越少越好,我們可以做的優化有:

  1. 儘量不要使用內嵌的dylib,加載內嵌dylib的性能開銷比較大
  2. 合併已有的dylib和使用靜態庫,減少dylib的使用個數
  3. 依賴加載dylib,但是要注意dlopen()可能造成一些問題,且實際上依賴加載做的工作更多

Rebase/Bind

在dylib加載的過程中,系統爲了安全考慮引用ASLR(address space layout randomization)技術和簽名。由於ASLR 的存在,鏡像會隨機的地址上加載,和之前的指針指向的地址,prefered_address會有一個偏差,dyld需要修正這個偏差,來指向正確的地址。
Rebase在前,Bind在後,Rebase做的是將鏡像讀入內存,修正鏡像內部的指針,性能消耗,主要實在IO,Bind做的是查詢符號表,設置指向鏡像外部的指針,性能消耗主要在CPU
所以指針數量越少越好,

  1. 減少objc類,方法selector、分類category的數量
  2. 減少c++虛函數的數量
  3. 使用swift struct(內部做了優化,符號數量更少)

objc setup

大部分objc初始化工作已經在rebase/bind 階段左外,這一步dyld會註冊所有生命過的objc,將分類插入到類方法列表中,再堅持每個selection的唯一性
這一步沒什麼可優化的,rebase/bind階段優化好了,這一步耗時也會減少

Initializers

dyld開始運行程序的初始化函數,調用每個objc類和分類的+load方法,調用c/c++ 中的構造器,和創建非基本類型的c++靜態全局變量。initializers階段執行完後,dyld開始調用main函數
objc的load函數和C++的靜態構造函數採用由低向上的方式執行,來保證每一個執行的方法,都可以找到所有的動態庫

優化:

  1. 少在類的+load方法中做事情,儘量把這些事情推遲到+initialize
  2. 減少構造器個數,在構造函數中少做事情
  3. 減少C++靜態變量的個數

main()階段的優化項

這一階段的優化主要是減少didFinishLaunchingWithOptions方法裏的工作,在didFinishLaunchingWithOptions方法裏,我們會創建應用的window,指定其rootViewController,調用window的makeKeyAndVisible方法讓其可見。由於業務需要,我們會初始化各個二方/三方庫,設置系統UI風格,檢查是否需要顯示引導頁、是否需要登錄、是否有新版本等,由於歷史原因,這裏的代碼容易變得比較龐大,啓動耗時難以控制。

所以,滿足業務需要的前提下,didFinishLaunchingWithOptions在主線程裏做的事情越少越好。在這一步,我們可以做的優化有:

  1. 梳理各個二方/三方庫,找到可以延遲加載的庫,做延遲加載處理,比如放到首頁控制器的viewDidAppear方法裏。

  2. 梳理業務邏輯,把可以延遲執行的邏輯,做延遲執行處理。比如檢查新版本、註冊推送通知等邏輯。

  3. 避免複雜/多餘的計算。

  4. 採用性能更好的API。

  5. 避免在首頁控制器的viewDidLoad和viewWillAppear做太多事情,這2個方法執行完,首頁控制器才能顯示,部分可以延遲創建的視圖應做延遲創建/懶加載處理。

  6. 首頁控制器用純代碼方式來構建。

啓動耗時的測量

pre-main階段:

xcode9以後APP提供了一種測試方法,在Xcode中Edit scheme-> Run -> Auguments
設置環境變量:DYLD_PRINT_STATISTICS爲1

main()階段測量

對於main()階段,主要是測量main()函數開始執行到didFinishLaunchingWithOptions執行結束的耗時,就需要自己插入代碼到工程中了。先在main()函數裏用變量StartTime記錄當前時間

CFAbsoluteTime StartTime;
int main(int argc, char *argv[]){
	StartTime = CFAbsoluteTimeGetCurrent();
}
再在AppDelegate.m文件中用extern聲明全局變量StartTime
extern CFAbsoluteTime Startime;
最後在didFinishLaunchingWithOptions裏,再獲取一下當前時間,與StartTime的差值即是main()階段運行耗時。
double launchTime = (CFAbsoluteTimeGetCurrent() - StartTime)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章