iOSAPP啟動(dòng)速度優(yōu)化與監(jiān)控

注:【本文內(nèi)容是閱讀「戴銘」老師的iOS開發(fā)高手課內(nèi)容后,自己的筆記總結(jié)】

1、APP啟動(dòng)分為兩種啟動(dòng):冷啟動(dòng) + 熱啟動(dòng)

冷啟動(dòng):APP的icon從點(diǎn)擊啟動(dòng)前,它的進(jìn)程不在系統(tǒng)里,需要新創(chuàng)建一個(gè)進(jìn)程分配給它的啟動(dòng)的情況。

熱啟動(dòng):APP在啟動(dòng)后用戶將APP退到后臺(tái),在APP的進(jìn)程還在系統(tǒng)里的情況下,用戶重新啟動(dòng)進(jìn)入APP的過程。(這個(gè)過程做的事情比較少)

2、APP啟動(dòng)慢,其實(shí)發(fā)生在主線程上。主線程慢的原因很多。(主線程上 執(zhí)行大文件讀寫、大量數(shù)據(jù)處理計(jì)算等等),但是當(dāng)這些任務(wù)全部去掉后,剩余的主線程耗時(shí)的任務(wù)還有哪些呢?下面我們一一來找出APP啟動(dòng)后,主線程都干了哪些事。

3、APP啟動(dòng)三個(gè)階段


冷啟動(dòng)流程圖

? ? ? ? ?1> main()函數(shù)執(zhí)行前。

1. 加載可執(zhí)行文件(APP的.o 文件的集合)

2. 加載動(dòng)態(tài)鏈接庫(kù),進(jìn)行rebase指針調(diào)整和bind符合綁定。

3. Objc 運(yùn)行時(shí)的初始化處理,包括?Objc相關(guān)類的注冊(cè)、category注冊(cè)、selector唯一性檢查等。

4. 初始化,包括了執(zhí)行 ?+load()方法、attribute(constructor)修飾的函數(shù)的調(diào)用、創(chuàng)建?C++靜態(tài)全局變量。


針對(duì)以上過程做優(yōu)化:

2> 減少動(dòng)態(tài)庫(kù)加載。每個(gè)庫(kù)本身都有依賴關(guān)系,蘋果公司建議使用更少的動(dòng)態(tài)庫(kù),并且建議在使用動(dòng)態(tài)庫(kù)的數(shù)量較多時(shí),盡量將多個(gè)動(dòng)態(tài)庫(kù)進(jìn)行合并。數(shù)量上,蘋果公司建議最多使用6個(gè)非系統(tǒng)動(dòng)態(tài)庫(kù)。

3>減少加載啟動(dòng)后不會(huì)使用的類或者方法。(項(xiàng)目種不再使用的類、方法 及時(shí)清理掉?。?/p>

4>+load()方法里的內(nèi)容可以放到首屏渲染完成后執(zhí)行,或使用+initialize()方法替換掉。因?yàn)?,在一個(gè)+load()方法里,進(jìn)行運(yùn)行時(shí)方法替換操作會(huì)帶來4毫秒的消耗。因此積少成多,使用load()方法較多會(huì)對(duì)啟動(dòng)速度影響越來越大。 ? 控制++全局變量的數(shù)量!

? ? ? ? ?2>mian()函數(shù)執(zhí)行后。(該階段:從main()函數(shù)執(zhí)行開始,到appDelegate的 didFinishLaunchingWIthOptions 啟動(dòng)方法里首屏渲染相關(guān)方法執(zhí)行完成。首頁(yè)的業(yè)務(wù)代碼都是要在這個(gè)階段,也就是首屏渲染前執(zhí)行的)

1. 首屏 ?"初始化" 所需配置文件的讀寫操作。

2. 首屏 ?"列表大數(shù)據(jù)" 的讀取

3. 首屏渲染的大量計(jì)算等。


針對(duì)以上過程優(yōu)化:

1> 從功能上梳理出哪些是 ? 首屏渲染必要的初始化功能 、APP啟動(dòng)必要的初始化功能 、 只需要在對(duì)應(yīng)功能開始使用時(shí)才需要初始化。

2> 將三個(gè) 初始化功能,分別放到合適的階段進(jìn)行初始化。

? ? ? ? 3>首屏渲染完成后。(該階段完成的是,非首屏其他業(yè)務(wù)服務(wù)模塊的初始化、監(jiān)聽的注冊(cè)、配置文件的讀取等。這個(gè)階段就是從 APP啟動(dòng)方法 內(nèi) 首屏渲染完成時(shí)開始,到 didFinishLaunchingWIthOptions方法作用域結(jié)束時(shí)結(jié)束。)這個(gè)階段用戶已經(jīng)看到APP首頁(yè)了,但是這個(gè)階段里 會(huì)卡主主線程的方法還是需要最優(yōu)先處理的,不然會(huì)影響用戶后續(xù)的操作。

1、非首屏其他業(yè)務(wù)服務(wù)模塊的初始化、監(jiān)聽的注冊(cè)、配置文件的讀取等

優(yōu)化:

1. 會(huì)阻礙 主線程的任務(wù)方法,進(jìn)行優(yōu)化處理。


4、功能級(jí)別的啟動(dòng)優(yōu)化:從main()函數(shù)執(zhí)行后到首屏渲染完成前只處理首屏相關(guān)的業(yè)務(wù),其他非首屏業(yè)務(wù)的初始化、監(jiān)聽注冊(cè)、配置文件讀取等都放到 首屏渲染完成后去做。(非首屏業(yè)務(wù)所需的功能滯后處理。)

以上處理會(huì)大大縮短 APP啟動(dòng)的時(shí)間。 下面會(huì)針對(duì) 方法 進(jìn)一步優(yōu)化。

5、方法級(jí)別的啟動(dòng)優(yōu)化:檢查首屏渲染完成前 主線程上有哪些耗時(shí)方法,將沒必要的耗時(shí)滯后或者異步執(zhí)行。(通常耗時(shí)較長(zhǎng)的方法主要發(fā)生在計(jì)算大量數(shù)據(jù)的情況下,具體的表現(xiàn)就是加載、編輯、存儲(chǔ)圖片和文件等資源。)


對(duì)方法耗時(shí)監(jiān)控,APP啟動(dòng)速度監(jiān)控辦法。

1、定時(shí)抓取主線程上的方法調(diào)用堆棧,計(jì)算一段時(shí)間里各個(gè)方法的耗時(shí)。Xcode工具套件里自帶的Time Profiler,采用的就是這種方式。(Xcode —> 右鍵 ——> Open Developer Tool ——> Instruments 工具里)

? ? ? 定時(shí)抓取:定時(shí)間隔設(shè)置得長(zhǎng)了,會(huì)漏掉一些方法,從而導(dǎo)致檢查出來的耗時(shí)不精確;

? ? ? ? ? ? ? ? ? ? ? ? ? 而定時(shí)間隔設(shè)置得短了,抓取堆棧這個(gè)方法本身調(diào)用過多也會(huì)影響整體耗時(shí),導(dǎo)致結(jié)果不準(zhǔn)確。

? ? ? ? ? ? ? ? ? ? ? ? ?這個(gè)定時(shí)間隔如果小于所有方法執(zhí)行的時(shí)間(比如 0.002 秒),那么基本就能監(jiān)控到所有方法。但這樣做的話,整體的耗時(shí)時(shí)間就不夠準(zhǔn)確。一般將這個(gè)定時(shí)間隔設(shè)置為 0.01 秒。這樣設(shè)置,對(duì)整體耗時(shí)的影響小,不過很多方法耗時(shí)就不精確了。但因?yàn)檎w耗時(shí)的數(shù)據(jù)更加重要些,單個(gè)方法耗時(shí)精度不高也是可以接受的,所以這個(gè)設(shè)置也是沒問題的。 總結(jié)來說,定時(shí)抓取主線程調(diào)用棧的方式雖然精準(zhǔn)度不夠高,但也是夠用的。

2、對(duì)objc_msgSend方法進(jìn)行hook來掌握所有方法的執(zhí)行耗時(shí):優(yōu)點(diǎn) 非常精確,但缺點(diǎn) 只能針對(duì) Objective-C的方法,當(dāng)然 對(duì)于c方法 和block 可以使用 libffi 的 ffi_call 來 達(dá)成hook,但缺點(diǎn)就是編寫維護(hù)相關(guān)工具門檻高。

objc_msgSend方法本身采用匯編語(yǔ)言寫的,所以性能優(yōu)化屬于原子級(jí)優(yōu)化,能夠把優(yōu)化做到極致。

objc_msgSend方法執(zhí)行的邏輯:先獲取對(duì)象的對(duì)應(yīng)類的信息,再獲取方法的緩存,根據(jù)方法的selector查找函數(shù)指針,經(jīng)過異常錯(cuò)誤處理后,最后跳到對(duì)應(yīng)函數(shù)的實(shí)現(xiàn)。


6、objc_msgSend 方法hook工具:Facebook開源的庫(kù) fishhook.(可以在iOS運(yùn)行的 Mach-O二進(jìn)制文件中動(dòng)態(tài)地重新綁定符號(hào)) GitHub地址:fishhook 代碼

fishhook原理:fishhook 實(shí)現(xiàn)的大致思路是,通過重新綁定符號(hào),可以實(shí)現(xiàn)對(duì) c 方法的 hook。dyld 是通過更新 Mach-O 二進(jìn)制的 __DATA segment 特定的部分中的指針來綁定 lazy 和 non-lazy 符號(hào),通過確認(rèn)傳遞給 rebind_symbol 里每個(gè)符號(hào)名稱更新的位置,就可以找出對(duì)應(yīng)替換來重新綁定這些符號(hào)。

使用Demo在戴銘老師的GitHub上地址:GCDFetchFeed

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

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

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