優(yōu)化 App 的啟動(dòng)時(shí)間實(shí)踐 iOS

前言

當(dāng)用戶按下home鍵的時(shí)候隅俘,iOS的App并不會(huì)馬上被kill掉,還會(huì)繼續(xù)存活若干時(shí)間笤喳。理想情況下为居,用戶點(diǎn)擊App的圖標(biāo)再次回來(lái)的時(shí)候,App幾乎不需要做什么杀狡,就可以還原到退出前的狀態(tài)蒙畴,繼續(xù)為用戶服務(wù)。這種持續(xù)存活的情況下啟動(dòng)App呜象,我們稱為熱啟動(dòng)膳凝,相對(duì)而言冷啟動(dòng)就是App被kill掉以后一切從頭開(kāi)始啟動(dòng)的過(guò)程。我們這里只討論App冷啟動(dòng)的情況恭陡。

對(duì)于冷啟動(dòng)來(lái)說(shuō)蹬音,啟動(dòng)時(shí)間是指從用戶點(diǎn)擊 APP 那一刻開(kāi)始到用戶看到第一個(gè)界面這中間的時(shí)間。我們進(jìn)行優(yōu)化的時(shí)候休玩,我們將啟動(dòng)時(shí)間分為 pre-main 時(shí)間和 main 函數(shù)到第一個(gè)界面渲染完成時(shí)間這兩個(gè)部分著淆。
因?yàn)?APP 的入口在 main 函數(shù) ,在 main 函數(shù)之后我們的代碼才會(huì)執(zhí)行拴疤。

這里有兩個(gè)階段

1. pre-main階段

1.1. 加載應(yīng)用的可執(zhí)行文件

1.2. 加載動(dòng)態(tài)鏈接庫(kù)加載器dyld(dynamic loader)

1.3. dyld遞歸加載應(yīng)用所有依賴的dylib(dynamic library 動(dòng)態(tài)鏈接庫(kù))

2. main()階段

2.1. dyld調(diào)用main() 

2.2. 調(diào)用UIApplicationMain() 

2.3. 調(diào)用applicationWillFinishLaunching

2.4. 調(diào)用didFinishLaunchingWithOptions

我們把 pre-main階段稱為 t1永部,main()階段一直到首個(gè)頁(yè)面加載完成稱為 t2

t1 時(shí)間的優(yōu)化分析

t1部分主要參考自APP啟動(dòng)優(yōu)化的一次實(shí)踐
其中 t1蘋(píng)果提供了內(nèi)建的測(cè)量方法, Xcode 中 Edit scheme -> Run -> Auguments 將環(huán)境變量 DYLD_PRINT_STATISTICS 設(shè)為 1

//結(jié)果為
Total pre-main time: 1.4 seconds (100.0%)
         dylib loading time: 1.3 seconds (89.4%)
        rebase/binding time:  36.75 milliseconds (2.5%)
            ObjC setup time:  35.65 milliseconds (2.4%)
           initializer time:  80.97 milliseconds (5.5%)
           slowest intializers :
             libSystem.B.dylib :  12.63 milliseconds (0.8%)
//解讀
1呐矾、main()函數(shù)之前總共使用了1.4s

2苔埋、在94.33ms中,加載動(dòng)態(tài)庫(kù)用了1.3s蜒犯,指針重定位使用了36.75ms组橄,ObjC類初始化使用了35.65ms荞膘,各種初始化使用了80.97ms。

3晨炕、在初始化耗費(fèi)的80.97ms中衫画,用時(shí)最多的初始化是libSystem.B.dylib。

可以看到,我的 dylib loading time 花費(fèi)了 1.3s時(shí)間瓮栗,

其中各部分的作用是

加載dylib
分析每個(gè)dylib(大部分是iOS系統(tǒng)的)削罩,找到其Mach-O文件,
打開(kāi)并讀取驗(yàn)證有效性费奸,找到代碼簽名注冊(cè)到內(nèi)核弥激,
最后對(duì)dylib的每個(gè)segment調(diào)用mmap()。
rebase/bind
dylib加載完成之后愿阐,它們處于相互獨(dú)立的狀態(tài)微服,需要綁定起來(lái)。

在dylib的加載過(guò)程中缨历,系統(tǒng)為了安全考慮以蕴,引入了ASLR(Address Space Layout Randomization)技術(shù)和代碼簽名。
由于ASLR的存在辛孵,鏡像(Image丛肮,包括可執(zhí)行文件、dylib和bundle)會(huì)在隨機(jī)的地址上加載魄缚,和之前指針指向的地址(preferred_address)會(huì)有一個(gè)偏差(slide)缔御,dyld需要修正這個(gè)偏差榨汤,來(lái)指向正確的地址。
Rebase在前到踏,Bind在后培遵,Rebase做的是將鏡像讀入內(nèi)存村砂,修正鏡像內(nèi)部的指針保屯,性能消耗主要在IO辜膝。
Bind做的是查詢符號(hào)表,設(shè)置指向鏡像外部的指針飞蛹,性能消耗主要在CPU計(jì)算须肆。
OC setup
OC的runtime需要維護(hù)一張類名與類的方法列表的全局表。
dyld做了如下操作:

對(duì)所有聲明過(guò)的OC類桩皿,將其注冊(cè)到這個(gè)全局表中(class registration)
將category的方法插入到類的方法列表中(category registration)
檢查每個(gè)selector的唯一性(selector uniquing)
如果在各個(gè) OC 類別的 ‘load’方法里做了不少事情(如在里面使用 Method swizzle),那么這是pre-main階段最耗時(shí)的部分。dyld運(yùn)行APP的初始化函數(shù)幢炸,調(diào)用每個(gè)OC類的+load方法泄隔,調(diào)用C++的構(gòu)造器函數(shù)(attribute((constructor))修飾),創(chuàng)建非基本類型的C++靜態(tài)全局變量宛徊,然后執(zhí)行main函數(shù)佛嬉。

優(yōu)化思路是

1. 移除不需要用到的動(dòng)態(tài)庫(kù)
2. 移除不需要用到的類
3. 合并功能類似的類和擴(kuò)展
4. 盡量避免在+load方法里執(zhí)行的操作逻澳,可以推遲到+initialize方法中。

t2 時(shí)間的優(yōu)化分析

t2使用了來(lái)自NewPan大大 的打點(diǎn)計(jì)時(shí)器BLStopwatch

檢測(cè)耗時(shí)

可以看到暖呕,我的 APP 加載時(shí)間并沒(méi)有很慢斜做,但是也想看一看有沒(méi)有優(yōu)化的空間。

didFinishLaunchingWithOptions 方法里我們一般都有以下的邏輯:

初始化第三方 SDK
配置 APP 運(yùn)行需要的環(huán)境
自己的一些工具類的初始化
...

這里主要參考[iOS]一次立竿見(jiàn)影的啟動(dòng)時(shí)間優(yōu)化
從優(yōu)化圖可以看到湾揽,我的應(yīng)用的跳轉(zhuǎn)邏輯是 打開(kāi) -> 廣告頁(yè) -> 首頁(yè)瓤逼,首頁(yè)的UI 架構(gòu)是:

UITabBarC管理一堆 UINavigationC

但是如果 UI 架構(gòu)如上,并且在didFinishLaunchingWithOptions里面設(shè)置了根視圖

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    NSLog(@"didFinishLaunchingWithOptions 開(kāi)始執(zhí)行");

    self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
    TestTabBarController *tabBarVc = [TestTabBarController new];
    self.window.rootViewController = tabBarVc;
    [self.window makeKeyAndVisible];

    NSLog(@"didFinishLaunchingWithOptions 跑完了");

    return YES;
}

然后我們來(lái)到 TestTabBarController 里的 viewDidLoad方法里進(jìn)行它的 viewControllers 的設(shè)置库物,然后再進(jìn)入到每個(gè) viewControllerviewDidLoad 方法里進(jìn)行更多的初始化操作霸旗。那么你覺(jué)得從 didFinishLaunchingWithOptions 到最后顯示展示的 viewControllerviewDidLoad 這些方法的執(zhí)行順序是怎么樣的呢?

didFinishLaunchingWithOptions 開(kāi)始執(zhí)行 
開(kāi)始加載 TestTabBarController 的 viewDidLoad
didFinishLaunchingWithOptions 跑完了
開(kāi)始加載 TestViewController 的 viewDidLoad, 然后執(zhí)行一堆初始化的操作

TestTabBarController 中操作了 TestViewControllerview 的話戚揭,那么調(diào)用順序?qū)?huì)是這樣:

didFinishLaunchingWithOptions 開(kāi)始執(zhí)行 
開(kāi)始加載 TestTabBarController 的 viewDidLoad
開(kāi)始加載 TestViewController 的 viewDidLoad, 然后執(zhí)行一堆初始化的操作
didFinishLaunchingWithOptions 跑完了

這樣的問(wèn)題就是當(dāng)我們把界面的初始化诱告、網(wǎng)絡(luò)請(qǐng)求、數(shù)據(jù)解析民晒、視圖渲染等操作放在了viewDidLoad 方法里精居,這樣一來(lái)每次啟動(dòng) APP 的時(shí)候,在用戶看到第一個(gè)頁(yè)面之前潜必,我們要把這些事件全部都處理完靴姿,才會(huì)進(jìn)入到視圖渲染階段。

一般來(lái)說(shuō)刮便,我們放到didFinishLaunchingWithOptions執(zhí)行的代碼空猜,有很多初始化操作,如日志恨旱,統(tǒng)計(jì)辈毯,SDK配置等。盡量做到只放必需的搜贤,其他的可以延遲到MainViewController展示完成viewDidAppear以后谆沃。

* 日志、統(tǒng)計(jì)等必須在 APP 一啟動(dòng)就最先配置的事件
* 項(xiàng)目配置仪芒、環(huán)境配置唁影、用戶信息的初始化 、推送掂名、IM等事件
* 其他 SDK 和配置事件
  • 第一類据沈,必須第一時(shí)間啟動(dòng),仍然把它留在 didFinishLaunchingWithOptions 里啟動(dòng)饺蔑。
  • 第二類锌介,這些功能在用戶進(jìn)入 APP 主體的之前是必須要加載完的,我把他放到廣告頁(yè)面的viewDidAppear啟動(dòng)。
  • 第三類孔祸,由于啟動(dòng)時(shí)間不是必須的隆敢,所以我們可以放在第一個(gè)界面的 viewDidAppear 方法里,這里完全不會(huì)影響到啟動(dòng)時(shí)間崔慧。
優(yōu)化后

這是優(yōu)化后的啟動(dòng)時(shí)間

優(yōu)化思路

梳理各個(gè)三方庫(kù)拂蝎,找到可以延遲加載的庫(kù),做延遲加載處理惶室,比如放到首頁(yè)控制器的viewDidAppear方法里温自。

梳理業(yè)務(wù)邏輯,把可以延遲執(zhí)行的邏輯拇涤,做延遲執(zhí)行處理捣作。比如檢查新版本、注冊(cè)推送通知等邏輯鹅士。

避免復(fù)雜/多余的計(jì)算券躁。

避免在首頁(yè)控制器的viewDidLoad和viewWillAppear做太多事情,這2個(gè)方法執(zhí)行完掉盅,首頁(yè)控制器才能顯示也拜,部分可以延遲創(chuàng)建的視圖應(yīng)做延遲創(chuàng)建/懶加載處理。

采用性能更好的API趾痘。

首頁(yè)控制器用純代碼方式來(lái)構(gòu)建慢哈。

[iOS]一次立竿見(jiàn)影的啟動(dòng)時(shí)間優(yōu)化 提到了使用一個(gè)工具類來(lái)管理的方法,可以比較方便的管理優(yōu)化永票。

總結(jié)

性價(jià)比最高的優(yōu)化階段就是t2的一些邏輯整理卵贱,盡量將不需要的耗時(shí)操作延遲到首屏展示之后執(zhí)行。
同時(shí)一般來(lái)說(shuō)侣集,優(yōu)化應(yīng)該在項(xiàng)目完成穩(wěn)定之后進(jìn)行键俱,避免過(guò)早優(yōu)化.

參考:

  1. App Startup Time: Past, Present, and Future
  2. [iOS]一次立竿見(jiàn)影的啟動(dòng)時(shí)間優(yōu)化
  3. iOS App 啟動(dòng)性能優(yōu)化
  4. APP啟動(dòng)優(yōu)化的一次實(shí)踐
  5. 阿里數(shù)據(jù)iOS端啟動(dòng)速度優(yōu)化的一些經(jīng)驗(yàn)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市世分,隨后出現(xiàn)的幾起案子编振,更是在濱河造成了極大的恐慌,老刑警劉巖臭埋,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件踪央,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡瓢阴,警方通過(guò)查閱死者的電腦和手機(jī)畅蹂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)荣恐,“玉大人魁莉,你說(shuō)我怎么就攤上這事。” “怎么了旗唁?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)痹束。 經(jīng)常有香客問(wèn)我检疫,道長(zhǎng),這世上最難降的妖魔是什么祷嘶? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任屎媳,我火速辦了婚禮,結(jié)果婚禮上论巍,老公的妹妹穿的比我還像新娘烛谊。我一直安慰自己,他們只是感情好嘉汰,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布丹禀。 她就那樣靜靜地躺著,像睡著了一般鞋怀。 火紅的嫁衣襯著肌膚如雪双泪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,111評(píng)論 1 285
  • 那天密似,我揣著相機(jī)與錄音焙矛,去河邊找鬼。 笑死残腌,一個(gè)胖子當(dāng)著我的面吹牛村斟,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播抛猫,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼蟆盹,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了邑滨?” 一聲冷哼從身側(cè)響起日缨,我...
    開(kāi)封第一講書(shū)人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎掖看,沒(méi)想到半個(gè)月后匣距,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡哎壳,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年毅待,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片归榕。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡尸红,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情外里,我是刑警寧澤怎爵,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站盅蝗,受9級(jí)特大地震影響鳖链,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜墩莫,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一芙委、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧狂秦,春花似錦灌侣、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至愕秫,卻和暖如春慨菱,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背戴甩。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工符喝, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人甜孤。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓协饲,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親缴川。 傳聞我的和親對(duì)象是個(gè)殘疾皇子茉稠,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 之前公司的 UI 設(shè)計(jì)師和我們提過(guò)好幾次啟動(dòng)時(shí)間的事情,當(dāng)時(shí)在開(kāi)發(fā)業(yè)務(wù)把夸,所以沒(méi)有時(shí)間去做這件事而线。最近發(fā)完版本,終于...
    凸阿濱閱讀 804評(píng)論 0 1
  • 針對(duì)APP性能優(yōu)化很重要一點(diǎn)在于APP啟動(dòng)時(shí)候啟動(dòng)速度的優(yōu)化恋日,避免在啟動(dòng)時(shí)處理過(guò)多業(yè)務(wù)邏輯導(dǎo)致啟動(dòng)速度慢膀篮,使用戶體...
    一路向北客閱讀 811評(píng)論 0 2
  • 先說(shuō)一下,這不是原創(chuàng)岂膳,因?yàn)橛?jì)劃今年學(xué)會(huì)游泳誓竿,所以在百度和知乎上查找了些資料,整理一下谈截,加深記憶筷屡,深入學(xué)習(xí)涧偷。 ...
    霞侃閱讀 309評(píng)論 0 0
  • 下載地址:git-scm.com/downloads 1.下載完選擇默認(rèn)選項(xiàng)安裝即可。 安裝完后在開(kāi)始菜單里找 G...
    我就是非主流閱讀 197評(píng)論 0 0
  • 先說(shuō)說(shuō)心里話:大概有幾個(gè)月的時(shí)間沒(méi)有更新了,倒不是因?yàn)閯e的扼倘,大概就是不服氣吧跟啤。每天絞盡腦汁地寫(xiě)出來(lái)的內(nèi)容,閱讀量還...
    7ded2af17431閱讀 960評(píng)論 0 1