iOS APP啟動監(jiān)控和優(yōu)化思路

前言:本文簡單描述APP啟動過程和監(jiān)控裙戏,一些深入原理性的東西可能需要繞路了,站在大神的肩膀上厕诡,簡單總結(jié)跟APP啟動性能有關(guān)累榜,如果差錯請不吝賜教

冷啟動

相對而言冷啟動就是App被kill掉以后一切從頭開始啟動的過程。
App 點擊啟動前灵嫌,它的進程不在系統(tǒng)里壹罚,需要系統(tǒng)新創(chuàng)建一個進程分配給它啟動的情況。這是一次完整的啟動過程寿羞。
用戶感知到的啟動慢猖凛,其實都發(fā)生在主線程上。而主線程慢的原因有很多稠曼,比如在主線程上執(zhí)行了大文件讀寫操作形病、在渲染周期中執(zhí)行了大量計算等。

熱啟動

當(dāng)用戶按下home鍵的時候霞幅,iOS的App并不會馬上被kill掉,還會繼續(xù)存活若干時間量瓜。用戶點擊App的圖標(biāo)再次回來的時候司恳,App幾乎不需要做什么,就可以還原到退出前的狀態(tài)绍傲,繼續(xù)為用戶服務(wù)扔傅。App 在冷啟動后用戶將 App 退后臺,在 App 的進程還在系統(tǒng)里的情況下烫饼,用戶重新啟動進入 App 的過程猎塞。這種持續(xù)存活的情況下啟動App,稱為熱啟動杠纵。

查看APP啟動耗時

根據(jù)APP啟動時間荠耽,繼續(xù)了解APP啟動時候都做了哪些
Xcode:(快捷鍵:command + shift + <)
ProjectSchemeEdit SchemeRunEnvironment Variables
添加 DYLD_PRINT_STATISTICS 環(huán)境變量,value為1

環(huán)境變量

APP 啟動時間:

Total pre-main time: 802.16 milliseconds (100.0%)
         dylib loading time: 294.37 milliseconds (36.6%)
        rebase/binding time: 377.42 milliseconds (47.0%)
            ObjC setup time:  86.68 milliseconds (10.8%)
           initializer time:  43.51 milliseconds (5.4%)
           slowest intializers :
             libSystem.B.dylib :   4.20 milliseconds (0.5%)
    libMainThreadChecker.dylib :  21.22 milliseconds (2.6%)

時間消耗解讀

  • main()函數(shù)之前總共使用了802.16ms
  • 加載動態(tài)庫占用36.6%
  • 指針重定位占用47.6%
  • ObjC類初始化占用10.8%
  • 各種初始化使用了5.4%比藻。
  • initializer time中最耗時的是libSystem.B.dylib铝量、libBacktraceRecording.dylib。

換言之:
App開始啟動后银亲,系統(tǒng)首先加載可執(zhí)行文件(自身App的所有.o文件的集合)慢叨,然后加載動態(tài)鏈接器dyld,dyld是一個專門用來加載動態(tài)鏈接庫的庫务蝠。 執(zhí)行從dyld開始拍谐,dyld從可執(zhí)行文件的依賴開始, 遞歸加載所有的依賴動態(tài)鏈接庫。
動態(tài)鏈接庫包括:iOS 中用到的所有系統(tǒng) framework,加載OC runtime方法的libobjc轩拨,系統(tǒng)級別的libSystem力穗,例如libdispatch(GCD)和libsystem_blocks (Block)。

APP啟動階段

啟動時間:用戶點擊APP → APP首頁面加載完成

  • 階段1:main() 函數(shù)執(zhí)行前
  • 階段2:main() 函數(shù)執(zhí)行后
  • 階段3:首屏渲染完成后

main()函數(shù)執(zhí)行前

在 main() 函數(shù)執(zhí)行前气嫁,系統(tǒng)主要會做下面幾件事情:

  • 【解析Info.plist】:加載信息当窗,例如閃屏;沙盒建立寸宵、權(quán)限檢查
  • 【Mach-O加載】:加載所有依賴的Mach-O文件(遞歸調(diào)用Mach-O加載的方法)崖面;加載可執(zhí)行文件(App 的.o 文件的集合)
  • 【加載動態(tài)鏈接庫】:進行 rebase 指針調(diào)整和 bind 符號綁定;定位內(nèi)部梯影、外部指針引用巫员,例如字符串、函數(shù)等
  • 【Objc 運行時的初始處理】:包括 Objc 相關(guān)類的注冊甲棍、category 注冊简识、selector 唯一性檢查等
  • 【初始化】:執(zhí)行 +load() 方法,執(zhí)行聲明為attribute((constructor))的C函數(shù)感猛,C++靜態(tài)對象加載

程序執(zhí)行

  • 調(diào)用 main()
  • 調(diào)用UIApplicationMain()
  • 調(diào)用applicationWillFinishLaunching

可優(yōu)化的功能點

  • 【減少動態(tài)庫加載】:每個庫本身都有依賴關(guān)系七扰,使用更少的動態(tài)庫,并且建議在使用動態(tài)庫的數(shù)量較多時陪白,盡量將多個動態(tài)庫進行合并颈走。最多可以支持 6 個非系統(tǒng)動態(tài)庫合并為一個。
  • 【減少+load方法】:方法里的內(nèi)容可以放到首屏渲染完成后再執(zhí)行咱士,或使用 +initialize() 方法替換掉立由。在一個 +load() 方法里,進行運行時方法替換操作會帶來 4 毫秒的消耗序厉。
  • 【減少使用】:減少寫attribute((constructor))的C函數(shù)锐膜,控制 C++ 全局變量的數(shù)量;

main()函數(shù)之后

main()函數(shù)開始至 appDelegate
didFinishLaunchingWithOptions結(jié)束弛房,稱為main()函數(shù)之后的部分道盏。

主要執(zhí)行內(nèi)容

  • 首屏初始化所需配置文件的讀寫操作;
  • 首屏列表大數(shù)據(jù)的讀韧ピ佟捞奕;
  • 首屏渲染的大量計算等。

main()函數(shù)之后耗時的影響因素

  • 執(zhí)行 main()函數(shù)的耗時
  • 執(zhí)行applicationWillFinishLaunching的耗時
  • rootViewController及其childViewController的加載拄轻、view及其subviews的加載

首屏渲染完成之后

[首屏渲染完成之后]指的是非首屏其他業(yè)務(wù)服務(wù)模塊的初始化颅围、監(jiān)聽的注冊、配置文件的讀取等恨搓。
該階段指的就是截止到 didFinishLaunchingWithOptions 方法作用域內(nèi)執(zhí)行首屏渲染之后的所有方法執(zhí)行完成院促。從渲染完成時開始筏养,到 didFinishLaunchingWithOptions方法作用域結(jié)束時結(jié)束。

優(yōu)化思路一:功能啟動優(yōu)化

main() 函數(shù)開始執(zhí)行后到首屏渲染完成前只處理首屏相關(guān)的業(yè)務(wù)常拓,其他非首屏業(yè)務(wù)的初始化渐溶、監(jiān)聽注冊、配置文件讀取等都放到首屏渲染完成后去做弄抬。

根據(jù)剛需分置階段進行
根據(jù)啟動流程把剛需功能放置在啟動階段茎辐,其他業(yè)務(wù)功能放在合適的階段

  • 首屏渲染必要的初始化功能
  • App 啟動必要的初始化功能
  • 只需要在對應(yīng)功能開始使用時才需要初始化的功能
  • 例如:主視圖第一時間加載,里面的數(shù)據(jù)和界面延后加載

優(yōu)化思路二:方法啟動優(yōu)化

檢查首屏渲染完成前主線程上的耗時方法掂恕,將非剛需的耗時方法滯后或異步執(zhí)行拖陆。耗時較長的方法主要發(fā)生在計算大量數(shù)據(jù)的情況下,例如加載懊亡、編輯依啰、存儲圖片和文件等資源。
+load() 方法店枣,一個耗時 4 毫秒速警,100 個就是 400 毫秒,不可小視

優(yōu)化三:移除不必要的動態(tài)庫

移除項目中非必要的動態(tài)庫

優(yōu)化四:移除不必要用到的類

代碼工程的維護非常重要

優(yōu)化五:合并功能相似的類和擴展(Category)

由于Category的實現(xiàn)原理鸯两,和ObjC的動態(tài)綁定有很強的關(guān)系闷旧,實際上類的擴展是比較占用啟動時間的。盡量可能合并一些擴展甩卓,并不是讓你不使用擴展

優(yōu)化六:壓縮資源圖片

圖片小了鸠匀,IO操作量小了,啟動就快了逾柿。推薦 TinyPNG

優(yōu)化七:優(yōu)化applicationWillFinishLaunching

需要在applicationWillFinishLaunching里處理的業(yè)務(wù)較多時,可以管理起這些任務(wù)
將不需要馬上在applicationWillFinishLaunching執(zhí)行的代碼延后執(zhí)行

優(yōu)化八:優(yōu)化rootViewController

rootViewController的加載宅此,適當(dāng)將某一級的childViewController或subviews延后加載
如果你的App可能會被后臺拉起并冷啟動机错,可考慮不加載rootViewController

優(yōu)化九:小優(yōu)化

  • 不使用xib,直接視用代碼加載首頁視圖
  • NSUserDefaults實際上是在Library文件夾下會生產(chǎn)一個plist文件父腕,如果文件太大的話一次能讀取到內(nèi)存中可能很耗時弱匪,如果耗時很大的話需要拆分(需考慮老版本覆蓋安裝兼容問題)
  • 每次用NSLog方式打印會隱式的創(chuàng)建一個Calendar,因此需要刪減啟動時各業(yè)務(wù)方打的log璧亮,或者僅僅針對內(nèi)測版輸出log
  • 梳理應(yīng)用啟動時發(fā)送的所有網(wǎng)絡(luò)請求萧诫,是否可以統(tǒng)一在異步線程請求

Debug 打印代碼塊

//MARK: - DEBUG print
func printLog<T>(msg: T,
                 file: String = #file,
                 method: String = #function,
                 line: Int = #line){
    if !DEBUG_ALPHA{//線上環(huán)境不print
        return
    }
    print("\((file as NSString).lastPathComponent) \(method),[\(line)]: \(msg)")
}

APP監(jiān)控方法一:計算主線程方法耗時

定時抓取主線程上的方法調(diào)用對戰(zhàn),計算在一段時間內(nèi)各個方法的耗時枝嘶。Xcode 工具套件里自帶的 Time Profiler帘饶,開發(fā)類似工具成本不高,能夠快速開發(fā)后集成到你的 App 中群扶,以便在真實環(huán)境中進行檢查及刻。

對于定時時間間隔的控制

  • 間隔長:會漏掉某些方法镀裤,從而導(dǎo)致檢查出來的耗時不精確
  • 間隔短:抓取堆棧這個方法本身調(diào)用過多會影響整體耗時,導(dǎo)致結(jié)果不準(zhǔn)確
  • 定時抓取主線程調(diào)用棧的方式精準(zhǔn)度不夠高缴饭,做參考足以
  • 大神得出最合適時間為 0.01 秒暑劝,雖然導(dǎo)致許多運行速度快的方法監(jiān)控誤差,對整體耗時影響小

APP監(jiān)控方法二:objc_msgSend 方法 hook 所有方法耗時

Hook:在原方法開始執(zhí)行時換成執(zhí)行其他指定的方法颗搂,或者在原有方法執(zhí)行前后執(zhí)行你指定的方法担猛,來達到掌握和改變指定方法的目的。

  • 優(yōu)點:非常精確
  • 缺點:只能對Objective-C方法丢氢,對c方法和block需要借助第三方框架處理傅联,編寫維護成本高

Objective-C 里每個對象都會指向一個類,每個類都會有一個方法列表卖丸,方法列表里的每個方法都是由 selector纺且、函數(shù)指針和 metadata 組成的。
objc_msgSend就是在運行時根據(jù)對象和方法的 selector 去找到對應(yīng)的函數(shù)指針稍浆,然后執(zhí)行载碌。objc_msgSendObjective-C 里方法執(zhí)行的必經(jīng)之路,能夠控制所有的 Objective-C 的方法衅枫,可自行閱讀objc_msgSend 源碼獲得更深層次的理解嫁艇。

objc_msgSend 用匯編語言寫的:

  • objc_msgSend 的調(diào)用頻次最高,在它上面進行的性能優(yōu)化能夠提升整個 App 生命周期的性能弦撩。匯編語言在性能優(yōu)化上屬于原子級優(yōu)化步咪,能夠把優(yōu)化做到極致。
  • 其他語言難以實現(xiàn)未知參數(shù)跳轉(zhuǎn)到任意函數(shù)指針的功能益楼。

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

Tips

Swift AppDelegate 沒有main函數(shù)入口了

錯,swift有main函數(shù)陪竿,被精簡成了一個@NSApplicationMain

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
檢查方法耗時的工具

推薦大神神作 SMCallTrace
友情提示:需要在SMCallTrace.m中打開第54行的注釋禽翼。

+load為什么會增加4毫秒,Swift呢

aop 的耗時族跛,swift沒有

iOS中用llvm的IR中插樁來統(tǒng)計函數(shù)耗時這個方法也可行

有待學(xué)習(xí)闰挡,嚶嚶嚶

objc的hook是用method swizzle來實現(xiàn),對于swift

使用Time Profiler 或者 使用Clang打樁統(tǒng)計耗時

oc的代碼在編譯時會轉(zhuǎn)成c++礁哄,再轉(zhuǎn)成c长酗,那swift如何轉(zhuǎn)換?

swift 和 c 編譯方式類似。Swift 會先編成 SIL( Swift Intermediate Language)然后再編成機器碼姐仅。

未完待續(xù)…

【干貨推薦】
iOS啟動時間優(yōu)化
iOS App 啟動性能優(yōu)化
今日頭條iOS客戶端啟動速度優(yōu)化
優(yōu)化 App 的啟動時間
《How we cut our iOS app’s launch time in half (with this one cool trick)》
匯編相關(guān)
https://blog.nelhage.com/2010/10/amd64-and-va_arg/
http://infocenter.arm.com/help/topic/com.arm.doc.ihi0055b/IHI0055B_aapcs64.pdf
https://developer.apple.com/library/ios/documentation/Xcode/Conceptual/iPhoneOSABIReference/Articles/ARM64FunctionCallingConventions.html
博客花枫、課程刻盐、開源 推薦 戴神

小結(jié):一枚突然頓悟的小白兔,學(xué)無止境

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末劳翰,一起剝皮案震驚了整個濱河市敦锌,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌佳簸,老刑警劉巖乙墙,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異生均,居然都是意外死亡听想,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門马胧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來汉买,“玉大人,你說我怎么就攤上這事佩脊⊥苷常” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵威彰,是天一觀的道長出牧。 經(jīng)常有香客問我,道長歇盼,這世上最難降的妖魔是什么舔痕? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮豹缀,結(jié)果婚禮上伯复,老公的妹妹穿的比我還像新娘。我一直安慰自己邢笙,他們只是感情好边翼,可當(dāng)我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著鸣剪,像睡著了一般。 火紅的嫁衣襯著肌膚如雪丈积。 梳的紋絲不亂的頭發(fā)上筐骇,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天,我揣著相機與錄音江滨,去河邊找鬼铛纬。 笑死,一個胖子當(dāng)著我的面吹牛唬滑,可吹牛的內(nèi)容都是我干的告唆。 我是一名探鬼主播棺弊,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼擒悬!你這毒婦竟也來了模她?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤懂牧,失蹤者是張志新(化名)和其女友劉穎侈净,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體僧凤,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡畜侦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了躯保。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片旋膳。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖途事,靈堂內(nèi)的尸體忽然破棺而出验懊,到底是詐尸還是另有隱情,我是刑警寧澤盯孙,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布鲁森,位于F島的核電站,受9級特大地震影響振惰,放射性物質(zhì)發(fā)生泄漏歌溉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一骑晶、第九天 我趴在偏房一處隱蔽的房頂上張望痛垛。 院中可真熱鬧,春花似錦桶蛔、人聲如沸匙头。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽蹂析。三九已至,卻和暖如春碟婆,著一層夾襖步出監(jiān)牢的瞬間电抚,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工竖共, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蝙叛,地道東北人。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓公给,卻偏偏與公主長得像借帘,于是被迫代替她去往敵國和親蜘渣。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,792評論 2 345