注:【本文內容是閱讀「戴銘」老師的iOS開發(fā)高手課內容后向叉,自己的筆記總結】
1、APP啟動分為兩種啟動:冷啟動 + 熱啟動
冷啟動:APP的icon從點擊啟動前,它的進程不在系統(tǒng)里俄周,需要新創(chuàng)建一個進程分配給它的啟動的情況星澳。
熱啟動:APP在啟動后用戶將APP退到后臺疚顷,在APP的進程還在系統(tǒng)里的情況下,用戶重新啟動進入APP的過程募判。(這個過程做的事情比較少)
2荡含、APP啟動慢,其實發(fā)生在主線程上届垫。主線程慢的原因很多释液。(主線程上 執(zhí)行大文件讀寫、大量數(shù)據(jù)處理計算等等)装处,但是當這些任務全部去掉后误债,剩余的主線程耗時的任務還有哪些呢?下面我們一一來找出APP啟動后妄迁,主線程都干了哪些事寝蹈。
3、APP啟動三個階段
? ? ? ? ?1> main()函數(shù)執(zhí)行前登淘。
1. 加載可執(zhí)行文件(APP的.o 文件的集合)
2. 加載動態(tài)鏈接庫箫老,進行rebase指針調整和bind符合綁定。
3. Objc 運行時的初始化處理黔州,包括?Objc相關類的注冊耍鬓、category注冊、selector唯一性檢查等流妻。
4. 初始化牲蜀,包括了執(zhí)行 ?+load()方法、attribute(constructor)修飾的函數(shù)的調用绅这、創(chuàng)建?C++靜態(tài)全局變量涣达。
針對以上過程做優(yōu)化:
2> 減少動態(tài)庫加載。每個庫本身都有依賴關系证薇,蘋果公司建議使用更少的動態(tài)庫度苔,并且建議在使用動態(tài)庫的數(shù)量較多時,盡量將多個動態(tài)庫進行合并棕叫。數(shù)量上林螃,蘋果公司建議最多使用6個非系統(tǒng)動態(tài)庫。
3>減少加載啟動后不會使用的類或者方法俺泣。(項目種不再使用的類疗认、方法 及時清理掉M瓴小)
4>+load()方法里的內容可以放到首屏渲染完成后執(zhí)行,或使用+initialize()方法替換掉横漏。因為谨设,在一個+load()方法里,進行運行時方法替換操作會帶來4毫秒的消耗缎浇。因此積少成多扎拣,使用load()方法較多會對啟動速度影響越來越大。 ? 控制++全局變量的數(shù)量素跺!
? ? ? ? ?2>mian()函數(shù)執(zhí)行后二蓝。(該階段:從main()函數(shù)執(zhí)行開始,到appDelegate的 didFinishLaunchingWIthOptions 啟動方法里首屏渲染相關方法執(zhí)行完成指厌。首頁的業(yè)務代碼都是要在這個階段刊愚,也就是首屏渲染前執(zhí)行的)
1. 首屏 ?"初始化" 所需配置文件的讀寫操作。
2. 首屏 ?"列表大數(shù)據(jù)" 的讀取
3. 首屏渲染的大量計算等踩验。
針對以上過程優(yōu)化:
1> 從功能上梳理出哪些是 ? 首屏渲染必要的初始化功能 鸥诽、APP啟動必要的初始化功能 、 只需要在對應功能開始使用時才需要初始化箕憾。
2> 將三個 初始化功能牡借,分別放到合適的階段進行初始化。
? ? ? ? 3>首屏渲染完成后袭异。(該階段完成的是钠龙,非首屏其他業(yè)務服務模塊的初始化、監(jiān)聽的注冊御铃、配置文件的讀取等俊鱼。這個階段就是從 APP啟動方法 內 首屏渲染完成時開始,到 didFinishLaunchingWIthOptions方法作用域結束時結束畅买。)這個階段用戶已經看到APP首頁了,但是這個階段里 會卡主主線程的方法還是需要最優(yōu)先處理的细睡,不然會影響用戶后續(xù)的操作谷羞。
1、非首屏其他業(yè)務服務模塊的初始化溜徙、監(jiān)聽的注冊湃缎、配置文件的讀取等
優(yōu)化:
1. 會阻礙 主線程的任務方法,進行優(yōu)化處理蠢壹。
4嗓违、功能級別的啟動優(yōu)化:從main()函數(shù)執(zhí)行后到首屏渲染完成前只處理首屏相關的業(yè)務,其他非首屏業(yè)務的初始化图贸、監(jiān)聽注冊蹂季、配置文件讀取等都放到 首屏渲染完成后去做冕广。(非首屏業(yè)務所需的功能滯后處理。)
以上處理會大大縮短 APP啟動的時間偿洁。 下面會針對 方法 進一步優(yōu)化撒汉。
5、方法級別的啟動優(yōu)化:檢查首屏渲染完成前 主線程上有哪些耗時方法涕滋,將沒必要的耗時滯后或者異步執(zhí)行睬辐。(通常耗時較長的方法主要發(fā)生在計算大量數(shù)據(jù)的情況下,具體的表現(xiàn)就是加載宾肺、編輯溯饵、存儲圖片和文件等資源。)
對方法耗時監(jiān)控锨用,APP啟動速度監(jiān)控辦法丰刊。
1、定時抓取主線程上的方法調用堆棧黔酥,計算一段時間里各個方法的耗時藻三。Xcode工具套件里自帶的Time Profiler,采用的就是這種方式跪者。(Xcode —> 右鍵 ——> Open Developer Tool ——> Instruments 工具里)
? ? ? 定時抓瓤妹薄:定時間隔設置得長了,會漏掉一些方法渣玲,從而導致檢查出來的耗時不精確逗概;
? ? ? ? ? ? ? ? ? ? ? ? ? 而定時間隔設置得短了,抓取堆棧這個方法本身調用過多也會影響整體耗時忘衍,導致結果不準確逾苫。
? ? ? ? ? ? ? ? ? ? ? ? ?這個定時間隔如果小于所有方法執(zhí)行的時間(比如 0.002 秒),那么基本就能監(jiān)控到所有方法枚钓。但這樣做的話铅搓,整體的耗時時間就不夠準確。一般將這個定時間隔設置為 0.01 秒搀捷。這樣設置星掰,對整體耗時的影響小,不過很多方法耗時就不精確了嫩舟。但因為整體耗時的數(shù)據(jù)更加重要些氢烘,單個方法耗時精度不高也是可以接受的,所以這個設置也是沒問題的家厌。 總結來說播玖,定時抓取主線程調用棧的方式雖然精準度不夠高,但也是夠用的饭于。
2蜀踏、對objc_msgSend方法進行hook來掌握所有方法的執(zhí)行耗時:優(yōu)點 非常精確维蒙,但缺點 只能針對 Objective-C的方法,當然 對于c方法 和block 可以使用 libffi 的 ffi_call 來 達成hook脓斩,但缺點就是編寫維護相關工具門檻高木西。
objc_msgSend方法本身采用匯編語言寫的,所以性能優(yōu)化屬于原子級優(yōu)化随静,能夠把優(yōu)化做到極致八千。
objc_msgSend方法執(zhí)行的邏輯:先獲取對象的對應類的信息,再獲取方法的緩存燎猛,根據(jù)方法的selector查找函數(shù)指針恋捆,經過異常錯誤處理后,最后跳到對應函數(shù)的實現(xiàn)重绷。
6沸停、objc_msgSend 方法hook工具:Facebook開源的庫 fishhook.(可以在iOS運行的 Mach-O二進制文件中動態(tài)地重新綁定符號) GitHub地址:fishhook 代碼
fishhook原理:fishhook 實現(xiàn)的大致思路是,通過重新綁定符號昭卓,可以實現(xiàn)對 c 方法的 hook愤钾。dyld 是通過更新 Mach-O 二進制的 __DATA segment 特定的部分中的指針來綁定 lazy 和 non-lazy 符號,通過確認傳遞給 rebind_symbol 里每個符號名稱更新的位置候醒,就可以找出對應替換來重新綁定這些符號能颁。
使用Demo在戴銘老師的GitHub上地址:GCDFetchFeed