1概念
?App啟動過程
* ?解析Info.plist
1 加載相關信息,例如如閃屏 ? ? 2。沙箱建立、權限檢查
* ?Mach-O加載
1。如果是胖二進制文件,尋找合適當前CPU類別的部分
2 ?加載所有依賴的Mach-O文件(遞歸調用Mach-O加載的方法)
3 定位內部、外部指針引用,例如字符串、函數等
4 執行聲明為__attribute__((constructor))的C函數
5 加載類擴展(Category)中的方法
C++靜態對象加載、調用ObjC的?+load?函數
** ? 程序執行
1 調用main() ? 2 ?調用UIApplicationMain() ? 3 調用applicationWillFinishLaunching
1.熱啟動:就是按下home鍵的時候,app還存在一段時間,這時點擊app馬上就能恢復到原狀態,這種啟動我們稱為熱啟動。
2.冷啟動:app被kill掉之后,重新打開啟動過程為冷啟動。
2. 如何測量啟動過程耗時
冷啟動比熱啟動重要
當用戶按下home鍵的時候,iOS的App并不會馬上被kill掉,還會繼續存活若干時間。理想情況下,用戶點擊App的圖標再次回來的時候,App幾乎不需要做什么,就可以還原到退出前的狀態,繼續為用戶服務。這種持續存活的情況下啟動App,我們稱為熱啟動,相對而言冷啟動就是App被kill掉以后一切從頭開始啟動的過程。我們這里只討論App冷啟動的情況。
main()函數之前
在不越獄的情況下,以往很難精確的測量在main()函數之前的啟動耗時,因而我們也往往容易忽略掉這部分數據。小型App確實不需要太過關注這部分。但如果是大型App(自定義的動態庫超過50個、或編譯結果二進制文件超過30MB),這部分耗時將會變得突出。所幸,蘋果已經在Xcode中加入這部分的支持。
蘋果提供的方法
在Xcode的菜單中選擇Project→Scheme→Edit Scheme...,然后找到?Run→?Environment Variables?→+,添加name為DYLD_PRINT_STATISTICSvalue為1的環境變量。
在Xcode運行App時,會在console中得到一個報告。例如,我在WiFi管家中加入以上設置之后,會得到這樣一個報告:
如何解讀
main()函數之前總共使用了94.33ms
在94.33ms中,加載動態庫用了61.87ms,指針重定位使用了3.09ms,ObjC類初始化使用了10.78ms,各種初始化使用了18.50ms。
在初始化耗費的18.50ms中,用時最多的三個初始化是libSystem.B.dylib、libBacktraceRecording.dylib以及GTFreeWifi。
main()函數之后
從main()函數開始至applicationWillFinishLaunching結束,我們統一稱為main()函數之后的部分。
3. 影響啟動性能的因素
App啟動過程中每一個步驟都會影響啟動性能,但是有些部分所消耗的時間少之又少,另外有些部分根本無法避免,考慮到投入產出比,我們只列出我們可以優化的部分:
main()函數之前耗時的影響因素
* 動態庫加載越多,啟動越慢。
* ObjC類越多,啟動越慢
* C的constructor函數越多,啟動越慢
* C++靜態對象越多,啟動越慢
* ObjC的+load越多,啟動越慢
實驗證明,在ObjC類的數目一樣多的情況下,需要加載的動態庫越多,App啟動就越慢。同樣的,在動態庫一樣多的情況下,ObjC的類越多,App的啟動也越慢。需要加載的動態庫從1個上升到10個的時候,用戶幾乎感知不到任何分別,但從10個上升到100個的時候就會變得十分明顯。同理,100個類和1000個類,可能也很難查察覺得出,但1000個類和10000個類的分別就開始明顯起來。
同樣的,盡量不要寫__attribute__((constructor))的C函數,也盡量不要用到C++的靜態對象;至于ObjC的+load方法,似乎大家已經習慣不用它了。任何情況下,能用dispatch_once()來完成的,就盡量不要用到以上的方法。
main()函數之后耗時的影響因素
* 執行main()函數的耗時
* 執行applicationWillFinishLaunching的耗時
* rootViewController及其childViewController的加載、view及其subviews的加載
applicationWillFinishLaunching的耗時
如果有這樣這樣的代碼:
那么-[MQQTabBarController viewDidLoad]、?-[AppDelegate application:didFinishLaunchingWithOptions:]、?-[MQQTab1ViewController viewDidLoad]、?-[MQQTab2ViewController viewDidLoad]、?-[MQQTab2ViewController viewDidLoad]?完成的先后順序是怎樣的呢?
答案是:
1 -[MQQTabBarController viewDidLoad]
2 -[MQQTab1ViewController viewDidLoad]
3 -[AppDelegate application:didFinishLaunchingWithOptions:]
4 -[MQQTab2ViewController viewDidLoad]?(點擊了第二個tab之后加載)
5 -[MQQTab3ViewController viewDidLoad]?(點擊了第三個tab之后加載)
一般而言,大部分情況下我們都會把界面的初始化過程放在viewDidLoad,但是這個過程會影響消耗啟動的時間。特別是在類似TabBarController這種會嵌套childViewController的ViewController的情況,它也會把部分children也初始化,因此各種viewDidLoad會遞歸的進行。
最簡單的解決的方法,是把viewController延后加載,但實際上這屬于一種掩耳盜鈴,確實,applicationWillFinishLaunching的耗時是降下來了,但用戶體驗上并沒有感覺變快。
更好一點的解決方法有點類似facebook,主視圖會第一時間加載,但里面的數據和界面都會延后加載,這樣用戶就會階段性的獲得視覺上的變化,從而在視覺體驗上感覺App啟動得很快。
4 優化的目標
由于每個App的情況有所不同,需要加載的數據量也有所不同,事實上我們無法使用一種統一的標準來衡量不同的App。蘋果。
應該在400ms內完成main()函數之前的加載
整體過程耗時不能超過20秒,否則系統會kill掉進程,App啟動失敗
400ms內完成main()函數前的加載的建議值是怎樣定出來的呢?其實我也沒有太深究過這個問題,但是,當用戶點擊了一個App的圖標時,iOS做動畫到閃屏圖出現的時長正好是這個數字,我想也許跟這個有關。
針對不同規模的App,我們的目標應該有所取舍。例如,對于像手機QQ這種集整個SNG的代碼大成擼出來的App,對動態庫的使用在所難免,但對于WiFi管家,由于在用戶連接WiFi的時候需要非常快速的響應,所以快速啟動就非常重要。
那么,如何定制優化的目標呢?首先,要確定啟動性能的界限,例如,在各種App性能的指標中,哪一此屬于啟動性能的范疇,哪一些則于App的流暢度性能?我認為應該首先把啟動過程分為四個部分:
1 main()函數之前
2 main()函數之后至applicationWillFinishLaunching完成
3 App完成所有本地數據的加載并將相應的信息展示給用戶
4 App完成所有聯網數據的加載并將相應的信息展示給用戶
1+2一起決定了我們需要用戶等待多久才能出現一個主視圖,同時也是技術上可以精確測量的時長,1+2+3決定了用戶視覺上的等待出現有用信息所需要的時長,1+2+3+4決定了我們需要多少時間才能讓我們需要展示給用戶的所有信息全部出現。
淘寶的iOS客戶端無疑是各部分都做得非常優秀的典型。它所承載的業務完全不比微信和手機QQ少,但幾乎瞬間完成了啟動,并利用緩存機制使得用戶馬上看到“貌似完整”的界面,然后立即又刷新了剛剛聯網更新回來的信息。也就是說,無論是技術上還是視覺上,它都非常的“快”。
5 WiFi管家啟動優化實踐
先show一下成果:
1. 移除不需要用到的動態庫
因為WiFi管家是個小項目,用到的動態庫不多,自動化處理的優勢不大,我這里也就簡單的把依賴的動態移除出項目,再根據編譯錯誤一個一個加回來。如果有靠譜的方法,歡迎大家補充一下。
2. 移除不需要用到的類
項目做久了總有一些吊詭的類像幽靈一樣驅之不去,由于【不要相信產品經理】的思想作怪,需求變更后,有些類可能用不上了,但卻因為擔心需求再變回來就沒有移除掉,后來就徹底忘記要移除了。
為了解決這個歷史問題,在這個過程中我試過多種方法來掃描沒有用到的類,其中有一種是編譯后對ObjC類的指針引用進行反向掃描,可惜實際上收獲不是很明顯,而且還要寫很多例外代碼來處理一些特殊情況。后來發現一個叫做fui(Find Unused Imports)的開源項目能很好的分析出不再使用的類,準確率非常高,唯一的問題是它處理不了動態庫和靜態庫里提供的類,也處理不了C++的類模板。
使用方法是在Terminal中cd到項目所在的目錄,然后執行fui find,然后等上那么幾分鐘(是的你沒有看錯,真的需要好幾分鐘甚至需要更長的時間),就可以得到一個列表了。由于這個工具還不是100%靠譜,可根據這個列表,在Xcode中手動檢查并刪除不再用到的類。
實際上,日常對代碼工程的維護非常重要,如果制定好一套半廢棄代碼的維護方法,小問題就不會積累成大問題。有時候對于一些暫時不再使用的代碼,我也很糾結于要不要svn rm,因為從代碼歷史中找刪除掉的文件還是不太方便。不知道大家有沒有相關的經驗可以分享,也請不吝賜教。
3. 合并功能類似的類和擴展(Category)
由于Category的實現原理,和ObjC的動態綁定有很強的關系,所以實際上類的擴展是比較占用啟動時間的。盡量合并一些擴展,會對啟動有一定的優化作用。不過個人認為也不能因為它占用啟動時間而去逃避使用擴展,畢竟程序員的時間比CPU的時間值錢,這里只是強調要合并一些在工程、架構上沒有太大意義的擴展。
4. 壓縮資源圖片
壓縮圖片為什么能加快啟動速度呢?因為啟動的時候大大小小的圖片加載個十來二十個是很正常的,圖片小了,IO操作量就小了,啟動當然就會快了。
事實上,Xcode在編譯App的時候,已經自動把需要打包到App里的資源圖片壓縮過一遍了。然而Xcode的壓縮會相對比較保守。另一方面,我們正常的設計師由于需要符合其正常的審美需要生成的正常的PNG圖片,因此圖片大小是比較大的,然而如果以程序員的直男審美而采用過激的壓縮會直接激怒設計師。
解決各種矛盾的方法就是要找出一種相當靠譜的壓縮方法,而且最好是基本無損的,而且壓縮率還要特別高,至少要比Xcode自動壓縮的效果要更好才有意義。經過各種試驗,最后發現唯一可靠的壓縮算法是TinyPNG,其它各種方法,要么沒效果,要么產生色差或模糊。但是非常可惜的是TinyPNG并不是完全免費的,而且需要通過網絡請求來壓縮圖片(應該是為了保護其牛逼的壓縮算法)。
5. 優化applicationWillFinishLaunching
隨著項目做的時間長了,applicationWillFinishLaunching里要處理的代碼會越積越多,WiFi管家的iOS版本有一段時間沒有控制好,里面的邏輯亂得有點丟人。因為可能涉及到一些項目的安全性問題,這里不能分享所有的優化細節及發現的思路。僅列出在applicationWillFinishLaunching中主要需要處理的業務及相關問題的改進方案。
這里大部分都是一些苦逼活,但有一點特別值得分享的是,有一些優化,是無法在數據上體現的,但是視覺上卻能給用戶較大的提升。例如在【各種業務請求配置更新】的部分,經過分析優化后,啟動過程并發的http請求數量從66條壓縮到了23條,如此一來為啟動成功后新聞資訊及其圖片的加載留出了更多的帶寬,從而保證了在第一時間完成新聞資訊的加載。實際測試表明,光做KPI的事情是不夠的,人還是需要有點理想,經過優化,在視覺體驗上進步非常明顯。
另外,過程中請教過SNG的大牛們,聽說他們因為需要在applicationWillFinishLaunching里處理的業務更多,所以還做了管理器管理這些任務,不過因為WiFi管家是個小項目,有點殺雞用牛刀的感覺,因此沒有深入研究。
6. 優化rootViewController加載
為了給公司節約成本,在優化之前,當然需要先測試一下哪些ViewController的加載耗時比較大,然后再深入到具體業務中看哪些部分存在較大的優化空間。同時,先做優化效果明顯的部分也有利于增強自己的信心。
6 總結
熱啟動優化。
?一.數據優化,將耗時操作做異步處理。
二.檢查NSUserDefaults的存儲,NSUserDefaults實際上是在Library文件夾下會生產一個plist文件,加載的時候是整個plist配置文件全部load到內存中。所以非常頻繁的存取大量數據也是有可能導致APP啟動卡頓的
.冷啟動優化
1 利用DYLD_PRINT_STATISTICS分析main()函數之前的耗時
* 重新梳理架構,減少動態庫、ObjC類的數目,減少Category的數目
* 定期掃描不再使用的動態庫、類、函數,例如每兩個迭代一次
* 用dispatchonce()代替所有的__attribute__((constructor))函數、C++靜態對象初始化、ObjC的+load
* 在設計師可接受的范圍內壓縮圖片的大小,會有意外收獲
2 ?利用錨點分析applicationWillFinishLaunching的耗時
* 將不需要馬上在applicationWillFinishLaunching執行的代碼延后執行
* rootViewController的加載,適當將某一級的childViewController或subviews延后加載
* 如果你的App可能會被后臺拉起并冷啟動,可考慮不加載rootViewController
3不應放過的一些小細節
* 異步操作并不影響指標,但有可能影響交互體驗,例如大量網絡請求導致數據擁堵
* 有時候一些交互上的優化比技術手段效果更明顯,視覺上的快決不是冰冷的數據可以解釋的,好好和你們的設計師談談動畫
熱啟動和冷啟動 以及相關優化 相關文章方案可以參考: