
本篇是記錄一下自己的學習筆記,如有勘誤望見諒.
先放上本文的學習來源鏈接,WWDC 16 和17, 19年 的三個session
優(yōu)化啟動時間 https://developer.apple.com/wwdc16/406
app啟動時間:過去,現(xiàn)在,與未來 https://developer.apple.com/wwdc17/413
優(yōu)化App啟動 https://developer.apple.com/wwdc19/423
關于Mach-O和虛擬內(nèi)存
Execuable(運行時 可執(zhí)行文件的文件類型)
可執(zhí)行文件 應用里最重要的二進制文件 也是應用擴展文件的主二進制文件
Dylib(動態(tài)庫)
Dylib是一個動態(tài)庫 在其他平臺上 用的是你可能會熟悉的名字: DSO和DLL
Bundle(捆綁包)
捆綁包是一種特殊的Dylib 是不能進行鏈接的 只能在運行時 用dlopen()函數(shù)打開它
Image(這里image是指任意上述三種文件)
例如 Dylib,可執(zhí)行文件
FarmeWork(框架)
這里指 存儲該Dylib需要的文件
Mach-O圖像格式

所有的段名都是由大寫字母組成
每一段都是頁面大小的倍數(shù) 圖片該例中
TEXT段大小是3頁
DATA和LINKEDIT段大小是1頁
頁面大小由硬件決定 arm64處理器的頁面大小是16K, 其他都是4K
一種查看方式是分區(qū) 編譯器常常會忽略分區(qū) 分區(qū)是段的子范圍

分區(qū)不用遵循頁面的大小 但是它們是不重疊的
最常見的段名是 TEXT DATA LINKEDIT 實際上幾乎每一個二進制文件 都包含這三段
TEXT是文件的開頭 它包含了Mach的頭文件 任何機器指令 以及任何只讀常量 比如C字符串
DATA段是重寫段 它包含了所有的全局變量
LINKEDIT段 它不包含全局變量的函數(shù) 它包含變量函數(shù)信息 比如名稱和地址

虛擬內(nèi)存
虛擬內(nèi)存解決的問題是 所有這些進程存在時 該如何管理所有物理內(nèi)存? 所以他們添加了一個小的間接層 每一個進程都是一個邏輯地址空間 映射到RAM的某個物理頁面.
這種映射不一定是一對一的 邏輯地址可以不對應任何物理RAM 也可以多個邏輯地址對應 同一物理RAM 這樣帶來很多種可能 能利用虛擬內(nèi)存做什么呢? 首先 如果有一個邏輯地址 不映射任何物理RAM 當進程要訪問該地址時 就會產(chǎn)生頁面錯誤 內(nèi)核將停止該線程 并試圖找出解決方案
copy on write
內(nèi)存寫時復制. 在所有進程里共享DATA頁面
只要進程是只讀, 共享內(nèi)容的全局變量, 但是一旦有進程想要 寫入其DATA頁面 寫入時復制開始 內(nèi)核會把該頁面復制 放入另一個物理RAM并重定向映射 所以該進程有了該頁面的副本

Dylib是如何操作
Dylib必須要做的第一件事 是查看Mach頭文件 在內(nèi)存里 在該進程里 它將查看內(nèi)存的頂盒, 由于虛擬內(nèi)存的原因, 啟動時那里是空的 沒有內(nèi)容映射到物理頁面上 所以產(chǎn)生頁面錯誤 到那時內(nèi)核意識到 它被映射到了一個文件 所以它將讀取文件的第一頁放入物理RAM設置其映射 ,
從而使得Dylib可以真正通過 Mach頭文件開始讀取
dyld實際上 將進程里面的虛擬內(nèi)存的 text,data,LINKEDIT 實際的加載到指定的進程中去
App的啟動流程 exec() 到main ()


Dyld 的工作流程 pro-main 的過程

先分析一下啟動的時候,發(fā)生了什么,
loadDylbs-> bebase -> binding -> initializes -> main
其實啟動的步驟就是這幾步

Rebase/Binding 環(huán)節(jié)的優(yōu)化
在講rebase之前,我們需要先講一下ASLR
ASLR(Address Space Layout Randomization),地址空間布局隨機化。在ASLR技術出現(xiàn)之前,程序都是在固定的地址加載的,這樣hacker可以知道程序里面某個函數(shù)的具體地址,植入某些惡意代碼,修改函數(shù)的地址等,帶來了很多的危險性。ASLR就是為了解決這個的,程序每次啟動后地址都會隨機變化,這樣程序里所有的代碼地址都需要需要重新對進行計算修復才能正常訪問。
代碼地址都需要需要重新對進行計算修復才能正常訪問。
出于這個原因,所以我們有了Rebase/Binding
Rebase
什么是Rebase?
rebasing:主要就是調(diào)整鏡像內(nèi)部指針的指向。
Binding
Binding: 將指針指向鏡像外部的內(nèi)容。
針對這個環(huán)節(jié)的優(yōu)化主要還是以下.
減少OC類的數(shù)量,合并大多數(shù)的模型類,屬性,CATEGORY
使用C++ Virtual 的虛函數(shù)
使用Swift 的結構體
為了縮短啟動時間 可以采用的方法有
減少已有dylib的數(shù)量.
減少已有ObjC類的數(shù)量 以及刪除靜態(tài)初始化器
還有可用更多Swift語言加快速度 因為Swift真的很強大
Swift有全局變量 并且會被初始化 它們確保在使用前被初始化 但是其方法不是用初始化器 在后臺 使用一次dispatch_once() 使用了一種調(diào)用點初始化器 所以轉(zhuǎn)為Swift語言 將會做到這一點
最后 不鼓勵使用dlopen() 它會帶來細微的性能問題 很難診斷
使用Instruments 去檢測啟動時間
screenshot.png
dyld 的預綁定
我們使用預綁定技術 為系統(tǒng)中的所有dylib和你的程序 找到固定地址 動態(tài)加載器將會 加載這些地址的所有內(nèi)容 如果成功 將會編輯 所有這些二進制數(shù)據(jù) 以獲得所有預計算地址 然后下次 當它將所有數(shù)據(jù)放入相同地址時 不必進行任何其它額外的工作 這會大幅提高速度
但是這也意味著 每次啟動時會編輯你的二進制數(shù)據(jù) 這并不是很好的做法 至少從安全性來說是如此
dyld2
dyld 2是dyld的 完全重寫版本
正確支持C++初始化器語義, 擴展mach-o格式 并且更新dyld 從而獲得高效率的C++庫支持
它具有完整的本機dlopen 和dlsym實現(xiàn) 具有正確的語義 棄用了舊版API 這些舊版API仍然位于macOS中 沒有加入到任何其它平臺上
dyld的設計目標是提高速度
dyld3

dyld3的特點是進程外的且有緩存的,啟動app前把很多耗時操作提前處理好了。據(jù)統(tǒng)計,在冷啟動時,dyld3比dyld2快20%。
Dyld3分為out-of-process,和in-process。
out-process會做:
分析Mach-O Headers
分析以來的動態(tài)庫
查找需要的Rebase和Bind的符號
將上面的分析結果寫入緩存。
in-process會做:
讀取緩存的分析結果
驗證分析結果
加載Mach-O文件
Rebase&Bind
Initializers
dyld的東西比較深奧,建議大家再多去找找資料,dyld3核心就是在app啟動之前就已經(jīng)將二進制加入到緩存中,從而提升速度的.
這里差不多也看完了2017 session 的啟動優(yōu)化, 核心是dyld3的優(yōu)化,能夠在開發(fā)者這邊操作的注意事項與2016 大多數(shù)也是通用的
1.減少動態(tài)庫的依賴
2.減少指針的使用。(指針地址對齊的操作消耗時間)
- 優(yōu)化Objc建立時間。這一步包含四個步驟:
3.1注冊類
3.2更新類實例變量偏移(例如SDK更新)
3.3注冊Category
3.4 選擇器的唯一性
4: 初始化。在iOS平臺下,如果項目使用Objc編寫,盡量少使用+load方法,如果非要使用, 替換為+initialize,延遲加載。如果項目已經(jīng)使用Swift編寫,那就沒什么優(yōu)化的了,Apple暗地里幫我們調(diào)用了他們自己的initializer(dispatch_once), 確保Swift class不會被初始化多次。
關于優(yōu)化第一幀
優(yōu)化個人業(yè)務
在最小化工作時 應該推遲與 生成第一幀無關的任何內(nèi)容
這意味著推遲 未顯示的視圖或尚未使用的 預加熱功能等內(nèi)容
還應該避免阻塞主線程 比如網(wǎng)絡 I/O 文件 I/O 或其他
因為這會影響啟動 將其移動到后臺線程
應該注意 減少內(nèi)存使用量分配和操作 內(nèi)存可能需要時間 接下來 優(yōu)先工作
使用Instrment 去檢測UI

如圖所示,根據(jù)時間定位耗時的代碼.

本篇是自己看完wwdc的筆記式的內(nèi)容.三篇session部分內(nèi)容重復, 后面有時間再進行整理. 多謝觀看
