iOS啟動優(yōu)化

希望總結(jié)項(xiàng)目中的啟動優(yōu)化,詳細(xì)學(xué)習(xí)iOS啟動流程

1. 想要對App進(jìn)行啟動優(yōu)化需要優(yōu)先了解App的啟動時(shí)間,和個(gè)個(gè)啟動過程中的時(shí)間占比(主要講解Pre-main之前的過程)

  • Xcode 13.0 beta / iOS 15.0之前比較方便我們可以在 Xcode 中配置環(huán)境變量 DYLD_PRINT_STATISTICS 為 1(Edit Scheme → Run → Arguments → Environment Variables → +)。
截屏2023-07-22 22.18.59.png
  • Xcode 13.0 beta / iOS 15.0之后這個(gè)方法失效了蘋果推薦我們使用instrument工具進(jìn)行監(jiān)測
截屏2023-07-22 22.20.51.png

2. 了解了方法后我們可以通過打印看到我們每個(gè)過程消耗的時(shí)間,此處有幾個(gè)關(guān)鍵指標(biāo)

  • dylib loading (動態(tài)庫加載)

  • rebase/binding (偏移修正/符號綁定)

rebase(偏移修正):任何一個(gè)app生成的二進(jìn)制文件,在二進(jìn)制文件內(nèi)部所有的方法、函數(shù)調(diào)用,都有一個(gè)地址,這個(gè)地址是在當(dāng)前二進(jìn)制文件中的偏移地址。一旦在運(yùn)行時(shí)刻(即運(yùn)行到內(nèi)存中),每次系統(tǒng)都會隨機(jī)分配一個(gè)ASLR(Address Space Layout Randomization,地址空間布局隨機(jī)化)地址值(是一個(gè)安全機(jī)制,會分配一個(gè)隨機(jī)的數(shù)值,插入在二進(jìn)制文件的開頭),例如,二進(jìn)制文件中有一個(gè) test方法,偏移值是0x0001,而隨機(jī)分配的ASLR是0x1f00,如果想訪問test方法,其內(nèi)存地址(即真實(shí)地址)變?yōu)?ASLR+偏移值 = 運(yùn)行時(shí)確定的內(nèi)存地址(即0x1f00+0x0001 = 0x1f01)

  • Objc setup (Objc相關(guān)類的注冊,selector唯一性檢查)

dyld調(diào)用的objc_init方法,這個(gè)是runtime的初始化方法,在這個(gè)方法里面主要的操作就是加載類(對需要的class和category進(jìn)行注冊),objc_init方法通過內(nèi)部的_dyld_objc_notify_register向dyld注冊了一個(gè)通知事件,當(dāng)有新的image(程序中對應(yīng)實(shí)例可簡稱為image,如程序可執(zhí)行文件macho,F(xiàn)ramework,bundle等)加載到內(nèi)存的時(shí)候,就會觸發(fā)load_images方法,這個(gè)方法里面就是加載對應(yīng)image里面的類,并調(diào)用load方法(在下一階段initializer),如果有繼承的類,那么會先調(diào)用父類的load方法,然后調(diào)用子類的,但是在load里面不能調(diào)用[super load]。最后才是調(diào)用category的load方法??傊?,所有的load都會被調(diào)用到(注意:子類的initialize方法會覆蓋父類,不同于load方法)

  • initializer (初始化執(zhí)行l(wèi)oad方法,創(chuàng)建靜態(tài)全局變量等)

3. 介紹幾個(gè)概念

1.Mach-O文件
Apple出品的操作系統(tǒng)的可執(zhí)行文件格式幾乎都是mach-o,iOS當(dāng)然也不例外。
mach-o可以大致的分為三部分:

  • Header 頭部,包含可以執(zhí)行的CPU架構(gòu),比如x86,arm64
  • Load commands 加載命令,包含文件的組織架構(gòu)和在虛擬內(nèi)存中的布局方式
  • Data,數(shù)據(jù),包含load commands中需要的各個(gè)段(segment)的數(shù)據(jù),每一個(gè)Segment都得大小是Page的整數(shù)倍。
image.png

2.Virtual Memory
虛擬內(nèi)存是建立在物理內(nèi)存和進(jìn)程之間的中間層。在iOS上,當(dāng)內(nèi)存不足的時(shí)候,會嘗試釋放那些只讀的Page,因?yàn)橹蛔x的Page在下次被訪問的時(shí)候,可以再從磁盤讀取。如果沒有可用內(nèi)存,會通知在后臺的App(也就是在這個(gè)時(shí)候收到了memory warning),如果在這之后仍然沒有可用內(nèi)存,則會殺死在后臺的App。

  • 虛擬內(nèi)存是在物理內(nèi)存上建立的一個(gè)邏輯地址空間,它向上(應(yīng)用)提供了一個(gè)連續(xù)的邏輯地址空間,向下隱藏了物理內(nèi)存的細(xì)節(jié)。
  • 虛擬內(nèi)存使得邏輯地址可以沒有實(shí)際的物理地址,也可以讓多個(gè)邏輯地址對應(yīng)到一個(gè)物理地址。
  • 虛擬內(nèi)存被劃分為一個(gè)個(gè)大小相同的Page(64位系統(tǒng)上是16KB),提高管理和讀寫的效率。 Page又分為只讀和讀寫的Page。

3.Page fault
在應(yīng)用執(zhí)行的時(shí)候,它被分配的邏輯地址空間都是可以訪問的,當(dāng)應(yīng)用訪問一個(gè)邏輯Page,而在對應(yīng)的物理內(nèi)存中并不存在的時(shí)候,這時(shí)候就發(fā)生了一次Page fault。當(dāng)Page fault發(fā)生的時(shí)候,會中斷當(dāng)前的程序,在物理內(nèi)存中尋找一個(gè)可用的Page,然后從磁盤中讀取數(shù)據(jù)到物理內(nèi)存,接著繼續(xù)執(zhí)行當(dāng)前程序。

4.改善APP的啟動

-在 dylib loading的過程中,會去裝載app使用的動態(tài)庫,而每一個(gè)動態(tài)庫有它自己的依賴關(guān)系,所以會消耗時(shí)間去查找和讀取,Apple官方建議盡量少的使用自定義的動態(tài)庫,或者考慮合并多個(gè)動態(tài)庫,其中一個(gè)建議是當(dāng)大于6個(gè)的時(shí)候,則需要考慮合并它們。簡單的舉個(gè)例子比如使用cocoapods管理的多個(gè)自定義的UI組件可以合并成一個(gè)自己的UIKIT,同時(shí)也建議動態(tài)庫轉(zhuǎn)靜態(tài)庫

-減少+load的方法使用,+load方法中盡量少做耗時(shí)操作,+load中代碼延遲到 main 之后子線程處理或者首頁顯示之后;改為 initialize 中執(zhí)行,針對 initialize 中處理需要注意的是分類 initialize 會覆蓋主類 initialize 以及有子類后 initialize 執(zhí)行多次的問題,需要使用 dispatch_once 來保證代碼只執(zhí)行一次;

-二進(jìn)制重排要實(shí)現(xiàn)符號的重排,一是需要我們收集整個(gè)啟動鏈路上的方法和函數(shù)等符號,二是需要生成對應(yīng)的 order 文件來配置 ld 中的 Order File 屬性。當(dāng)工程在編譯的時(shí)候,Xcode 會讀取這個(gè) order 文件,在鏈接過程中會根據(jù)這個(gè)文件中的符號順序來生成對應(yīng)的 MachO。一般業(yè)界中收集符號的方案有兩種:
1.Hook objc_msgSend,只能拿到 OC 以及 swift @objc dynamic 的符號;
2.Clang 插樁,能完美拿到 OC、C/C++、Swift、Block 的符號;
第二種方法實(shí)現(xiàn)成本較高
采用第一種方法在編譯完成后通過驗(yàn)證 LinkMap 文件中 #Symbols: 部分符號順序是否和 order 文件中的符號順序一致來確定是否配置成功即可

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容