iOS App 启动性能优化
為什么80%的碼農(nóng)都做不了架構(gòu)師?>>> ??
本文來自于騰訊Bugly公眾號(hào)(weixinBugly),未經(jīng)作者同意,請(qǐng)勿轉(zhuǎn)載,原文地址:https://mp.weixin.qq.com/s/Kf3EbDIUuf0aWVT-UCEmbA
作者:samsonxu
導(dǎo)語
本文介紹了如何優(yōu)化iOS App的啟動(dòng)性能。
本文分為四個(gè)部分:
- 第一部分科普了一些和App啟動(dòng)性能相關(guān)的前置知識(shí)
- 第二部分主要講如何定制啟動(dòng)性能的優(yōu)化目標(biāo)
- 第三部分通過在WiFi管家這個(gè)具體項(xiàng)目的優(yōu)化過程,分享一些有用的經(jīng)驗(yàn)
- 第四部分是關(guān)鍵點(diǎn)的總結(jié)。
【第一部分】一些小科普
因?yàn)槠南拗?#xff0c;沒有辦法很詳盡的說明一些原理性的東西,只是方便大家了解哪些事情可能跟啟動(dòng)性能有關(guān)。同時(shí),內(nèi)容相對(duì)也比較入門,大神們請(qǐng)?zhí)^這一部分。
1. App啟動(dòng)過程
- 解析Info.plist
- 加載相關(guān)信息,例如如閃屏
- 沙箱建立、權(quán)限檢查
- Mach-O加載
- 如果是胖二進(jìn)制文件,尋找合適當(dāng)前CPU類別的部分
- 加載所有依賴的Mach-O文件(遞歸調(diào)用Mach-O加載的方法)
- 定位內(nèi)部、外部指針引用,例如字符串、函數(shù)等
- 執(zhí)行聲明為__attribute__((constructor))的C函數(shù)
- 加載類擴(kuò)展(Category)中的方法
- C++靜態(tài)對(duì)象加載、調(diào)用ObjC的 +load 函數(shù)
- 程序執(zhí)行
- 調(diào)用main()
- 調(diào)用UIApplicationMain()
- 調(diào)用applicationWillFinishLaunching
2. 如何測(cè)量啟動(dòng)過程耗時(shí)
冷啟動(dòng)比熱啟動(dòng)重要
當(dāng)用戶按下home鍵的時(shí)候,iOS的App并不會(huì)馬上被kill掉,還會(huì)繼續(xù)存活若干時(shí)間。理想情況下,用戶點(diǎn)擊App的圖標(biāo)再次回來的時(shí)候,App幾乎不需要做什么,就可以還原到退出前的狀態(tài),繼續(xù)為用戶服務(wù)。這種持續(xù)存活的情況下啟動(dòng)App,我們稱為熱啟動(dòng),相對(duì)而言冷啟動(dòng)就是App被kill掉以后一切從頭開始啟動(dòng)的過程。我們這里只討論App冷啟動(dòng)的情況。
main()函數(shù)之前
在不越獄的情況下,以往很難精確的測(cè)量在main()函數(shù)之前的啟動(dòng)耗時(shí),因而我們也往往容易忽略掉這部分?jǐn)?shù)據(jù)。小型App確實(shí)不需要太過關(guān)注這部分。但如果是大型App(自定義的動(dòng)態(tài)庫超過50個(gè)、或編譯結(jié)果二進(jìn)制文件超過30MB),這部分耗時(shí)將會(huì)變得突出。所幸,蘋果已經(jīng)在Xcode中加入這部分的支持。
蘋果提供的方法
-
在Xcode的菜單中選擇Project→Scheme→Edit Scheme...,然后找到 Run → Environment Variables →+,添加name為DYLD_PRINT_STATISTICSvalue為1的環(huán)境變量。
-
在Xcode運(yùn)行App時(shí),會(huì)在console中得到一個(gè)報(bào)告。例如,我在WiFi管家中加入以上設(shè)置之后,會(huì)得到這樣一個(gè)報(bào)告:
如何解讀
main()函數(shù)之后
從main()函數(shù)開始至applicationWillFinishLaunching結(jié)束,我們統(tǒng)一稱為main()函數(shù)之后的部分。
3. 影響啟動(dòng)性能的因素
App啟動(dòng)過程中每一個(gè)步驟都會(huì)影響啟動(dòng)性能,但是有些部分所消耗的時(shí)間少之又少,另外有些部分根本無法避免,考慮到投入產(chǎn)出比,我們只列出我們可以優(yōu)化的部分:
main()函數(shù)之前耗時(shí)的影響因素
- 動(dòng)態(tài)庫加載越多,啟動(dòng)越慢。
- ObjC類越多,啟動(dòng)越慢
- C的constructor函數(shù)越多,啟動(dòng)越慢
- C++靜態(tài)對(duì)象越多,啟動(dòng)越慢
- ObjC的+load越多,啟動(dòng)越慢
實(shí)驗(yàn)證明,在ObjC類的數(shù)目一樣多的情況下,需要加載的動(dòng)態(tài)庫越多,App啟動(dòng)就越慢。同樣的,在動(dòng)態(tài)庫一樣多的情況下,ObjC的類越多,App的啟動(dòng)也越慢。需要加載的動(dòng)態(tài)庫從1個(gè)上升到10個(gè)的時(shí)候,用戶幾乎感知不到任何分別,但從10個(gè)上升到100個(gè)的時(shí)候就會(huì)變得十分明顯。同理,100個(gè)類和1000個(gè)類,可能也很難查察覺得出,但1000個(gè)類和10000個(gè)類的分別就開始明顯起來。
同樣的,盡量不要寫__attribute__((constructor))的C函數(shù),也盡量不要用到C++的靜態(tài)對(duì)象;至于ObjC的+load方法,似乎大家已經(jīng)習(xí)慣不用它了。任何情況下,能用dispatch_once()來完成的,就盡量不要用到以上的方法。
main()函數(shù)之后耗時(shí)的影響因素
- 執(zhí)行main()函數(shù)的耗時(shí)
- 執(zhí)行applicationWillFinishLaunching的耗時(shí)
- rootViewController及其childViewController的加載、view及其subviews的加載
applicationWillFinishLaunching的耗時(shí)
如果有這樣這樣的代碼:
//AppDelegate.m @implementation AppDelegate- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {self.rootViewController = [[[MQQTabBarController alloc] init] autorelease];self.window = [[[UIWindow alloc] init] autorelease];[self.window makeKeyAndVisible];self.window.rootViewController = self.rootViewController;UITabBarController *tabBarViewController = [[[UITabBarController alloc] init] autorelease];NSLog(@"%s", __PRETTY_FUNCTION__);return YES; }...//MQQTabBarController.m @implementation MQQTabBarController- (void)viewDidLoad {NSLog(@"%s", __PRETTY_FUNCTION__);[super viewDidLoad];// Do any additional setup after loading the view.UIViewController *tab1 = [[[MQQTab1ViewController alloc] init] autorelease];tab1.tabBarItem.title = @"red";[self addChildViewController:tab1];UIViewController *tab2 = [[[MQQTab2ViewController alloc] init] autorelease];tab2.tabBarItem.title = @"blue";[self addChildViewController:tab2];UIViewController *tab3 = [[[MQQTab3ViewController alloc] init] autorelease];tab3.tabBarItem.title = @"green";[self addChildViewController:tab3]; }... @end那么-[MQQTabBarController viewDidLoad]、 -[AppDelegate application:didFinishLaunchingWithOptions:]、 -[MQQTab1ViewController viewDidLoad]、 -[MQQTab2ViewController viewDidLoad]、 -[MQQTab2ViewController viewDidLoad] 完成的先后順序是怎樣的呢?
答案是:
一般而言,大部分情況下我們都會(huì)把界面的初始化過程放在viewDidLoad,但是這個(gè)過程會(huì)影響消耗啟動(dòng)的時(shí)間。特別是在類似TabBarController這種會(huì)嵌套childViewController的ViewController的情況,它也會(huì)把部分children也初始化,因此各種viewDidLoad會(huì)遞歸的進(jìn)行。
最簡單的解決的方法,是把viewController延后加載,但實(shí)際上這屬于一種掩耳盜鈴,確實(shí),applicationWillFinishLaunching的耗時(shí)是降下來了,但用戶體驗(yàn)上并沒有感覺變快。
更好一點(diǎn)的解決方法有點(diǎn)類似facebook,主視圖會(huì)第一時(shí)間加載,但里面的數(shù)據(jù)和界面都會(huì)延后加載,這樣用戶就會(huì)階段性的獲得視覺上的變化,從而在視覺體驗(yàn)上感覺App啟動(dòng)得很快。
【第二部分】優(yōu)化的目標(biāo)
由于每個(gè)App的情況有所不同,需要加載的數(shù)據(jù)量也有所不同,事實(shí)上我們無法使用一種統(tǒng)一的標(biāo)準(zhǔn)來衡量不同的App。蘋果。
- 應(yīng)該在400ms內(nèi)完成main()函數(shù)之前的加載
- 整體過程耗時(shí)不能超過20秒,否則系統(tǒng)會(huì)kill掉進(jìn)程,App啟動(dòng)失敗
400ms內(nèi)完成main()函數(shù)前的加載的建議值是怎樣定出來的呢?其實(shí)我也沒有太深究過這個(gè)問題,但是,當(dāng)用戶點(diǎn)擊了一個(gè)App的圖標(biāo)時(shí),iOS做動(dòng)畫到閃屏圖出現(xiàn)的時(shí)長正好是這個(gè)數(shù)字,我想也許跟這個(gè)有關(guān)。
針對(duì)不同規(guī)模的App,我們的目標(biāo)應(yīng)該有所取舍。例如,對(duì)于像手機(jī)QQ這種集整個(gè)SNG的代碼大成擼出來的App,對(duì)動(dòng)態(tài)庫的使用在所難免,但對(duì)于WiFi管家,由于在用戶連接WiFi的時(shí)候需要非常快速的響應(yīng),所以快速啟動(dòng)就非常重要。
那么,如何定制優(yōu)化的目標(biāo)呢?首先,要確定啟動(dòng)性能的界限,例如,在各種App性能的指標(biāo)中,哪一此屬于啟動(dòng)性能的范疇,哪一些則于App的流暢度性能?我認(rèn)為應(yīng)該首先把啟動(dòng)過程分為四個(gè)部分:
1+2一起決定了我們需要用戶等待多久才能出現(xiàn)一個(gè)主視圖,同時(shí)也是技術(shù)上可以精確測(cè)量的時(shí)長,1+2+3決定了用戶視覺上的等待出現(xiàn)有用信息所需要的時(shí)長,1+2+3+4決定了我們需要多少時(shí)間才能讓我們需要展示給用戶的所有信息全部出現(xiàn)。
淘寶的iOS客戶端無疑是各部分都做得非常優(yōu)秀的典型。它所承載的業(yè)務(wù)完全不比微信和手機(jī)QQ少,但幾乎瞬間完成了啟動(dòng),并利用緩存機(jī)制使得用戶馬上看到“貌似完整”的界面,然后立即又刷新了剛剛聯(lián)網(wǎng)更新回來的信息。也就是說,無論是技術(shù)上還是視覺上,它都非常的“快”。
【第三部分】WiFi管家啟動(dòng)優(yōu)化實(shí)踐
先show一下成果:
1. 移除不需要用到的動(dòng)態(tài)庫
因?yàn)閃iFi管家是個(gè)小項(xiàng)目,用到的動(dòng)態(tài)庫不多,自動(dòng)化處理的優(yōu)勢(shì)不大,我這里也就簡單的把依賴的動(dòng)態(tài)移除出項(xiàng)目,再根據(jù)編譯錯(cuò)誤一個(gè)一個(gè)加回來。如果有靠譜的方法,歡迎大家補(bǔ)充一下。
2. 移除不需要用到的類
項(xiàng)目做久了總有一些吊詭的類像幽靈一樣驅(qū)之不去,由于【不要相信產(chǎn)品經(jīng)理】的思想作怪,需求變更后,有些類可能用不上了,但卻因?yàn)閾?dān)心需求再變回來就沒有移除掉,后來就徹底忘記要移除了。
為了解決這個(gè)歷史問題,在這個(gè)過程中我試過多種方法來掃描沒有用到的類,其中有一種是編譯后對(duì)ObjC類的指針引用進(jìn)行反向掃描,可惜實(shí)際上收獲不是很明顯,而且還要寫很多例外代碼來處理一些特殊情況。后來發(fā)現(xiàn)一個(gè)叫做fui(Find Unused Imports)的開源項(xiàng)目能很好的分析出不再使用的類,準(zhǔn)確率非常高,唯一的問題是它處理不了動(dòng)態(tài)庫和靜態(tài)庫里提供的類,也處理不了C++的類模板。
使用方法是在Terminal中cd到項(xiàng)目所在的目錄,然后執(zhí)行fui find,然后等上那么幾分鐘(是的你沒有看錯(cuò),真的需要好幾分鐘甚至需要更長的時(shí)間),就可以得到一個(gè)列表了。由于這個(gè)工具還不是100%靠譜,可根據(jù)這個(gè)列表,在Xcode中手動(dòng)檢查并刪除不再用到的類。
實(shí)際上,日常對(duì)代碼工程的維護(hù)非常重要,如果制定好一套半廢棄代碼的維護(hù)方法,小問題就不會(huì)積累成大問題。有時(shí)候?qū)τ谝恍簳r(shí)不再使用的代碼,我也很糾結(jié)于要不要svn rm,因?yàn)閺拇a歷史中找刪除掉的文件還是不太方便。不知道大家有沒有相關(guān)的經(jīng)驗(yàn)可以分享,也請(qǐng)不吝賜教。
3. 合并功能類似的類和擴(kuò)展(Category)
由于Category的實(shí)現(xiàn)原理,和ObjC的動(dòng)態(tài)綁定有很強(qiáng)的關(guān)系,所以實(shí)際上類的擴(kuò)展是比較占用啟動(dòng)時(shí)間的。盡量合并一些擴(kuò)展,會(huì)對(duì)啟動(dòng)有一定的優(yōu)化作用。不過個(gè)人認(rèn)為也不能因?yàn)樗加脝?dòng)時(shí)間而去逃避使用擴(kuò)展,畢竟程序員的時(shí)間比CPU的時(shí)間值錢,這里只是強(qiáng)調(diào)要合并一些在工程、架構(gòu)上沒有太大意義的擴(kuò)展。
4. 壓縮資源圖片
壓縮圖片為什么能加快啟動(dòng)速度呢?因?yàn)閱?dòng)的時(shí)候大大小小的圖片加載個(gè)十來二十個(gè)是很正常的,圖片小了,IO操作量就小了,啟動(dòng)當(dāng)然就會(huì)快了。
事實(shí)上,Xcode在編譯App的時(shí)候,已經(jīng)自動(dòng)把需要打包到App里的資源圖片壓縮過一遍了。然而Xcode的壓縮會(huì)相對(duì)比較保守。另一方面,我們正常的設(shè)計(jì)師由于需要符合其正常的審美需要生成的正常的PNG圖片,因此圖片大小是比較大的,然而如果以程序員的直男審美而采用過激的壓縮會(huì)直接激怒設(shè)計(jì)師。
解決各種矛盾的方法就是要找出一種相當(dāng)靠譜的壓縮方法,而且最好是基本無損的,而且壓縮率還要特別高,至少要比Xcode自動(dòng)壓縮的效果要更好才有意義。經(jīng)過各種試驗(yàn),最后發(fā)現(xiàn)唯一可靠的壓縮算法是TinyPNG,其它各種方法,要么沒效果,要么產(chǎn)生色差或模糊。但是非常可惜的是TinyPNG并不是完全免費(fèi)的,而且需要通過網(wǎng)絡(luò)請(qǐng)求來壓縮圖片(應(yīng)該是為了保護(hù)其牛逼的壓縮算法)。
為了解決這個(gè)問題,我寫了一個(gè)類來執(zhí)行這個(gè)請(qǐng)求,請(qǐng)參見閱讀原文里的SSTinyPNGRequest和SSPNGCompressor。因?yàn)檫@個(gè)項(xiàng)目只有我一個(gè)人在用所以代碼寫得有點(diǎn)隨意,有問題可以私聊也可以在評(píng)論里問,有改進(jìn)的方法也非常歡迎指正。另外說明一下,使用這個(gè)類需要你自行到 這里 申請(qǐng)APIKey,每一個(gè)用戶每月有500張圖片壓縮是免費(fèi)的,而每個(gè)郵箱可以注冊(cè)一個(gè)用戶,你懂的。
5. 優(yōu)化applicationWillFinishLaunching
隨著項(xiàng)目做的時(shí)間長了,applicationWillFinishLaunching里要處理的代碼會(huì)越積越多,WiFi管家的iOS版本有一段時(shí)間沒有控制好,里面的邏輯亂得有點(diǎn)丟人。因?yàn)榭赡苌婕暗揭恍╉?xiàng)目的安全性問題,這里不能分享所有的優(yōu)化細(xì)節(jié)及發(fā)現(xiàn)的思路。僅列出在applicationWillFinishLaunching中主要需要處理的業(yè)務(wù)及相關(guān)問題的改進(jìn)方案。
這里大部分都是一些苦逼活,但有一點(diǎn)特別值得分享的是,有一些優(yōu)化,是無法在數(shù)據(jù)上體現(xiàn)的,但是視覺上卻能給用戶較大的提升。例如在【各種業(yè)務(wù)請(qǐng)求配置更新】的部分,經(jīng)過分析優(yōu)化后,啟動(dòng)過程并發(fā)的http請(qǐng)求數(shù)量從66條壓縮到了23條,如此一來為啟動(dòng)成功后新聞資訊及其圖片的加載留出了更多的帶寬,從而保證了在第一時(shí)間完成新聞資訊的加載。實(shí)際測(cè)試表明,光做KPI的事情是不夠的,人還是需要有點(diǎn)理想,經(jīng)過優(yōu)化,在視覺體驗(yàn)上進(jìn)步非常明顯。
另外,過程中請(qǐng)教過SNG的大牛們,聽說他們因?yàn)樾枰赼pplicationWillFinishLaunching里處理的業(yè)務(wù)更多,所以還做了管理器管理這些任務(wù),不過因?yàn)閃iFi管家是個(gè)小項(xiàng)目,有點(diǎn)殺雞用牛刀的感覺,因此沒有深入研究。
6. 優(yōu)化rootViewController加載
考慮到我作為一只高級(jí)程序猴,工資很高,為了給公司節(jié)約成本,在優(yōu)化之前,當(dāng)然需要先測(cè)試一下哪些ViewController的加載耗時(shí)比較大,然后再深入到具體業(yè)務(wù)中看哪些部分存在較大的優(yōu)化空間。同時(shí),先做優(yōu)化效果明顯的部分也有利于增強(qiáng)自己的信心。
在開始講述問題之前,我們先來看一下WiFi管家的UI層次結(jié)構(gòu):
一個(gè)看似簡單的界面由于承載了很多業(yè)務(wù)需求,代碼量其實(shí)已經(jīng)非常驚人。這里我不具體講述這些驚人的業(yè)務(wù)量了,抽象而言可WiFi管家的UI架構(gòu)總體而言基于TabBarController的框架,三個(gè)tab分別是“連接”、“發(fā)現(xiàn)”及“我的”。App啟動(dòng)的時(shí)候,根據(jù)加載原理,會(huì)加載TabBarController、第一個(gè)Tab(“連接”)的ViewController及其所有childViewController。
UI構(gòu)架請(qǐng)看如下示意圖,其中藍(lán)色的部分需要在App啟動(dòng)的時(shí)候立即加載:
對(duì)所有啟動(dòng)相關(guān)的模塊打錨點(diǎn)計(jì)算耗時(shí)后,發(fā)現(xiàn)tabBarController和connectingViewController分別占用了applicationWillFinishLaunching耗時(shí)的31%和24%。加載耗費(fèi)了大量時(shí)間,這跟它所需要承載的邏輯任務(wù)似乎并不對(duì)稱。于是檢查相關(guān)代碼進(jìn)行深入分析,發(fā)現(xiàn)了幾個(gè)問題比較嚴(yán)重:
有些程序員可能架構(gòu)意識(shí)不是太強(qiáng),直接在tabBarController的啟動(dòng)過程中插入了各種奇怪的業(yè)務(wù),例如檢查WiFi連接狀態(tài)變化、配置拉取,而這些業(yè)務(wù)顯然應(yīng)該在另外的某些地方統(tǒng)一處理,而不應(yīng)該在一個(gè)ViewController上。
由于一些歷史原因,連接頁的視圖控制器connectingViewController包含了三個(gè)childViewController:WiFiViewController、3GViewController、errorViewController,分別在WiFi狀態(tài)、3G狀態(tài)和出錯(cuò)狀態(tài)下展示界面(三選一,其中一個(gè)展示的時(shí)候其它兩個(gè)視圖會(huì)隱藏)。
大部分view都是直接加載完的。有些界面的加載非常復(fù)雜,比如再進(jìn)入App時(shí)會(huì)展示一個(gè)檢查WiFi可用性和安全性的動(dòng)畫,由于需要疊加較多圖片,這部分視圖的加載耗時(shí)較多。
由于隨著幾次改版之后,連接頁的UI架構(gòu)已經(jīng)變得很不合理,歷史包袱還是比較重的,而且耦合比較嚴(yán)重,幾乎無法改動(dòng),因此決定重構(gòu)。至于tabBarController,檢查代碼后決定簡單的把不相關(guān)的業(yè)務(wù)做一些遷移,優(yōu)化childViewController的加載過程,不作重構(gòu)。
改進(jìn)后的結(jié)構(gòu)大致如下圖,其中藍(lán)色部分需要在App啟動(dòng)的時(shí)候立即加載:
由于本篇主要講啟動(dòng)性能優(yōu)化,重構(gòu)涉及的軟件工程和設(shè)計(jì)模式方面的東西就不詳細(xì)論述了,對(duì)啟動(dòng)優(yōu)化的過程,主要是使用了更合理的分層結(jié)構(gòu),使得啟動(dòng)得以在更短的時(shí)間內(nèi)完成。
至此,WiFi管家的啟動(dòng)性能基本優(yōu)化完畢。
7. 挖掘最后一點(diǎn)性能優(yōu)化
由于WiFi管家是一個(gè)具有WiFi連接能力的App,因此有可能在后臺(tái)過程中完成冷啟動(dòng)過程(實(shí)際上是在用戶進(jìn)入系統(tǒng)的WiFi設(shè)置時(shí),iOS會(huì)啟動(dòng)WiFi管家,以便請(qǐng)求WiFi密碼)。在這種情況下,整個(gè)rootViewController都是不需要加載的。
【第四部分】總結(jié)
- 利用DYLD_PRINT_STATISTICS分析main()函數(shù)之前的耗時(shí)
- 重新梳理架構(gòu),減少動(dòng)態(tài)庫、ObjC類的數(shù)目,減少Category的數(shù)目
- 定期掃描不再使用的動(dòng)態(tài)庫、類、函數(shù),例如每兩個(gè)迭代一次
- 用dispatch_once()代替所有的 attribute((constructor)) 函數(shù)、C++靜態(tài)對(duì)象初始化、ObjC的+load
- 在設(shè)計(jì)師可接受的范圍內(nèi)壓縮圖片的大小,會(huì)有意外收獲
- 利用錨點(diǎn)分析applicationWillFinishLaunching的耗時(shí)
- 將不需要馬上在applicationWillFinishLaunching執(zhí)行的代碼延后執(zhí)行
- rootViewController的加載,適當(dāng)將某一級(jí)的childViewController或subviews延后加載
- 如果你的App可能會(huì)被后臺(tái)拉起并冷啟動(dòng),可考慮不加載rootViewController
- 不應(yīng)放過的一些小細(xì)節(jié)
- 異步操作并不影響指標(biāo),但有可能影響交互體驗(yàn),例如大量網(wǎng)絡(luò)請(qǐng)求導(dǎo)致數(shù)據(jù)擁堵
- 有時(shí)候一些交互上的優(yōu)化比技術(shù)手段效果更明顯,視覺上的快決不是冰冷的數(shù)據(jù)可以解釋的,好好和你們的設(shè)計(jì)師談?wù)剟?dòng)畫
更多精彩內(nèi)容歡迎關(guān)注騰訊 Bugly的微信公眾賬號(hào):
騰訊 Bugly是一款專為移動(dòng)開發(fā)者打造的質(zhì)量監(jiān)控工具,幫助開發(fā)者快速,便捷的定位線上應(yīng)用崩潰的情況以及解決方案。智能合并功能幫助開發(fā)同學(xué)把每天上報(bào)的數(shù)千條 Crash 根據(jù)根因合并分類,每日日?qǐng)?bào)會(huì)列出影響用戶數(shù)最多的崩潰,精準(zhǔn)定位功能幫助開發(fā)同學(xué)定位到出問題的代碼行,實(shí)時(shí)上報(bào)可以在發(fā)布后快速的了解應(yīng)用的質(zhì)量情況,適配最新的 iOS, Android 官方操作系統(tǒng),鵝廠的工程師都在使用,快來加入我們吧!
轉(zhuǎn)載于:https://my.oschina.net/bugly/blog/1512600
總結(jié)
以上是生活随笔為你收集整理的iOS App 启动性能优化的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: php-fpm优化
- 下一篇: MySQL空密码用户清理