1. 程式人生 > >iOS 如何優化 App

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。

首頁控制器用純程式碼方式來構建。

參考:

  1. App Startup Time: Past, Present, and Future
  2. [iOS]一次立竿見影的啟動時間優化
  3. iOS App 啟動效能優化
  4. APP啟動優化的一次實踐
  5. 阿里資料iOS端啟動速度優化的一些經驗