App的啓動過程及優化分享

App的啓動過程及優化分享

App 啓動時都幹了些什麼事兒?

一般情況下,App 的啓動分爲冷啓動和熱啓動。

  • 1、冷啓動是指, App 點擊啓動前,它的進程不在系統裏,需要系統新創建一個進程分配給它啓動的情況。這是一次完整的啓動過程。
  • 2、熱啓動是指,App再冷啓動後用戶將App退到後臺,在App的進程還在系統裏的情況下,用戶重新啓動進入App的過程,這個過程做的事情比較少。

用戶能感知到的啓動慢,其實都發生在主線程上。而主線程慢的原因有很多,比如在主線程上執行了大文件讀寫操作、在渲染週期中執行了大量計算等。
一般而言,App 的啓動時間,指的是從用戶點擊 App 開始,到用戶看到第一個界面之間的時間。總結來說,App 的啓動主要包括三個階段:

  • 1、main() 函數執行前;
  • 2、main() 函數執行後;
  • 3、首屏渲染完成後。

main() 函數執行之前
在這裏插入圖片描述

或者:

@UIApplicationMain
UIApplicationMain的內容
 *  @param argc   系統參數 
 *  @param argv   系統參數 
 *  @param nil    應用程序名稱 
 *  @param class 應用程序代理名稱 
 */ 
 UIApplicationMain(int argc, charchar *argv[], NSString *principalClassName, NSString *delegateClassName);

UIApplicationMain函數有四個參數,
argc、argv:直接傳遞給UIApplicationMain進行相關處理,包含了系統帶過來的啓動時間;
principalClassName:指定應用程序類名,該類必須是UIApplication(或子類)。如果爲nil,則用UIApplication類作爲默認值;delegateClassName:指定應用程序的代理類,該類必須遵守UIApplicationDelegate協議

UIApplicationMain函數會根據principalClassName創建UIApplication對象,根據delegateClassName創建一個delegate對象,並將該delegate對象賦值給UIApplication對象中的delegate屬性 。接着會建立應用程序的Main Runloop(事件循環),進行事件的處理(首先會在程序完畢後調用delegate對象的application:didFinishLaunchingWithOptions:方法)。app啓動時會加載Info.plist文件,看是否指定了main.storyboard,如果設置了就去加載main.storyboard,那麼加載main.storyboard時,系統會進行如下操作:
創建窗口 -> 加載main.storyboard並且加載main.storyboard中指定的控制器 -> 創建控制器成爲窗口的根控制器,讓窗口顯示出來。

這個階段的耗時可以通過配置環境變量查看。DYLD_PRINT_STATISTICS
在這裏插入圖片描述
——————
從上圖我們可以知道,這個階段的系統工作大致爲:
1、dylib loading | 加載動態鏈接庫。dyld從可執行文件的依賴開始, 遞歸加載所有的依賴動態鏈接庫。動態鏈接庫包括:iOS 中用到的所有系統 framework、加載OC runtime方法的libobjc、系統級別的libSystem,例如libdispatch(GCD)和libsystem_blocks (Block)。優化手段是減少動態庫的加載,將多個動態庫進行合併使用,以及使用靜態資源,比如把代碼加入主程序
2、 rebease/bind | 進行rebase指針調整和bind符號綁定。由於ASLR(address space layout randomization,一種針對緩衝區溢出的安全保護技術)的存在,可執行文件和動態鏈接庫在虛擬內存中的加載地址每次啓動都不固定,所以需要這2步來修復資源指針,指向正確的地址。rebase修復的是指向內部的資源指針,bind指向的是外部的資源指針。
3、 ObjC setup | 運行時初始處理,包括註冊Objc類、把category的定義插入方法列表、保證每一個selector唯一等。這個部分的優化手段是定期清理項目中不再使用的類或者方法,因爲在功能迭代時期可能會淘汰掉很多不再需要的功能,應該將這些文件及時清理。
4、 initializer | 初始化,包括了執行+load()方法、創建靜態全局變量等。在一個+load()方法裏,進行運行時方法替換會帶來4毫秒的消耗,這部分消耗積少成多,可以選擇使用+initialize()方法替換,或者將這部分內容放到首屏渲染完成後執行,進行分流。

由於這部分主要是系統級別的操作,所以我們能優化的範圍很小,主要是要了解這個階段系統的主要工作,儘量避免使用動態庫,檢查+load()方法等。

注:蘋果建議的是在400ms內完成main()函數之前的加載,整體耗時不能超過20s,否則會殺掉進程,App啓動失敗。

main() 函數執行之後 和 首屏渲染完成後
首先我們先通過代碼明確一下main() 函數執行之後和首屏渲染完成後具體是什麼意思?
在這裏插入圖片描述
在這裏插入圖片描述
運行程序,控制檯結果如下:
在這裏插入圖片描述

可以看出,
1、 main() 函數執行之後這個階段,指的是從 AppDelegate 的 didFinishLaunchingWithOptions 方法開始,直到首頁控制器的ViewDidLoad方法執行完畢。
2、 首頁渲染完畢後這個階段,指的是從首頁控制器 ViewDidLoad 方法結束,直到 AppDelegate 的 didFinishLaunchingWithOptions 方法結束。

那麼這兩個階段分別又執行什麼任務呢?耗時又如何監控?

在 main() 函數執行之後 到 首屏渲染完成前,主要任務包括:
1、首屏初始化所需配置文件的讀寫
2、首屏所需數據的讀取
3、首屏渲染的計算等
在 首屏渲染完成後,主要完成:
1、 非首屏業務模塊的初始化
2、 系統監聽註冊
3、 配置文件的讀取等
此時用戶已經可以看到APP的首頁了,以上這些不緊急的任務最好在這個階段處理。

如何監控耗時?

  • 1、Xcode自帶工具Time Profiler,定時抓取主線程上的方法調用堆棧,計算一段時間裏各個方法的耗時。
  • 2、對objc_msgSend方法進行 hook 來掌握所有方法的執行耗時,hook方法的意思是,在原方法開始執行時換成執行其他你指定的方法,或者再原有方法執行前後執行你指定的方法,來達到掌握和改變指定方法的目的。
    hook objc_msgSend 方法,是基於Facebook開源的 fishhook 庫。
    在這裏插入圖片描述
    理解了APP啓動的流程,我們大概能感受到,啓動速度的優化主要集中在didFinishLaunchingWithOptions 中,這裏主要進行的工作就是:
  • 1、 初始化各種SDK
  • 2、 初始化各種工具類
  • 3、 配置各種監聽等
    我們需要做的就是根據項目需求,有的放矢的安排各種工作的運行順序並解偶 AppDelegate,巧妙的使用預加載方法,優化首頁的佈局等。

最後

優化工作應該是貫穿始終的
優化工作更需要前後端的密切配合。

參考資料

1、ASLR機制:https://www.jianshu.com/p/728f2ef139ae
2、https://www.jianshu.com/p/c603a1e69126

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