iOS老司機的App啟動優化Tips, 讓啟動速度提升10%

語言: CN / TW / HK

持續創作,加速成長!這是我參與「掘金日新計劃 · 10 月更文挑戰」的第7天,點選檢視活動詳情

前言

  • 評價一個App是不是一款出色的應用, 第一印象很重要.
  • 這就要求我們必須把App的啟動速度的優先順序排的很高, 設想一個場景, 點選了App圖示, 3秒過去了, 依然還卡在啟動狀態.這是令使用者難以接受的事.
  • 要想解決問題, 首先要對問題有個全面的認識.
  • 下面我們就App啟動流程及啟動優化實操層面, 做一個拋磚引玉的探討, 如有錯誤, 請評論區指正, 先行謝過了:)

1. iOS啟動流程分析

  • App的啟動可以分為兩種
  • 冷啟動(Cold Launch): 從零開始, 點選App圖示啟動App
  • 冷啟動又可以概括為3大階段
    • dyld
    • runtime
    • main.m
  • 熱啟動(Warm Launch): App之前已經啟動好了, 在記憶體中, 後臺執行, 再次點選App圖示啟動App
  • App啟動時間的優化, 主要是針對冷啟動進行.

1.1 列印檢視當前App的啟動時間

  • 可以通過新增XCode環境變數打印出App的啟動耗時
  • Edit scheme -> Run -> Arguments , DYLD_PRINT_STATISTICES設定為1.

1.2 App冷啟動, 流程分析

  • 啟動時間是使用者點選App圖示, 到第一個介面展示的時間.
  • 以main函式作為分水嶺, 啟動時間其實包含了兩部分,
    • main函式之前, 分析並載入動態庫, 註冊需要的類, Category中的方法也會註冊到對應的類中, 執行必要的初始化+load方法
    • main函式到第一個介面的viewDidAppear.
  • 所以, 優化也是從兩個方面進行的, 建議先優化第二部分, 因為絕大多數App的瓶頸在自己的程式碼裡. image.png

2. App冷啟動, 啟動優化策略

2.1 mian函式之前的啟動優化

2.1.1 什麼是dyld?

  • dyld(dynamic link editor), 是Apple的動態聯結器, 可以用來裝載Mach-O檔案(可執行檔案、動態庫等).
  • App冷啟動時, dyld所做的事情有哪些?
  • 裝載App的可執行檔案, 同時會遞迴載入所有依賴的動態庫.
  • 當dyld把可執行檔案、動態庫都裝載完畢後, 會通知runtime進行下一步的處理.

2.1.2 dyld層面的優化方向

  • 減少動態庫的數量
  • 合併動態庫, 比如自己寫的UI控制元件合併成自己的UIKit
  • 確認動態庫是optional還是required.
    • 如果該Framework在當前App支援的所有iOS系統版本都存在,
    • 那麼就設為required, 否則就設為optional, 因為option會有些額外的檢查.

2.1.3 App冷啟動時runtime都做了哪些事?

  1. 呼叫map_images進行可執行檔案內容的解析和處理
  2. load_images中呼叫call_load_methods, 呼叫所有ClassCategory+load方法
  3. 進行各種objc結構的初始化如註冊Objc類、初始化類物件等
  4. 呼叫C++靜態初始化器和__attribute__((constructor))修飾的函式

  5. 到此為止, 可執行檔案和動態庫中所有的符號(Class、Protocol、Selector、IMP等)都已經按格式成功載入到記憶體中, 被runtime所管理.

2.1.4 runtime載入層面的優化方向

  • 合併Category, 如UIView+FrameUIView+AutoLayout合併成一個.
  • 將不必須在+load方法中做的事, 放到+initialize中去做.

2.2 main函式之後的啟動優化

  • main函式開始執行到顯示出第一個頁面, 這段時間做了哪些事?
  • 執行didFinishLaunchingWithOptions方法
  • 初始化Window, 初始化基礎ViewController
  • 獲取資料
  • 展示給使用者
  • 減少建立執行緒, 執行緒不僅有建立時的時間開銷, 還會消耗記憶體, 每個執行緒大約消耗1kb的記憶體空間.
    • 執行緒建立的耗時, 區間範圍在4-5毫秒, 建立執行緒後啟動執行緒的耗時區間為5-100毫秒, 平均在29毫秒.
    • 這是很大的時間開銷, 若在應用啟動時開啟多個執行緒, 則尤為明顯. 多次的上下文切換會帶來開銷.
    • 在開發中避免濫用多執行緒.
  • 合併或者刪減不必要的類/分類/函式, 類越多, 函式越多, 啟動越慢.
  • 在設計師可接受的範圍內, 儘量使用小的圖片.

2.3 AppDelegate中的優化

  • 從AppDelegate先入手優化
  • didFinishLaunchingWithOptionsapplicationDidBecomeActive
  • 優化的核心思想就是, 能延時的延時, 不能延時的儘量放到後臺去優化.
    • 日誌、統計等必須在App已啟動就最先配置的事件, 仍留在didFinishLaunchingWithOptions裡啟動.
    • 專案配置、環境配置、使用者資訊的初始化、推送、IM等事件, 這些功能在使用者進入App首屏之前是必須要載入完的, 放到開屏廣告頁面的viewDidAppear裡.
    • 其他SDK和配置事件, 由於啟動時間不是必須的, 可以放在首屏的viewDidAppear方法裡, 在這裡不會影響啟動時間.
    • 每次用NSLog方式列印會隱式的建立一個Calendar, 因此需要刪減啟動時各業務的log, 或者僅針對內測版輸出log.
    • 儘量不要在didFinishLaunchingWithOptions裡面建立和開啟多執行緒.

3. 總結

  1. App的啟動由dyld主導, 將可執行檔案載入到記憶體, 順便載入所有依賴的動態庫
  2. 並由runtiime負責載入成objc定義的結構
  3. 所有初始化工作結束後, dyld就會呼叫main函式
  4. 接下來就是UIApplictionMain函式, AppDelegate

3.1 dyld階段

  • 減少動態庫、合併一些動態庫定期清理不必要的動態庫
  • 減少Objc類、分類的數量、減少Selector數量, 定期清理不必要的類、分類
  • 減少C++虛擬函式的數量

3.2 runtime階段

  • +initialeze方法和dispatch_once取代所有的__attribute__((constructor))、C++靜態構造器、Objc的+load方法

3.3 main函式階段

  • 在不影響使用者體驗的前提下, 儘可能將一些初始化操作延遲, 不要全部都放在finishLaunching方法中, 做到延遲載入, 按需載入.

發文不易, 喜歡點讚的人更有好運氣👍 :), 定期更新+關注不迷路~

ps:歡迎加入筆者18年建立的研究iOS稽核及前沿技術的三千人扣群:662339934,坑位有限,備註“掘金網友”可被群管通過~