iOS系統(tǒng)優(yōu)化——03啟動(dòng)性能優(yōu)化

App啟動(dòng)過程

iOS應(yīng)用的啟動(dòng)可分為pre-main階段和main( )階段,其中系統(tǒng)做的事情依次是:

image.png

無論對于系統(tǒng)的動(dòng)態(tài)鏈接庫還是對于App本身的可執(zhí)行文件而言,他們都算是image(鏡像),而每個(gè)App都是以image( 鏡像)為單位進(jìn)行加載的

image:

1.Executable: 應(yīng)用的主要二進(jìn)制(比如.o文件)

2.Dylib: [動(dòng)態(tài)鏈接庫](dynamic library,又稱 DSO 或 DLL)

3.Bundle: 資源文件,不能被鏈接的 Dylib,只能在運(yùn)行時(shí)使用 dlopen() 加載

1. pre-main階段

1.1. 加載應(yīng)用的可執(zhí)行文件(自身App的所有.o文件的集合)

1.2. 加載動(dòng)態(tài)鏈接器dyld(dynamic loader,是一個(gè)專門用來加載動(dòng)態(tài)鏈接庫的庫)

1.3. dyld遞歸加載應(yīng)用所有依賴的動(dòng)態(tài)鏈接庫dylib

2. main( )階段

2.1. dyld調(diào)用main( )

2.2. 調(diào)用UIApplicationMain( )

2.3. 調(diào)用applicationWillFinishLaunching

2.4. 調(diào)用didFinishLaunchingWithOptions

一、 pre-main階段的過程和可優(yōu)化項(xiàng)

dyld的加載主要分為4步:

1.Load dylibs

image.png

這一階段dyld會(huì)分析應(yīng)用依賴的dylib(xcode7以后.dylib已改為名.tbd),找到其mach-o文件,打開和讀取這些文件并驗(yàn)證其有效性,接著會(huì)找到代碼簽名注冊到內(nèi)核,最后對dylib的每一個(gè)segment調(diào)用mmap()。

一般情況下,iOS應(yīng)用會(huì)加載100-400個(gè)dylibs,其中大部分是系統(tǒng)庫,這部分dylib的加載系統(tǒng)已經(jīng)做了優(yōu)化。

所以,依賴的dylib越少越好。在這一步,我們可以做的優(yōu)化有:

1.1、盡量不使用內(nèi)嵌(embedded)的dylib,加載內(nèi)嵌dylib性能開銷較大

1.2、合并已有的dylib和使用靜態(tài)庫(static archives),減少dylib的使用個(gè)數(shù)

1.3、懶加載dylib,但是要注意dlopen()可能造成一些問題,且實(shí)際上懶加載做的工作會(huì)更多

2. Rebase/Bind

image.png

在dylib的加載過程中,系統(tǒng)為了安全考慮,引入了ASLR(Address Space Layout Randomization)技術(shù)和代碼簽名。由于ASLR的存在,鏡像(Image,包括可執(zhí)行文件、dylib和bundle)會(huì)在隨機(jī)的地址上加載,和之前指針指向的地址(preferred_address)會(huì)有一個(gè)偏差(slide),dyld需要修正這個(gè)偏差,來指向正確的地址。

Rebase在前,Bind在后,Rebase做的是將鏡像讀入內(nèi)存,修正鏡像內(nèi)部的指針,性能消耗主要在IO。Bind做的是查詢符號表,設(shè)置指向鏡像外部的指針,性能消耗主要在CPU計(jì)算。

所以,指針數(shù)量越少越好。在這一步,我們可以做的優(yōu)化有:
2.1、減少ObjC類(class)、方法(selector)、分類(category)的數(shù)量

2.2、減少[C++虛函數(shù)]的數(shù)量(創(chuàng)建虛函數(shù)表有開銷)

2.3、使用Swift structs(內(nèi)部做了優(yōu)化,符號數(shù)量更少)

3. Objc setup

image.png

大部分ObjC初始化工作已經(jīng)在Rebase/Bind階段做完了,這一步dyld會(huì)注冊所有聲明過的ObjC類,將分類插入到類的方法列表里,再檢查每個(gè)selector的唯一性。

在這一步?jīng)]什么優(yōu)化可做的,Rebase/Bind階段優(yōu)化好了,這一步的耗時(shí)也會(huì)減少。

4. Initializers

image.png

到了這一階段,dyld開始運(yùn)行程序的初始化函數(shù),調(diào)用每個(gè)Objc類和分類的+load方法,調(diào)用C/C++ 中的構(gòu)造器函數(shù)(用attribute((constructor))修飾的函數(shù)),和創(chuàng)建非基本類型的C++靜態(tài)全局變量(通常是類或結(jié)構(gòu)體)。Initializers階段執(zhí)行完后,dyld開始調(diào)用main()函數(shù)。

Objc的load函數(shù)和C++的靜態(tài)構(gòu)造函數(shù)采用由底向上的方式執(zhí)行,來保證每個(gè)執(zhí)行的方法,都可以找到所依賴的動(dòng)態(tài)庫。例:


image.png

在這一步,我們可以做的優(yōu)化有:

4.1、少在類的+load方法里做事情,盡量把這些事情推遲到+initiailize

4.2、減少構(gòu)造器函數(shù)個(gè)數(shù),在構(gòu)造器函數(shù)里少做些事情

4.3、減少C++靜態(tài)全局變量的個(gè)數(shù)

二、main( )階段的可優(yōu)化項(xiàng)

這一階段的優(yōu)化主要是減少didFinishLaunchingWithOptions方法里的工作,在didFinishLaunchingWithOptions方法里,我們會(huì)創(chuàng)建應(yīng)用的window,指定其rootViewController,調(diào)用window的makeKeyAndVisible方法讓其可見。由于業(yè)務(wù)需要,我們會(huì)初始化各個(gè)二方/三方庫,設(shè)置系統(tǒng)UI風(fēng)格,檢查是否需要顯示引導(dǎo)頁、是否需要登錄、是否有新版本等,由于歷史原因,這里的代碼容易變得比較龐大,啟動(dòng)耗時(shí)難以控制。

所以,滿足業(yè)務(wù)需要的前提下,didFinishLaunchingWithOptions在主線程里做的事情越少越好。我們可以做的優(yōu)化有:

1、梳理各個(gè)二方/三方庫,找到可以延遲加載的庫,做延遲加載處理,比如放到首頁控制器的viewDidAppear方法里。

2、梳理業(yè)務(wù)邏輯,把可以延遲執(zhí)行的邏輯,做延遲執(zhí)行處理。比如檢查新版本、注冊推送通知等邏輯。

3、避免復(fù)雜/多余的計(jì)算。

4、采用性能更好的API。

5、避免在首頁控制器的viewDidLoad和viewWillAppear做太多事情,這2個(gè)方法執(zhí)行完,首頁控制器才能顯示,部分可以延遲創(chuàng)建的視圖應(yīng)做延遲創(chuàng)建/懶加載處理。

6、首頁控制器用純代碼方式來構(gòu)建。

三、啟動(dòng)優(yōu)化總結(jié)

1. pre-main階段的優(yōu)化

pre-main階段優(yōu)化蘋果給出的建議最好是400ms之內(nèi),但這個(gè)肯定要按照項(xiàng)目的實(shí)際情況有所取舍。

1.1、排查無用的dylib(不確定的可以先刪除,在編譯下項(xiàng)目試試),減少dylib的數(shù)目

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

1.3、減少ObjC類(項(xiàng)目中不常用的庫,廢棄的代碼等)、方法(selector)、分類(category)的數(shù)量、無用的庫、非基本類型的C++靜態(tài)全局變量(通常是類或結(jié)構(gòu)體)

1.4、壓縮資源圖片,刪除無用的圖片(IO操作)

1.5、少在類的+load方法里做事情,盡量把這些事情推遲到+initiailize

1.6、使用Swift structs(這是長期工作,可以考慮未來新頁面用swift寫)

2. main()階段的優(yōu)化

2.1、可使用instruments的Time Profiler先分析啟動(dòng)時(shí)哪些地方比較耗時(shí),是否可以做優(yōu)化

2.2、梳理各個(gè)二方/三方庫,找到可以延遲加載的庫,做延遲加載處理,比如放到首頁控制器或tabBar控制器的viewDidAppear方法里,并且保證只執(zhí)行一次(按項(xiàng)目結(jié)構(gòu),放在合適的地方)

2.3、梳理業(yè)務(wù)邏輯,把可以延遲執(zhí)行的邏輯,做延遲執(zhí)行處理。比如檢查新版本、注冊推送通知等邏輯。

2.4、避免復(fù)雜/多余的計(jì)算

2.5、每次用NSLog方式打印會(huì)隱式的創(chuàng)建一個(gè)Calendar,因此需要?jiǎng)h減啟動(dòng)時(shí)各業(yè)務(wù)方打的log

2.6、避免在用戶看到的第一個(gè)界面(首頁控制器或注冊登錄頁面)的viewDidLoad和viewWillAppear做太多事情,這2個(gè)方法執(zhí)行完,第一個(gè)頁面才能顯示,部分可以延遲創(chuàng)建的視圖應(yīng)做延遲創(chuàng)建/懶加載處理

2.7、首頁控制器或注冊登錄頁面用純代碼方式來構(gòu)建

2.8、我們項(xiàng)目中每次啟動(dòng)會(huì)全量拉取AppServerConfig的配置,內(nèi)容太多,未來需要api配合拆分,等頁面使用的時(shí)候在拉取相應(yīng)配置

2.9、持久化數(shù)據(jù)的讀取到內(nèi)存中的時(shí)間也可以評估一下

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

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