iOS【啟動優(yōu)化】

冷啟動 VS 熱啟動

04.png
05.png

如果你剛剛啟動過App,這時候App的啟動所需要的數(shù)據(jù)仍然在緩存中,再次啟動的時候稱為熱啟動。如果設(shè)備剛剛重啟,然后啟動App,這時候稱為冷啟動。

啟動時間在小于400ms是最佳的,因為從點擊圖標(biāo)到顯示Launch Screen,到Launch Screen消失這段時間是400ms。啟動時間不可以大于20s,否則會被系統(tǒng)殺掉。

以main函數(shù)作為分水嶺,啟動時間其實包括了兩部分:main函數(shù)之前和main函數(shù)到第一個界面的viewDidAppear:。所以,優(yōu)化也是從兩個方面進(jìn)行的,個人建議優(yōu)先優(yōu)化后者,因為絕大多數(shù)App的瓶頸在自己的代碼里。

Main函數(shù)之后

我們首先來分析下,從main函數(shù)開始執(zhí)行,到你的第一個界面顯示,這期間一般會做哪些事情。

*   執(zhí)行AppDelegate的代理方法,主要是didFinishLaunchingWithOptions
*   初始化Window,初始化基礎(chǔ)的ViewController結(jié)構(gòu)(一般是UINavigationController+UITabViewController)
*   獲取數(shù)據(jù)(Local DB/Network),展示給用戶。

AppDelegate

通常我們會在AppDelegate的代理方法里進(jìn)行初始化工作,主要包括了兩個方法:

didFinishLaunchingWithOptions
applicationDidBecomeActive
優(yōu)化這些初始化的核心思想就是:

能延遲初始化的盡量延遲初始化,不能延遲初始化的盡量放到后臺初始化。

這些工作主要可以分為幾類:

三方SDK初始化,比如Crash統(tǒng)計; 像分享之類的,可以等到第一次調(diào)用再出初始化。
初始化某些基礎(chǔ)服務(wù),比如WatchDog,遠(yuǎn)程參數(shù)。
啟動相關(guān)日志,日志往往涉及到DB操作,一定要放到后臺去做
業(yè)務(wù)方初始化,這個交由每個業(yè)務(wù)自己去控制初始化時間。

Main函數(shù)之前

Main函數(shù)之前是iOS系統(tǒng)的工作,所以這部分的優(yōu)化往往更具有通用性。

dylibs

在每個動態(tài)庫的加載過程中, dyld需要:

1.  分析所依賴的動態(tài)庫
2.  找到動態(tài)庫的mach-o文件
3.  打開文件
4.  驗證文件
5.  在系統(tǒng)核心注冊文件簽名
6.  對動態(tài)庫的每一個segment調(diào)用mmap()
通常的,一個App需要加載很多個dylibs, 但是其中的系統(tǒng)庫被優(yōu)化,可以很快的加載。應(yīng)用所依賴的dylib文件可能會再依賴其他 dylib,所以dyld所需要加載的是動態(tài)庫列表一個遞歸依賴的集合。
針對這一步驟的優(yōu)化有:

1.  減少非系統(tǒng)庫的依賴
2.  合并非系統(tǒng)庫

啟動的第一步是加載動態(tài)庫,加載系統(tǒng)的動態(tài)庫使很快的,因為可以緩存,而加載內(nèi)嵌的動態(tài)庫速度較慢。所以,提高這一步的效率的關(guān)鍵是:減少動態(tài)庫的數(shù)量。

  • 合并動態(tài)庫,比如公司內(nèi)部由私有Pod建立了如下動態(tài)庫:XXTableView, XXHUD, XXLabel,強烈建議合并成一個XXUIKit來提高加載速度。

Rebase & Bind & Objective C Runtime

由于ASLR(address space layout randomization)的存在,可執(zhí)行文件和動態(tài)鏈接庫在虛擬內(nèi)存中的加載地址每次啟動都不固定,所以需要這2步來修復(fù)鏡像中的資源指針,來指向正確的地址。 rebase修復(fù)的是指向當(dāng)前鏡像內(nèi)部的資源指針; 而bind指向的是鏡像外部的資源指針。
rebase步驟先進(jìn)行,需要把鏡像讀入內(nèi)存,并以page為單位進(jìn)行加密驗證,保證不會被篡改,所以這一步的瓶頸在IO。bind在其后進(jìn)行,由于要查詢符號表,來指向跨鏡像的資源,加上在rebase階段,鏡像已被讀入和加密驗證,所以這一步的瓶頸在于CPU計算。
優(yōu)化該階段的關(guān)鍵在于減少__DATA segment中的指針數(shù)量。我們可以優(yōu)化的點有:

1.  減少Objc類數(shù)量, 減少selector數(shù)量
2.  減少C++虛函數(shù)數(shù)量

Rebase和Bind都是為了解決指針引用的問題。對于Objective C開發(fā)來說,主要的時間消耗在Class/Method的符號加載上,所以常見的優(yōu)化方案是:

*   減少`__DATA`段中的指針數(shù)量。
*   合并Category和功能類似的類。比如:UIView+Frame,UIView+AutoLayout…合并為一個
*   刪除無用的方法和類。
*   多用Swift Structs,因為Swfit Structs是靜態(tài)分發(fā)的。

Initializers

通常,我們會在+load方法中進(jìn)行method-swizzling,這也是Nshipster推薦的方式。

  • 用initialize替代load。不少同學(xué)喜歡用method-swizzling來實現(xiàn)AOP去做日志統(tǒng)計等內(nèi)容,強烈建議改為在initialize進(jìn)行初始化。
  • 減少__atribute__((constructor))的使用,而是在第一次訪問的時候才用dispatch_once等方式初始化。
  • 不要創(chuàng)建線程
  • 使用Swfit重寫代碼。

pre-main階段耗時的影響因素:

動態(tài)庫加載越多,啟動越慢。
ObjC類越多,函數(shù)越多,啟動越慢。
可執(zhí)行文件越大啟動越慢。
C的constructor函數(shù)越多,啟動越慢。
C++靜態(tài)對象越多,啟動越慢。
ObjC的+load越多,啟動越慢。

整體上pre-main階段的優(yōu)化有:

①減少依賴不必要的庫,不管是動態(tài)庫還是靜態(tài)庫;如果可以的話,把動態(tài)庫改造成靜態(tài)庫;
如果必須依賴動態(tài)庫,則把多個非系統(tǒng)的動態(tài)庫合并成一個動態(tài)庫;

②檢查下 framework應(yīng)當(dāng)設(shè)為optional和required,
如果該framework在當(dāng)前App支持的所有iOS系統(tǒng)版本都存在,那么就設(shè)為required,否則就設(shè)為optional,
因為optional會有些額外的檢查;

③合并或者刪減一些OC類和函數(shù);
關(guān)于清理項目中沒用到的類,使用工具AppCode代碼檢查功能,查到當(dāng)前項目中沒有用到的類(也可以用根據(jù)linkmap文件來分析,但是準(zhǔn)確度不算很高);

④刪減一些無用的靜態(tài)變量,

⑤刪減沒有被調(diào)用到或者已經(jīng)廢棄的方法。

⑥將不必須在+load方法中做的事情延遲到+initialize中,盡量不要用C++虛函數(shù)(創(chuàng)建虛函數(shù)表有開銷)
因為load是在啟動的時候調(diào)用,而initialize是在類首次被使用的時候調(diào)用,不過當(dāng)你把load中的邏輯移到initialize中時候,一定要注意initialize的重復(fù)調(diào)用問題。

⑦類和方法名不要太長:iOS每個類和方法名都在__cstring段里都存了相應(yīng)的字符串值,所以類和方法名的長短也是對可執(zhí)行文件大小是有影響的;
因還是object-c的動態(tài)特性,因為需要通過類/方法名反射找到這個類/方法進(jìn)行調(diào)用,object-c對象模型會把類/方法名字符串都保存下來;

⑧用dispatch_once()代替所有的 attribute((constructor)) 函數(shù)、C++靜態(tài)對象初始化、ObjC的+load函數(shù);

⑨在設(shè)計師可接受的范圍內(nèi)壓縮圖片的大小,會有意外收獲。
壓縮圖片為什么能加快啟動速度呢?因為啟動的時候大大小小的圖片加載個十來二十個是很正常的,
圖片小了,IO操作量就小了,啟動當(dāng)然就會快了,比較靠譜的壓縮算法是TinyPNG。

2、抽象重復(fù)代碼

1、在iOS代碼中可能會為同一個類寫很多分類方法,由于參與開發(fā)同學(xué)較多,可能會導(dǎo)致方法重復(fù),但是實際上運行起來只能有一個分類的方法被調(diào)用,這取決于哪個分類后被加載,然而編譯的二進(jìn)制代碼中,兩個方法應(yīng)該是都存在的,這不僅會增加app體積,也會增加啟動時間,所以應(yīng)該杜絕這樣的重復(fù)問題;
2、有很多地方可能是名字不同,但是函數(shù)的功能相同,這個不容易被發(fā)現(xiàn),需要大家在寫代碼的過程中注意;
3、又或者兩個函數(shù)名字比較接近,里面有很多相似的代碼,這種情況下可以進(jìn)行相同的代碼的提取。

main階段的優(yōu)化大致有如下幾個點:

①減少啟動初始化的流程,能懶加載的就懶加載,能放后臺初始化的就放后臺,
能夠延時初始化的就延時,不要卡主線程的啟動時間,已經(jīng)下線的業(yè)務(wù)直接刪掉;
②優(yōu)化代碼邏輯,去除一些非必要的邏輯和代碼,減少每個流程所消耗的時間;
③啟動階段使用多線程來進(jìn)行初始化,把CPU的性能盡量發(fā)揮出來;
④使用純代碼而不是xib或者storyboard來進(jìn)行UI框架的搭建,尤其是主UI框架比如TabBarController這種,
盡量避免使用xib和storyboard,因為xib和storyboard也還是要解析成代碼來渲染頁面,多了一些步驟;

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

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