愛伊米

58 同城 App 效能治理實踐-iOS 啟動時間最佳化

導讀

啟動速度是使用者體驗一款 APP 的第一印象,良好的啟動速度對於提升使用者體驗有著積極的作用。58 同城 APP 作為一款承載招聘、安居客、黃頁、二手車等各大業務線的平臺型 APP,複雜的業務啟動邏輯與眾多 SDK 初始化邏輯對 58 同城的啟動治理帶來了不少挑戰。

挑戰與治理思路

啟動治理是一項長期且需要公司全業務線參與的課題,當前58同城在啟動治理上主要面臨以下一些挑戰:

如何準確、穩定地衡量 58 同城 APP 的啟動時間,以及如何橫向比較 58 同城 APP 在業界主流 APP 中的啟動時間?

APP 啟動變慢了,如何快速找出並定位是哪些耗時方法導致的啟動速度降低?

在某個版本進行了啟動最佳化,下個版本的啟動耗時又突然爆發式增長,如何監控各個版本的啟動耗時資料,及早的介入新增版本的啟動耗時最佳化?

為了解決上面的問題,58 同城形成了一套系統的啟動治理思路,首先透過探索自研了一套啟動時間統計工具,包括統計單個 APP 啟動耗時和多個 APP 之間的橫向啟動資料比較,解決啟動時間衡量的問題。

然後,開發一套方法耗時檢測工具,用於檢測啟動過程中的方法耗時資料,方便及時定位跟蹤啟動變慢原因,並基於方法耗時檢測工具,監控各個版本新增的方法耗時資料,在新版本發版前及早介入啟動最佳化工作。

基於分析方法耗時檢測工具生成的啟動資料,我們針對性的優化了啟動邏輯、調研並實踐二進位制重排方案,透過動態庫懶載入方案來最佳化 pre-main 階段。因此,本文將從啟動時間統計工具、啟動方法耗時檢測工具、二進位制重排、動態庫懶載入幾個方向介紹 58 同城 APP 在啟動最佳化上的實踐。下圖展示了58 同城 APP 啟動最佳化的治理思路:

58 同城 App 效能治理實踐-iOS 啟動時間最佳化

啟動時間衡量

1、啟動時間的定義

一般而言,我們把 APP 啟動時間定義為,從點選圖示到使用者看到第一個介面的時間,期間主要包含了兩個階段:

pre-main 階段,從點選圖示到 main 函式執行前:

動態庫載入,包含系統動態庫及自定義動態庫;

Rebase,修正當前映象內部的指標偏移;

Bind,修正不同映象之間的外部指標偏移;

Objc 初始化,包括 Objc 類、Category 的註冊,以及 selector 的唯一性檢查;

Initializer 初始化,每個類和 Category 的 load 方法執行、C/C++ 建構函式呼叫、非基本型別的 C++ 靜態全域性變數初始化;

post-main 階段,main 函式執行到首屏展現:主要執行各種 SDK 註冊、各種業務初始化以及準備首屏渲染需要的資料等邏輯;

2、如何測量啟動時間?

一般來說,單個 APP 的啟動時間測量可以透過下面兩種方案獲取:

直接透過 Xcode 自帶的 Timer Profier 工具進行測量,在xcode11 之後 Instrument 提供了 App Launch 工具,可以看到 pre-main 階段的各個過程的耗時;

分別統計 pre-main 階段和 post-main 階段,其中 pre-main 階段透過設定 Xcode 執行環境來獲取(Project→Scheme→Edit Scheme…,在 Environment Variables 中新增 DYLD_PRINT_STATISTICS=1 的環境變數),post-main 階段可以透過手動埋點的方式來獲取;

啟動時間統計工具

上面兩種方式都需人工進行干預,沒法進行自動化統計,58 同城自研了一種基於讀取手機系統日誌來獲取 APP 啟動時間的自動化工具,透過這個自動化統計工具,我們不僅可以實現單個 APP 啟動時間的獲取,還可以實現多個 APP 之間的橫向啟動資料比較。

我們發現,iOS13。0 以後,在隱私-分析與改進-分析資料中有以log-power-xxx。session命名的日誌檔案,日誌檔案中提供了應用執行的一些基本資料資訊,系統日誌的基本格式如下:

其中,app_bundleid 表示啟動應用的 bundleid,app_performance下的 launch 資訊中就是關於啟動時間的資料,count 表示當天啟動該應用的次數,sessions 分別提供了每次啟動的耗時(從點選圖示到首屏渲染時的耗時)。基於上面的資訊,我們可以獲取到該 app_bundleid 對應的啟動耗時資料,完整分析該日誌檔案,則可以獲取到當日所有啟動過的 APP 耗時資料。

為了能夠自動化統計啟動資料,我們透過一個三方框架來自動啟動指定 APP,在生成系統日誌後,自動分析該日誌檔案,輸出各個 APP 的啟動時間。為了保證啟動資料的穩定,減少實驗結果偏差,蘋果官方推薦我們每次測試時進行如下操作:

重啟手機;

點選其他應用,儘量將該應用在 APP 內的快取給替換掉;

執行多次,去掉偏差較大的值,取平均值;

基於這個原則,我們在每次跑資料時。透過指令碼重啟 SpringBoot,延遲20s 後再去開啟一個 APP。透過啟動時間自動化統計工具獲取的不同 APP 橫向啟動資料如下:

58 同城 App 效能治理實踐-iOS 啟動時間最佳化

獲取線上使用者啟動耗時

上面幾種方式,都算是一種線下的啟動耗時測量方案,對於線上實際使用者的啟動耗時資料獲取,可以透過下面幾種方式進行:

透過 Xcode 自帶工具來檢視,選擇,在左側選單欄選擇 Launch Time 項檢視線上使用者 APP 的啟動耗時資料,這種方式主要看線上使用者整體啟動耗時區間分佈情況;

透過獲取程序資訊,拿到程序建立時間作為啟動初始點,如下程式碼。

這種方式經過實際測試發現,獲取到的程序建立時間偏差較大。

建立一個自定義動態庫(或直接使用已有的自定義動態庫),在 +load 方法中進行埋點作為 APP 的啟動時間,為了儘可能將其他動態庫中的耗時統計到,我們可以將自定義的動態庫放在所有動態庫載入的第一位。

如果我們需要將我們自定義的動態庫放在第一位載入,只需要將其按照-framework“xxx”的格式寫到第一位即可,如在將 LoadHook 這個動態庫按照-framework“LoadHook”這種格式寫在第一位之後,檢視編譯生成的 Mach-O 檔案的 Load Commands 區,可以看到 LoadHook 的確是被編排到第一位載入了。

58 同城 App 效能治理實踐-iOS 啟動時間最佳化

啟動治理實踐

為了能夠快速定位啟動過程中耗時較長的方法,我們調研並實現了一套方法耗時檢測工具,耗時檢測工具包括階段和階段的啟動方法。

1、啟動耗時方法檢測工具

主執行緒中方法執行的快慢會直接影響 APP 啟動的速度,因此,找出啟動過程中那些耗時的方法,對那些耗時較長的方法進行最佳化可以顯著改善啟動時間。對於pre-main階段主要是統計 Initialize 階段的+load方法、C/C++ 建構函式呼叫和非基本型別的 C++ 靜態全域性變數初始化的耗時,post-main階段基本都是我們編寫的業務邏輯。

對於C/C++ 建構函式和非基本型別的 C++ 靜態全域性變數會儲存在 Mach-O __DATA 下的__mod_init_func的section中,而 load 方法的執行是早於C/C++ 建構函式和非基本型別的 C++ 靜態全域性變數的,因此,我們可以在 load 中讀取__mod_init_func這個 section ,拿到__mod_init_func中每個函式的原地址並儲存到一個佇列中,然後將原函式指向為 hook 後的函式地址,這樣我們在 hook 後的函式中從佇列中取出儲存的所有原函式地址並執行,從而獲取到所有C/C++ 建構函式和非基本型別的 C++ 靜態全域性變數的執行時間。

58 同城 APP 在C/C++ 建構函式和非基本型別的 C++ 靜態全域性變數的使用上並不多,總體耗時資料也較少,因此對於 pre-main 階段我們主要關注的是 load 方法的監控與最佳化。

1。1、+load 方法耗時檢測1。1。1、+load 耗時統計的技術方案

我們知道,在類和分類中都可以定義 +load 方法,因此,為了完整統計所有 +load 方法耗時,我們的方案需要考慮到分類中存在 +load 方法的情況。

對於定義了 +load 方法的類和分類,在編譯時會被分別寫入到 Mach-O 的 __DATA 段的__objc_nlclslist和__objc_nlcatlist兩個 section。

58 同城 App 效能治理實踐-iOS 啟動時間最佳化

因此,我們的方案是,首先透過讀取 Mach-O 中 __DATA 段的__objc_nlclslist和__objc_nlcatlist兩個 section,拿到包含 +load 方法的類和分類。

Mach-O 檔案解析可以參考 58 開源專案:WBBlades

拿到含有 +load 方法的類和分類之後進行遍歷,拿到原始 +load 方法的 IMP,透過imp_implementationWithBlock的方式 hook 掉原有 +load 方法的 IMP,在新的 IMP 中,在方法開始和結束位置插入時間統計程式碼,中間去呼叫原有 +load 邏輯。

這樣,透過前、後相減得到每個 +load 方法耗時資料,並將所有資料匯出到 excel 檔案中,並寫入到沙盒檔案。

1。1。2、遇到的一個坑

在讀取 Mach-O 中__objc_nlclslist節的時候,發現讀出了大量__ARCLite__型別的 load 方法,__ARCLite__load是系統庫載入的時候呼叫的,為了避免這個干擾項,我們在讀取的過程中需要跳過__ARCLite__load的處理。

1。1。3、load 耗時最佳化

透過上面方案獲取所有類和分類的 load 方法耗時之後,就可以進行針對性優化了。最佳化的手段一般有下面幾種:

如果時機可以的話,優先使用 +initialize 方法替換 load 方法;

繼續使用 load 方法,但是透過監聽啟動完成後的一個通知,再執行原來的一些耗時邏輯,從而將耗時邏輯儘可能的延後;

另一種方案就是利用 Clang 提供的編譯器函式實現對 Mach-O 的寫能力,透過使用__attribute__((used, section(“__DATA,__wbce_func”)))來標記函式,在編譯期時,編譯器會將標記的資料寫入到指定的 __DATA 段的__wbce_func section中,在執行時,透過讀取 Mach-O 的 __wbce_func 節,取到儲存的函式地址並執行。

這種方式可以快速替換 load 方法,並將原 load 方法中的邏輯移入到啟動過程中的某一個合適的時機去執行。

實際上,獲取到所有 load 資料之後,我們發現,正常一個普通 load 方法是不耗時的,一個耗時的 load 方法主要是在裡面進行了 Method Swizzling、資料儲存等耗時操作。

因此,我們最佳化的主要目標可以集中在那些耗時 load 上,利用 load 耗時檢測工具,我們推動各業務線優化了多個 load 方法的處理,累計最佳化約182ms(iPhone 6S,iOS 13。0 測試裝置),同時做好每個版本 load 的資料監控,防止新版本出現耗時較大的 load 方法。

1。2、OC 方法耗時檢測1。2。1、OC 方法耗時檢測方案

OC 是一種動態語言,方法的呼叫會傳遞物件本身和物件的方法名稱兩個隱藏引數,執行時方法的呼叫過程會交給一個 C 函式 objc_msgSend 來完成,objc_msgSend 會根據傳入的物件和方法的 selector 去查詢對應的函式指標並執行。整個基本流程如下:

58 同城 App 效能治理實踐-iOS 啟動時間最佳化

可以看到,OC 方法的執行必然會經過 objc_msgSend,因此如果我們能 hook 掉 objc_msgSned 方法,也就能攔截到所有 OC 方法的執行過程,這樣我們在原方法的前後插入時間統計程式碼就能夠計算出原方法的執行時間了。

而 objc_msgSend 是一個 C 方法,因此我們可以用 fishhook 進行 hook,蘋果公司為了最佳化 objc_msgSend 的呼叫效能,對 objc_msgSend 使用了彙編進行編碼,因此 hook 後的 objc_msgSend 方法也需要基於彙編進行處理。

由於 objc_msgSend 是變參函式,因此在 hook 後的彙編函式中,先要儲存暫存器中的資料來保護現場,並插入我們自定義的打點方法,用於記錄函式執行的開始時間,同時儲存 LR 暫存器中的資料。然後恢復暫存器的資料,開始原始 objc_msgSend 方法的呼叫,執行完原始 objc_msgSend 方法的呼叫後,再插入我們自定義的打點方法,用於記錄函式執行完成時間,透過前、後時間相差得到原始函式的執行時間。

為了能夠儲存函式呼叫記錄,我們在結束原方法呼叫並插入結束時間打點後,就需要對該方法的呼叫生成一條呼叫記錄儲存下來,同時放到一條呼叫佇列中,主要的資料結構如下:

透過 hook objc_msgSend 拿到各個 OC 方法的呼叫時間資料之後,根據函式呼叫棧為先進後出、而同一層的函式為先進先出的特性,設計相應資料結構以及記錄呼叫深度,來將整個呼叫過程還原為函式呼叫棧的形式。同時,基於最外層方法耗時進行排序,以函式呼叫棧的格式輸出到 Excel 中,輸出結果如下:

58 同城 App 效能治理實踐-iOS 啟動時間最佳化

1。2。2、基於 OC 方法耗時檢測的最佳化效果

基於 OC 方法耗時檢測工具,我們檢測出UserAgent 獲取、音訊預載入、WIFILog、IWatch 連結等幾個耗時處理邏輯,分別透過非同步處理、延遲載入等方式進行了最佳化,對於涉及到業務線部分,推動各業務線進行了相應最佳化,累計減少約450ms(iPhone 6S iOS 13。0 測試裝置)。

1。3、基於方法耗時檢測工具在版本監控上的應用

方法耗時檢測工具包含 pre-main 階段的 load 耗時檢測和整個 post-main 階段方法檢測,然後輸出對應的耗時資料報表,從而找到那些耗時方法。另外,為了解決每個版本可能新增較大耗時方法的痛點,檢測工具還應用在了各版本啟動耗時監控上,我們在每個版本整合之後,跑一次最新版本的啟動耗時資料,與上一版本進行對比,及時監控每個版本新增的啟動耗時方法。

每個版本在方法耗時檢測工具的監控下,我們能夠及時發現和最佳化新增的耗時方法。為了進一步最佳化啟動耗時,我們調研並實踐了二進位制重排方案,以及為最佳化 pre-main 階段我們實現了動態庫懶載入方案。

2、二進位制重排實踐

二進位制重排的關鍵是獲取啟動過程中的符號,目前業界常用方案有:

基於靜態掃描+執行時 trace 的方案來獲取啟動時的符號,從而生成 order file 檔案實現二進位制重排;

基於 Clang 靜態插樁的方式來獲取啟動過程中的所有函式符號;

第一種方案存在對於initialize、block、以及 C++ 函式hook 不到的問題,第二種基於 Clang 靜態插樁的方案則可以解決前種方案的不足獲取到所有符號。因此,58 同城選擇了基於 Clang 靜態插樁的方案來獲取啟動符號。

2。1、虛擬記憶體與 Page Fault

早期計算機中,並沒有設計虛擬記憶體,程式都是直接從磁碟按序完整地載入進物理記憶體中,這種方式由於使用的是真實物理記憶體地址且程式是有序載入進去的,那麼透過計算地址偏移就可以訪問到其他程式的記憶體,存在安全隱患,另外由於是完整載入,而使用者實際使用時只會用到少部分功能,這樣也會造成記憶體的極大浪費。

為了解決這些問題,現在的作業系統在物理記憶體的基礎上引入了虛擬記憶體的概念。虛擬記憶體引入後,每個程序可以認為自己擁有從0x000000~0xffffff這一大片連續的記憶體空間,只不過這個記憶體地址是虛擬的,要訪問實際物理記憶體地址,需要透過作業系統維護的一張對映表對映之後才可以真正訪問到,而對映表是以頁(Page)為單位進行管理的。

58 同城 App 效能治理實踐-iOS 啟動時間最佳化

當程序要訪問的一個虛擬記憶體頁在經過對映表對映之後發現對應的物理記憶體頁不存在時,會觸發一次缺頁中斷Page Fault,此時會發生 I/O 操作,將磁碟中的資料讀入到物理記憶體頁中,讀取的過程中蘋果還會對讀入的記憶體頁進行驗籤處理,因此如果頻繁發生Page Fault的話,Page Fault產生的耗時也不可小覷。Page Fault的數量可以透過 Instruments 自帶的 System Trace 工具來檢視,其中File Backed Page In就是Page Fault的次數。

58 同城 App 效能治理實踐-iOS 啟動時間最佳化

2。2、二進位制重排最佳化原理

APP 啟動過程中,會載入大量的類、執行大量的方法,當頻繁觸發Page Fault的話,對啟動耗時會產生不小的影響,因此儘可能減少Page Fault的數量可以最佳化啟動耗時。

當啟動過程中需要呼叫的兩個方法method1和method2分佈在不同的記憶體頁時,此時作業系統需要觸發兩次缺頁中斷Page Fault,來載入這兩頁到記憶體,如果透過一定的技術手段將這兩個方法排列在同一個記憶體頁中,那此時系統只需要觸發一次缺頁中斷即可,如果能夠減少一定數量的缺頁中斷次數,那也就能夠減少整體啟動耗時。

58 同城 App 效能治理實踐-iOS 啟動時間最佳化

因此,二進位制重排的一個核心問題就是如何將不同的方法儘可能地排列在同一個記憶體頁中。

生成一個二進位制的 Mach-O 檔案,需要經過編譯、連結的過程,Xcode 使用 ld 作為連結器,ld 連結器的配置中有一個名為Order File的引數,它可以配置一個 order 檔案路徑。

一個 order 檔案記憶體儲的是符號列表,當我們配置了 order 檔案之後,ld 在工作的時候就會根據 order 檔案中的符號按照順序進行排列生成二進位制檔案。

因此,如果我們將啟動過程中呼叫的函式符號都找到,並配置到 order 檔案中,那生成的二進位制檔案在啟動時所呼叫的方法都會盡量排在相同且相鄰的記憶體頁上,從而減少啟動過程中發生Page Fault的次數,減少因Page Fault而產生的耗時。

因此,現在的關鍵是找到啟動過程中呼叫的函式符號。透過 hook objc_msgSend 能夠拿到 OC 方法的呼叫,但是對於load方法、C++ 建構函式還需要透過掃描 Mach-O 檔案來獲取,還有一種方案是基於 Clang 編譯期插樁來獲取符號,Clang 插樁可以一次獲取 OC、Swift、C、block 函式符號,因此,58 APP 採用的就是基於 Clang 插樁來實現符號收集。

2。3、Clang 插樁收集啟動過程中的函式符號

基於 Clang 插樁獲取符號有兩種實現方式:

一種是自己編寫一個 Clang 外掛,在 Clang 外掛中我們去分析抽象語法樹不同的節點,在相應的節點中插入自定義的程式碼用於符號收集,這種自定義 Clang 外掛的方式優點是可根據自己需求進行靈活處理,缺點是通用性較差,

一種是利用 SanitizerCoverage 工具進行符號收集。

SanitizerCoverage 是 LLVM 內建的一個程式碼覆蓋率檢測工具,在編譯時,它能夠根據我們的編譯配置,將一系列以__sanitizer_cov_trace_pc_為字首的函式插入到我們自定義的函式內,比如,我們在Clang的自定義配置 Other C Flags中新增-fsanitize-coverage=trace-pc-guard標誌時,編譯器將會為每個自定義的函式中插入__sanitizer_cov_trace_pc_guard回撥函式。

Clang 靜態插樁收集符號的原理就是,利用編譯期在每一個函式內部插入回撥函式,我們透過實現該函式,在執行期間就能夠拿到被插入該函式的原函式地址,透過函式地址解析出函式符號,從而達到收集啟動過程中函式符號的目的。

因此,為了 Clang 前端能夠利用 SanitizerCoverage 插入插樁函式,我們首先需要在Other C Flags 中新增-fsanitize-coverage=trace-pc-guard配置,這樣在編譯後,我們的自定義函式中都會被插入__sanitizer_cov_trace_pc_guard函式,然後我們需要實現該回調函式,並在回撥函式內部收集原函式符號:

函式__sanitizer_cov_trace_pc_guard是在編譯期由 Clang 插入到原函式內部的,因此__sanitizer_cov_trace_pc_guard函式算是原函式內部的一個巢狀子函式,而作業系統在執行 bl 跳轉指令的時候,會先儲存下一條指令地址到lr暫存器中,當__sanitizer_cov_trace_pc_guard函式執行完即執行ret指令後,需要繼續回到原函式中繼續執行,作業系統會去讀取 LR 暫存器中的值拿到原函式的下一條待執行指令地址,這個地址可以透過下面程式碼來獲取:

也就是說,在__sanitizer_cov_trace_pc_guard函式中我們可以透過 __builtin_return_address(0) 拿到原函式某條指令的地址,那我們只要再透過 dladdr() 函式就可以獲取到原函式的資訊,從而拿到該函式符號。

在實際的使用過程中,需要解決以下幾個主要問題:

多執行緒問題,由於__sanitizer_cov_trace_pc_guard函式是各個方法內插入的回撥函式,而原函式可能處於不同的執行緒中,從而造成__sanitizer_cov_trace_pc_guard函式呼叫的多執行緒問題,解決這個問題可以使用原子佇列 OSAtomicEnqueue 來處理,使用原子佇列之後需要在 Other C Flags 配置中修改原來的配置為如下形式:

-fsanitize-coverage=func,trace-pc-guard

如果要支援 Swift 符號收集,由於 Swift 的編譯前端與 OC 不同,需要在編譯配置的Other Swift Flags下,新增下面配置:

-sanitize-coverage=func

-sanitize=undefined

使用 Cocoapods 管理的專案,存在多 target 的情況下,需要在每個 target 下都要進行上面的Other C Flags配置。

收集到啟動過程中的函式符號之後,將這些符號寫入到 order 檔案中,並將該 order 檔案的地址在 Xcode 的Order File引數下進行配置即可。

2。4、二進位制重排前後的效果對比

儘量完全冷啟動 APP 進行多次試驗,二進位制重排前、後對比的缺頁中斷次數如下:

可以看到,二進位制重排最佳化後,缺頁中斷的次數減少約1626次,耗時減少約162ms(iPhone 6S測試裝置)。

3、動態庫懶載入

我們知道,pre-main 過程中,有dylib的載入步驟,而動態庫載入是需要耗時的,蘋果建議我們自定義的動態庫不要超過 6 個,因此,儘量減少啟動過程中的動態庫載入有助於啟動耗時的最佳化。減少啟動過程中的動態庫載入主要有以下兩個方案:

一個是動態庫轉靜態庫;

一個是多個動態庫進行合併;

上面兩種方案都可行,但是在實際工程操作中可能存在轉換繁瑣,需要解決部分依賴的問題。iOS 58 同城 APP 採用了一種動態庫懶載入的方案,來減少啟動過程中需要載入的動態庫數量,從而達到最佳化啟動耗時目的。

3。1、動態庫懶載入方案

所謂動態庫懶載入是指,在啟動的過程中並不載入該動態庫,而是在業務真正使用到該動態庫中的內容時才進行載入,從而減少啟動耗時。在 Cocoapods 1。2 之前存在配置動態庫懶載入的入口,升級到 1。8 之後沒有了動態庫懶載入的配置入口,我們需要在pod install之後生成的配置檔案中進行配置。

使用 Cocoapods 管理的專案,在pod install之後,會生成Pods-xxx-frameworks。sh和Pods-xxx。adhoc/debug/release。xcconfig這兩個檔案,其中Pods-xxx-frameworks。sh檔案指令碼負責架構剔除和重簽名等功能,而xxx。adhoc/debug/release。xcconfig檔案則負責靜態庫和動態庫的連結配置,我們自定義的動態庫想要進行懶載入,只需要修改配置檔案,將需要懶載入的動態庫從配置檔案中移除,這樣保證懶載入的動態庫參與簽名和複製,但是不參與連結。

3。2、動態庫懶載入後的呼叫方式

由於採用動態庫懶載入後動態庫在編譯時沒有參與連結,原有的程式碼呼叫方式會報找不到對應動態庫符號的錯誤,因此,原有動態庫的呼叫方式需要修改成Runtime動態呼叫的形式,在使用某個動態庫中的類時,先動態獲取該類,如果獲取不到,則透過dlopen的方式動態載入該動態庫:

動態庫懶載入後,將減少啟動過程中dlopen帶來的損耗,同時減少 rebase/bind 的時間,以及避免了該懶載入動態庫內 load、contructor 等函式在啟動過程中執行。

3。3、有益效果

目前,58 同城已經有 12 個動態庫 採用了懶載入的方式引入,採用懶載入後在啟動速度方面減少了約 817ms(iPhone 6P iOS 12。0),同時,後續將會逐步將更多的靜態庫轉成動態庫懶載入的方式進行接入。

總結與展望

本文首先介紹了 58 同城基於手機系統日誌獲取啟動時間方案自研的橫向啟動時間對比統計工具,然後介紹了方法耗時檢測工具、二進位制重排和動態庫懶載入三個方向在 58 同城 APP 啟動最佳化上的一些實踐經驗,方法耗時檢測工具已應用在持續監控每個版本的啟動耗時資料上,同時方法耗時檢測工具已應用在 58 同鎮本地版 APP 上。下一階段,我們將持續最佳化耗時檢測工具並重點關注 pre-main 階段的最佳化。

參考文獻:

[1] Clang 12 documentation:https://clang。llvm。org/docs/SanitizerCoverage。html

[2] WBBlades:基於Mach-O檔案解析的APP分析工具:https://mp。weixin。qq。com/s/HWJArO5y9G20jb2pqaAQWQ

[3] 基於二進位制檔案重排的解決方案 APP啟動速度提升超15%:https://mp。weixin。qq。com/s/Drmmx5JtjG3UtTFksL6Q8Q

[4]App 啟動速度怎麼做最佳化與監控?:https://time。geekbang。org/column/article/85331

[5] 監控所有的OC方法耗時:https://juejin。cn/post/6844903875804135431

作者簡介:

廖露陽,58 同城 – 平臺技術部 – iOS 技術部 高階研發工程師

樸惠姝,58 同城 – 平臺技術部 – iOS 技術部 高階研發工程師

鄧竹立,58 同城 – 平臺技術部 - iOS 技術部 資深研發工程師