基本概念
虛擬內存&物理內存
- 早期的數據訪問是直接通過物理地址訪問的,這種方式有以下兩種問題
- 內存不夠用
- 內存數據的安全問題
內存不夠用解決方案:虛擬內存
- 我們在進程和物理內存之間增加一個中間層,這個中間層就是所謂的虛擬內存,主要用於解決當多個進程同時存在時,對物理內存的管理。提高CPU的使用率,使多個進程可以同時按需求加載,而虛擬內存的本質是一張虛擬地址亂以地址的對應關係映射表。
- 每個進程都有一個獨立的虛擬內存。其地址都是從0開始,大小是4G固定的,每個虛擬內存又會劃分一個一個的頁(頁的大小在iOS中是16K,其他是4K),每次加載都是以頁為單位加載的,進程間無法互相訪問的,也保證了數據的安全性。
- 一個進程中,只有部分功能是活躍的,所以只需要
將進程中活躍的部分放入物理內存
,避免物理內存的浪費 - 當CPU需要訪問數據時,首先是訪問虛擬內存,然後通過虛擬內存去尋址,即可以理解為在表中找對應的物理地址然後相對應的物理地址進行訪問
- 如果再訪問時,虛擬地址的內容為加載到物理內存,會發生
缺頁異常(pagefault)
,將當前進程阻塞掉,此時需要先將數據載入到物理內存,然後在尋址,進行讀取。這樣就避免了內存浪費
如下圖所示,虛擬內存與物理內存間的關係
123.png
內存數據的安全問題:ASLR技術
- 我們了解到虛擬內存的起始地址與大小都是固定的,意味著,當我們訪問數據地址時也是固定的,這會導致我們的數據非常容易被破解,為了解決這個問題,所以蘋果為了解決這個問題,在iOS4.3開始引入ASLR技術。
- ASLR概念(Address Space Layout Randomization )
地址空間配置隨機加載
,是一種針對緩衝區溢出
的安全保護技術
,通過對堆,棧,共享庫映射等線性區佈局的隨機化,通過增加攻擊者預測目的的地址的難度,防止攻擊者直接定位攻擊代碼位置,達到阻止溢出攻擊的目的的一種技術。其目的是通過利用隨機方式配置數據地址空間
,使某些敏感數據(例如APP登入註冊,支付相關代碼)配置到一個惡意程序無法事先獲知的地址,令攻擊者難以進行攻擊 - 由於ASLR的存在,導致可執行文件和動態鏈接庫在虛擬內存中的加載地址每次啟動都不固定,所以需要在編譯時來修復鏡像中的資源指針,來指向正確的地址,即
正確的內存地址= ASLR + 偏移值
優化建議
冷啟動/熱啟動
- 從用戶
點擊app的icon
到AppDelegate的didFinishLaunching
方法執行完成為止稱之為啟動的過程
,然而啟動也分為了冷啟動和熱啟動。-
冷啟動
:內存中不包含app相關數據的啟動,一般我們可以通過重啟手機來實現冷啟動。 -
熱啟動
:當app退到後台(即按下home鍵),app還存在一段時間,這時點擊app馬上能恢復到原狀態(即數據還在內存),這種稱之為熱啟動。
-
- 接下來探討的啟動優化則是屬於
冷啟動
,這個啟動主要分為兩個部分- T1:pre-main階段,即main函數之前,操作系統加載App可執行文件到內存,執行一系列的加載以及鏈接等工作,簡單來說,就是dyld加載過程。
- T2:main函數之後,即從main函數開始,到Appdelegate的
didFinishLaunching
方法執行完成為止主要是構建第一個介面,並完成渲染。
所以,T1+T2的過程就是從用戶點擊App圖標到用戶能看到App主界面的過程,即需要啟動優化的部分。
pre-main階段的優化
- 在dyld加載過程中,已經了解過了dyld加載流程。pre-main階段的啟動時間其實就是dyld加載過程的時間
- 對於main函數之前的啟動時間,平過提供了內建的檢測方法,Edit Scheme → Run → Argument → Enviroment Variables點擊+添加環境變量
DYLD_PRINT_STATISTICS
,然後運行 以下是iPhone8plus 正常冷啟動的pre-main時間(以Wechat為例)。
內容說明
pre-main階段總共用時1.7s
-
dylib loading
time (動態庫耗時) : 主要是加載動態庫共使用了175.11ms -
rebase/binding
time(偏移修正/符號綁定耗時),耗時160.52ms-
rebase(偏移修正)
:任何一個app生成的二進制文件,在二進制文件內部所有的方法,函數調用,都有一個地址,這個地址是在當前二進制文件中的偏移地址
。一旦在運行時刻(即運行到內存中),每次系統都會隨機分配一個ASLR(Address Space Layout Randomization,地址空間隨機加載化
)地址值(是一個安全機制,會分配一個隨機的數值,插入在二進制文件的開頭),例如,二進制文件中有一個test方法,偏移值是0x0001,而隨機分配的ASLR是0x1ff0,如果想訪問test方法,其內存地址(真實地址)變為ASLR+偏移值 = 運行時確定的內存地址(即0x1ff0+0x0001=0x1ff1) -
binding(綁定)
:例如NSLog
方法,在編譯時期生成的mach-O文件中,會創建一個符號!NSLog
(指向一個隨機地址),在運行時(從瓷盤加載到內存中,是一個鏡像文件),會將真正的地址給符號(即在內存中將地址與符號進行綁定,是dyld
做的,也稱為動態庫符號綁定
),一句話概括:綁定就是符號賦值的過程
-
-
Objc setup
time (OC類註冊的耗時):OC類越多,越耗時 -
initializer
time (執行load和構造函數的耗時)
針對以上幾點,有以下優化建議
- 盡量少用外部動態庫,蘋果官方建議自定義的動態庫最好不要超過六個,如果超過六個需要合併動態庫
- 減少OC類,因為OC類越多,越耗時
- 將不必須在+load方法中做的事情延遲到+initialize中,盡量不要用c++虛函數
- 如果是swift,盡量使用
struct
main函數階段的優化
- 在main函數之後的
didFinishLaunching
方法中,主要執行了各種業務,有很多並不是必須在這裡立即執行,這種業務我們可以採取延遲加載,防止影像啟動時間 - didFinishLaunching中的業務主要分為三個類型
- 第一類:初始化第三方sdk
- 第二類:app運行環境配置
- 第三類:自己工具類的初始化
main函數階段的優化建議主要有以下幾點:
- 減少啟動初始化的流程,能懶加載的懶加載,能延遲的延遲,能放後台初始化的放後台,盡量不要佔用主線程的啟動時間。
- 優化代碼邏輯,去除非必須的代碼邏輯,減少每個流程的消耗時間
- 啟動階段能
使用多線程
來初始化的,就使用多線程 - 盡量
使用纯代碼
來進行UI框架的搭建,尤其是主UI框架,例如UITabBarController。盡量避免使用Xib或者Storyboard,相比純代碼而言,這種更耗時 - 刪除廢棄的類和方法