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

前言

當(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)的情況。

對(duì)于冷啟動(dòng)來說,啟動(dòng)時(shí)間是指從用戶點(diǎn)擊 APP 那一刻開始到用戶看到第一個(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è)頁面加載完成稱為 t2。

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

t1部分主要參考自APP啟動(dòng)優(yōu)化的一次實(shí)踐
其中 t1蘋果提供了內(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文件,
打開并讀取驗(yàn)證有效性,找到代碼簽名注冊(cè)到內(nèi)核,
最后對(duì)dylib的每個(gè)segment調(diào)用mmap()。
rebase/bind
dylib加載完成之后,它們處于相互獨(dú)立的狀態(tài),需要綁定起來。

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

對(duì)所有聲明過的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使用了來自NewPan大大 的打點(diǎn)計(jì)時(shí)器BLStopwatch

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

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

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

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

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

UITabBarC管理一堆 UINavigationC

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

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

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

    NSLog(@"didFinishLaunchingWithOptions 跑完了");

    return YES;
}

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

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

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

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

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

一般來說,我們放到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 主體的之前是必須要加載完的,我把他放到廣告頁面的viewDidAppear啟動(dòng)。
  • 第三類,由于啟動(dòng)時(shí)間不是必須的,所以我們可以放在第一個(gè)界面的 viewDidAppear 方法里,這里完全不會(huì)影響到啟動(dòng)時(shí)間。
優(yōu)化后

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

優(yōu)化思路

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

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

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

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

采用性能更好的API。

首頁控制器用純代碼方式來構(gòu)建。

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

總結(jié)

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

參考:

  1. App Startup Time: Past, Present, and Future
  2. [iOS]一次立竿見影的啟動(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)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,333評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,491評(píng)論 3 416
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,263評(píng)論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,946評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,708評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,186評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,409評(píng)論 0 288
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,939評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,774評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,976評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,209評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,641評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,872評(píng)論 1 286
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,650評(píng)論 3 391
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,958評(píng)論 2 373

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

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