導(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)槠南拗?,沒有辦法很詳盡的說明一些原理性的東西,只是方便大家了解哪些事情可能跟啟動(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. 如何測量啟動(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ù)之前
在不越獄的情況下,以往很難精確的測量在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)告:
Total pre-maintime:94.33milliseconds(100.0%)? ? ? ? ? dylib loadingtime:61.87milliseconds(65.5%)? ? ? ? ? rebase/bindingtime:3.09milliseconds(3.2%)? ? ? ? ? ? ? ObjC setuptime:10.78milliseconds(11.4%)? ? ? ? ? ? initializertime:18.50milliseconds(19.6%)? ? ? ? ? ? slowest intializers :? ? ? ? ? ? ? libSystem.B.dylib :3.59milliseconds(3.8%)? ? libBacktraceRecording.dylib :3.65milliseconds(3.8%)? ? ? ? ? ? ? ? ? ? ? GTFreeWifi :7.09milliseconds(7.5%)
如何解讀
main()函數(shù)之前總共使用了94.33ms
在94.33ms中,加載動(dòng)態(tài)庫用了61.87ms,指針重定位使用了3.09ms,ObjC類初始化使用了10.78ms,各種初始化使用了18.50ms。
在初始化耗費(fèi)的18.50ms中,用時(shí)最多的三個(gè)初始化是libSystem.B.dylib、libBacktraceRecording.dylib以及GTFreeWifi。
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@implementationAppDelegate- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {self.rootViewController= [[[MQQTabBarController alloc] init] autorelease];self.window= [[[UIWindowalloc] init] autorelease];? ? [self.windowmakeKeyAndVisible];self.window.rootViewController=self.rootViewController;UITabBarController*tabBarViewController = [[[UITabBarControlleralloc] init] autorelease];NSLog(@"%s", __PRETTY_FUNCTION__);returnYES;}...//MQQTabBarController.m@implementationMQQTabBarController- (void)viewDidLoad {NSLog(@"%s", __PRETTY_FUNCTION__);? ? [superviewDidLoad];// Do any additional setup after loading the view.UIViewController*tab1 = [[[MQQTab1ViewController alloc] init] autorelease];? ? tab1.tabBarItem.title= @"red";? ? [selfaddChildViewController:tab1];UIViewController*tab2 = [[[MQQTab2ViewController alloc] init] autorelease];? ? tab2.tabBarItem.title= @"blue";? ? [selfaddChildViewController:tab2];UIViewController*tab3 = [[[MQQTab3ViewController alloc] init] autorelease];? ? tab3.tabBarItem.title= @"green";? ? [selfaddChildViewController:tab3];}...@end
那么-[MQQTabBarController viewDidLoad]、-[AppDelegate application:didFinishLaunchingWithOptions:]、-[MQQTab1ViewController viewDidLoad]、-[MQQTab2ViewController viewDidLoad]、-[MQQTab2ViewController viewDidLoad]完成的先后順序是怎樣的呢?
答案是:
-[MQQTabBarController viewDidLoad]
-[MQQTab1ViewController viewDidLoad]
-[AppDelegate application:didFinishLaunchingWithOptions:]
-[MQQTab2ViewController viewDidLoad](點(diǎn)擊了第二個(gè)tab之后加載)
-[MQQTab3ViewController viewDidLoad](點(diǎn)擊了第三個(gè)tab之后加載)
一般而言,大部分情況下我們都會(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è)部分:
main()函數(shù)之前
main()函數(shù)之后至applicationWillFinishLaunching完成
App完成所有本地?cái)?shù)據(jù)的加載并將相應(yīng)的信息展示給用戶
App完成所有聯(lián)網(wǎng)數(shù)據(jù)的加載并將相應(yīng)的信息展示給用戶
1+2一起決定了我們需要用戶等待多久才能出現(xiàn)一個(gè)主視圖,同時(shí)也是技術(shù)上可以精確測量的時(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)勢不大,我這里也就簡單的把依賴的動(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)生色差或模糊。但是非??上У氖荰inyPNG并不是完全免費(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í)際測試表明,光做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)然需要先測試一下哪些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)畫
本文轉(zhuǎn)載至http://geek.csdn.net/news/detail/232186?ref=myread