【ios學(xué)習(xí)】?jī)?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)鏈接庫加載器dyld(dynamic loader)

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

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)庫用了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)庫

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è) viewController 的 viewDidLoad 方法里進(jìn)行更多的初始化操作寻拂。那么你覺得從 didFinishLaunchingWithOptions 到最后顯示展示的 viewController 的 viewDidLoad 這些方法的執(zhí)行順序是怎么樣的呢?

didFinishLaunchingWithOptions 開始執(zhí)行

開始加載 TestTabBarController 的 viewDidLoad

didFinishLaunchingWithOptions 跑完了

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

在TestTabBarController 中操作了 TestViewController 的 view 的話丈牢,那么調(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è)三方庫,找到可以延遲加載的庫蜕依,做延遲加載處理桅锄,比如放到首頁控制器的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)化.

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末懂诗,一起剝皮案震驚了整個(gè)濱河市蜂嗽,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌殃恒,老刑警劉巖植旧,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件辱揭,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡病附,警方通過查閱死者的電腦和手機(jī)问窃,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來完沪,“玉大人域庇,你說我怎么就攤上這事「不” “怎么了较剃?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)技健。 經(jīng)常有香客問我写穴,道長(zhǎng),這世上最難降的妖魔是什么雌贱? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任啊送,我火速辦了婚禮,結(jié)果婚禮上欣孤,老公的妹妹穿的比我還像新娘馋没。我一直安慰自己,他們只是感情好降传,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布篷朵。 她就那樣靜靜地躺著,像睡著了一般婆排。 火紅的嫁衣襯著肌膚如雪声旺。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天段只,我揣著相機(jī)與錄音腮猖,去河邊找鬼。 笑死赞枕,一個(gè)胖子當(dāng)著我的面吹牛澈缺,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播炕婶,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼姐赡,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了柠掂?” 一聲冷哼從身側(cè)響起项滑,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎陪踩,沒想到半個(gè)月后杖们,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡肩狂,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年摘完,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片傻谁。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡孝治,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出审磁,到底是詐尸還是另有隱情谈飒,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布态蒂,位于F島的核電站杭措,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏钾恢。R本人自食惡果不足惜手素,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望瘩蚪。 院中可真熱鬧泉懦,春花似錦、人聲如沸疹瘦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽言沐。三九已至邓嘹,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間险胰,已是汗流浹背吴超。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留鸯乃,地道東北人鲸阻。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像缨睡,于是被迫代替她去往敵國和親鸟悴。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

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