做iOS開發(fā)日常不怎么涉及到這些內(nèi)容,但總會遇見相關(guān)的bug或者一些優(yōu)化需求。下面整理學(xué)習(xí)一些知識。
程序啟動過程發(fā)生了什么?
啟動后發(fā)生了什么?
程序啟動后的生命周期是怎么樣的?
如何優(yōu)化啟動時間?
App啟動過程
iOS開發(fā)中,main函數(shù)是我們熟知的程序啟動入口,但實際上并非真正意義上的入口,因為在我們運行程序,再到main方法被調(diào)用之間,程序已經(jīng)做了許許多多的事情,比如我們熟知的runtime的初始化就發(fā)生在main函數(shù)調(diào)用前,還有程序動態(tài)庫的加載鏈接也發(fā)生在這階段。
1、系統(tǒng)先讀取App的可執(zhí)行文件(Mach-O文件),獲取到dyld的路徑,并加載dyld(動態(tài)庫鏈接程序)。
2、dyld去初始化運行環(huán)境、開啟緩存策略、加載依賴庫、我們的可執(zhí)行文件、鏈接依賴庫,并調(diào)用每個依賴庫的初始化方法。
3、在上一步runtime被初始化,當所有的依賴庫初始化后,程序可執(zhí)行文件進行初始化,這個時候runtime會對項目中的所有類進行類結(jié)構(gòu)初始化,然后調(diào)用所有類的+load方法。
1、runtime初始化方法 _objc_init 中最后注冊了兩個通知:
map_images: 主要是在鏡像加載進內(nèi)容后對其二進制內(nèi)容進行解析,初始化里面的類結(jié)構(gòu)等
load_images: 主要是調(diào)用call_load_methods 按照繼承層次依次調(diào)用Class的 +load方法 然后是Category的+ load方法。(call_load_methods 調(diào)用load 是通過方法地址直接調(diào)用的load方法,并不是通過消息機制,這就是為什么分類中的load方法并不會覆蓋主類以及其他同主類的分類里的load 方法實現(xiàn)了。)
2、runtime 調(diào)用項目中所有的load方法時,所有的類的結(jié)構(gòu)已經(jīng)初始化了,此時在load方法中可以使用任何類創(chuàng)建實例并給他們發(fā)送消息。
4、最后dyld返回main函數(shù)地址,main函數(shù)被調(diào)用。
App啟動后
App啟動后執(zhí)行流程
main() -> UIApplicationMain() -> UIApplication 構(gòu)建、Appdelegate 構(gòu)建、info.plist文件加載
UIApplication -> runloop創(chuàng)建
Appdelegate(代理) -> didFinishLaunchingWithOptions 函數(shù)執(zhí)行( window構(gòu)建、RootVC構(gòu)建)
app運行的五種狀態(tài)
ios app有5種狀態(tài),分別是:
1、Not running未運行:app沒啟動或被迫終止。
2、Inactive未激活:當前應(yīng)用正在前臺運行,但是并不接收事件(當前或許正在執(zhí)行其它代碼)。一般每當應(yīng)用要從一個狀態(tài)切換到另一個不同的狀態(tài)時,中途過渡會短暫停留在此狀態(tài)。唯一在此狀態(tài)停留時間比較長的情況是:當用戶鎖屏?xí)r,或者系統(tǒng)提示用戶去響應(yīng)某些(諸如電話來電、有未讀短信等)事件的時候。
3、Active激活:當前應(yīng)用正在前臺運行,并且接收事件。這是應(yīng)用正在前臺運行時所處的正常狀態(tài)。
4、Backgroud后臺:程序在后臺而且能執(zhí)行代碼,大多數(shù)程序進入這個狀態(tài)后會在在這個狀態(tài)上停留一會。時間到之后會進入掛起狀態(tài)(Suspended)。經(jīng)過特殊的請求后可以長期處于Backgroud狀態(tài)。
5、Suspended掛起:程序在后臺不能執(zhí)行代碼。系統(tǒng)會自動把程序變成這個狀態(tài)而且不會發(fā)出通知。當掛起時,程序還是停留在內(nèi)存中的,當系統(tǒng)內(nèi)存低時,系統(tǒng)就把掛起的程序清除掉,為前臺程序提供更多的內(nèi)存。
App的生命周期
application:didFinishLaunchingWithOptions: 這是程序啟動時調(diào)用的函數(shù)。
applicationDidBecomeActive: 應(yīng)用在準備進入前臺運行時執(zhí)行的函數(shù)。
applicationWillResignActive: 應(yīng)用當前正要從前臺運行狀態(tài)離開時執(zhí)行的函數(shù)。
applicationDidEnterBackground: 此時應(yīng)用處在background狀態(tài),并且沒有執(zhí)行任何代碼,未來將被掛起進入suspended狀態(tài)。
applicationWillEnterForeground: 當前應(yīng)用正從后臺移入前臺運行狀態(tài),但是當前還沒有到Active狀態(tài)時執(zhí)行的函數(shù)。
applicationWillTerminate: 當前應(yīng)用即將被終止,在終止前調(diào)用的函數(shù)。如果應(yīng)用當前處在suspended,此方法不會被調(diào)用。
啟動時間優(yōu)化
啟動時間
啟動時間是用戶點擊App圖標,到第一個界面展示的時間
啟動時間在小于400ms是最佳的,因為從點擊圖標到顯示Launch Screen,到Launch Screen消失這段時間是400ms。啟動時間不可以大于20s,否則會被系統(tǒng)殺掉。
在Xcode中,可以通過設(shè)置環(huán)境變量來查看App的啟動時間,DYLD_PRINT_STATISTICS和DYLD_PRINT_STATISTICS_DETAILS。
Edit Scheme - Arguments - Environment Variables
冷啟動 和 熱啟動
如果你剛剛啟動過App,這時候App的啟動所需要的數(shù)據(jù)仍然在緩存中,再次啟動的時候稱為熱啟動。如果設(shè)備剛剛重啟,然后啟動App,這時候稱為冷啟動。
優(yōu)化啟動時間
用Time Profiler 檢查一遍自己的App。
main函數(shù)之前的優(yōu)化蘋果本身已經(jīng)處理了很好,一般而言,不再需要我們?nèi)フ{(diào)整,調(diào)整性價比也會比我們調(diào)整自己的代碼低很多,所以優(yōu)化啟動時間大部分是優(yōu)化我們自己寫的代碼。
優(yōu)化main函數(shù)之前的操作
動態(tài)庫加載優(yōu)化
加載系統(tǒng)的動態(tài)庫使很快的,因為可以緩存,而加載內(nèi)嵌的動態(tài)庫速度較慢。
所以,提高這一步的效率的關(guān)鍵是:減少我們自己的動態(tài)庫的數(shù)量。
之前公司進行項目模塊化的時候拆出來很多庫,pod一加載 30幾個pod,編譯都慢成狗,這種情況建議合并動態(tài)庫,比如公司內(nèi)部由私有Pod建立了如下動態(tài)庫:XXTableView, XXHUD, XXLabel,強烈建議合并成一個XXUIKit來提高加載速度。
runtime初始化優(yōu)化
合并Category和功能類似的類
刪除廢棄代碼
其他
+load 中少做swizzle 等操作
不要在 Initializers 創(chuàng)建線程等操作
多用swift 靜態(tài)分發(fā)
優(yōu)化我們自己的代碼
從main函數(shù)開始執(zhí)行,到你的第一個界面顯示,這期間一般會做的事情:
1、執(zhí)行AppDelegate的代理方法,主要是didFinishLaunchingWithOptions
2、初始化Window,初始化基礎(chǔ)的ViewController結(jié)構(gòu)(一般是UINavigationController+UITabViewController)
3、獲取數(shù)據(jù)(Local DB/Network),展示給用戶。
處理方向:
AppDelegate 中的方法
didFinishLaunchingWithOptions 方法
applicationDidBecomeActive 方法
能延遲初始化的盡量延遲初始化,不能延遲初始化的盡量放到后臺初始化。
第一個頁面展示
延遲初始化那些不必要的 UIViewController
以上兩個地方不使用大量占用主線程資源的操作,數(shù)據(jù)獲取解析等放在異步處理。