iOS App啓動過程

1、啓動過程

iOS開發中,main函數是咱們熟知的程序啓動入口,但實際上並不是真正意義上的入口,由於在咱們運行程序,再到main方法被調用之間,程序已經作了許許多多的事情,好比咱們熟知的runtime的初始化就發生在main函數調用前,還有程序動態庫的加載連接也發生在這階段。html

整個過程爲:ios

  1. 系統先讀取App的可執行文件(Mach-O文件),從裏面得到dyld的路徑
  2. 加載dyld(the dynamic link editor,Apple 的動態連接器,系統 kernel 作好啓動程序的初始準備後,交給 dyld 負責),dyld去初始化運行環境,開啓緩存策略,
  3. dyld加載程序相關動態庫,並對這些庫進行連接,調用每一個依賴庫的初始化方法
  4. runtime被初始化
  5. ImageLoader:dyld把Image(包含咱們的類、方法等)load進來
  6. runtime對加載進來的Image全部類進行類結構初始化,調用全部的load方法,Category方法也在此時被調用
  7. dyld返回main函數地址,main函數被調用
  8. main函數調用
  9. 執行AppDelegate的代理方法,主要是didFinishLaunchingWithOptions
  10. 初始化Window,初始化基礎的ViewController結構
  11. 獲取數據(Local DB/Network),展現給用戶

2、main函數啓動以前

Mach-O可執行文件

Mach-O文件格式是 OS X 與 iOS 系統上的可執行文件格式,像咱們編譯過程產生的.O文件,以及程序的可執行文件,動態庫等都是Mach-O文件。有如下幾種Mach-Ogit

  • Executable 可執行文件
  • Dylib 動態庫
  • Bundle 庫:沒法被鏈接的動態庫,只能經過dlopen()加載
  • Image :指的是Executable,Dylib或者Bundle的一種,文中會屢次使用Image這個名詞。
  • Framework 庫:動態庫和對應的頭文件和資源文件的集合

Mach-O的結構以下:github

  • Header 頭部,包含能夠執行的CPU架構,好比x86,arm64
  • Load commands 加載命令,包含文件的組織架構和在虛擬內存中的佈局方式
  • Data,數據,包含load commands中須要的各個段(segment)的數據,每個Segment都得大小是Page的整數倍。

更多Mach-O可查看《Mac OS X ABI Mach-O File Format Reference》緩存

有兩種方式能夠查看一個APP動態調用的系統可執行文件安全

一、經過machoview,選擇APP的可執行文件,能夠看到bash

二、經過otool -L命令行查看

dyld

全程the dynamic loade,Apple 的動態連接器,系統 kernel 作好啓動程序的初始準備後,交給 dyld 負責。微信

2017年,蘋果引入了Dyld 3.0,可是隻有系統APP採用這個,第三方APP都是採用Dyld 2.0。架構

Dyld 2.0的加載過程是:app

  1. 解析 mach-o 文件,找到其依賴的庫,而且遞歸的找到全部依賴的庫,造成一張動態庫的依賴圖。iOS 上的大部分 app 都依賴 300 到 600 個動態連接庫,因此這個步驟包含了較大的工做量。
  2. 匹配 mach-o 文件到自身的地址空間;
  3. 進行符號查找:好比 app 中調用了 printf 方法,就須要去系統庫中查找到 printf 的地址,而後將地址拷貝到 app 中的函數指針中;
  4. 綁定和變基:因爲 app 須要讓地址空間配置隨機加載,因此全部的指針都須要加上一個基地址;
  5. 運行初始化程序(Runtime、+load、+initialize),以後運行 main() 函數。

3、優化啓動時間

main函數以後

Appdelegate

能延遲初始化的儘可能延遲初始化,不能延遲初始化的儘可能放到後臺初始化。

  • 三方SDK初始化,好比Crash統計; 像分享之類的,能夠等到第一次調用再出初始化。
  • 初始化某些基礎服務,好比WatchDog,遠程參數。
  • 啓動相關日誌,日誌每每涉及到DB操做,必定要放到後臺去作
  • 業務方初始化,這個交由每一個業務本身去控制初始化時間。

啓動業務的優化

建一個類來管理初始化,全部須要初始化的代碼都在這裏進行,分類初始化:

ViewController

延遲初始化那些沒必要要的UIViewController。

用Time Profiler找到元兇

Main以前

dylibs

啓動的第一步是加載動態庫,加載系統的動態庫使很快的,由於能夠緩存,而加載內嵌的動態庫速度較慢。因此,提升這一步的效率的關鍵是:減小動態庫的數量。

  • 合併動態庫,好比公司內部由私有Pod創建了以下動態庫:XXTableView, XXHUD, XXLabel,強烈建議合併成一個XXUIKit來提升加載速度。

Rebase & Bind & Objective C Runtime

Rebase和Bind都是爲了解決指針引用的問題。對於Objective C開發來講,主要的時間消耗在Class/Method的符號加載上,因此常見的優化方案是:

  • 減小__DATA段中的指針數量。
  • 合併Category和功能相似的類。好比:UIView+Frame,UIView+AutoLayout…合併爲一個
  • 刪除無用的方法和類。
  • 多用Swift Structs,由於Swfit Structs是靜態分發的。感興趣的同窗能夠看看我以前這篇文章:《Swift進階以內存模型和方法調度》

Initializers

一般,咱們會在+load方法中進行method-swizzling,這也是Nshipster推薦的方式。

  • 用initialize替代load。很多同窗喜歡用method-swizzling來實現AOP去作日誌統計等內容,強烈建議改成在initialize進行初始化。
  • 減小__atribute__((constructor))的使用,而是在第一次訪問的時候才用dispatch_once等方式初始化。
  • 不要建立線程
  • 使用Swfit重寫代碼。

4、Category

category其實是個結構體,與Class結構體相似

struct _category_t {
	const char *name; // 類的名字,用於尋找類
	struct _class_t *cls; // 編譯期爲空,在runtime經過name找到類後得到
	const struct _method_list_t *instance_methods; // 實例方法
	const struct _method_list_t *class_methods; // 類方法
	const struct _protocol_list_t *protocols; // 協議
	const struct _prop_list_t *properties; // 關聯屬性
};
複製代碼
  1. runtime的入口_objc_init方法裏調用_getObjc2CategoryList獲取category_t 的列表,而後再一一將category添加進去
  2. category是在runtime在把類的結構已經初始化以後,加載進去的,由於內存佈局已經肯定,因此不能添加實例變量,只能經過添加關聯屬性的方式來添加「變量」。
  3. runtime添加方法的時候會放在方法列表的前面,也就是說若是以前有相同名字的方法,根據運行時的特性,調用方法時將會調用category的方法,從而達到了「覆蓋」的效果

5、iOS 靜態庫,動態庫與 Framework

靜態庫與動態庫的區別

首先來看什麼是庫,庫(Library)說白了就是一段編譯好的二進制代碼,加上頭文件就能夠供別人使用 何時咱們會用到庫呢?一種狀況是某些代碼須要給別人使用,可是咱們不但願別人看到源碼,就須要以庫的形式進行封裝,只暴露出頭文件。另一種狀況是,對於某些不會進行大的改動的代碼,咱們想減小編譯的時間,就能夠把它打包成庫,由於庫是已經編譯好的二進制了,編譯的時候只須要 Link 一下,不會浪費編譯時間。 上面提到庫在使用的時候須要 Link,Link 的方式有兩種,靜態和動態,因而便產生了靜態庫和動態庫。

靜態庫

靜態庫即靜態連接庫(Windows 下的 .lib,Linux 和 Mac 下的 .a)。之因此叫作靜態,是由於靜態庫在編譯的時候會被直接拷貝一份,複製到目標程序裏,這段代碼在目標程序裏就不會再改變了。 靜態庫的好處很明顯,編譯完成以後,庫文件實際上就沒有做用了。目標程序沒有外部依賴,直接就能夠運行。固然其缺點也很明顯,就是會使用目標程序的體積增大。

動態庫

動態庫即動態連接庫(Windows 下的 .dll,Linux 下的 .so,Mac 下的 .dylib/.tbd)。與靜態庫相反,動態庫在編譯時並不會被拷貝到目標程序中,目標程序中只會存儲指向動態庫的引用。等到程序運行時,動態庫纔會被真正加載進來。 動態庫的優勢是,不須要拷貝到目標程序中,不會影響目標程序的體積,並且同一份庫能夠被多個程序使用(由於這個緣由,動態庫也被稱做共享庫)。同時,編譯時才載入的特性,也可讓咱們隨時對庫進行替換,而不須要從新編譯代碼。動態庫帶來的問題主要是,動態載入會帶來一部分性能損失,使用動態庫也會使得程序依賴於外部環境。若是環境缺乏動態庫或者庫的版本不正確,就會致使程序沒法運行(Linux 下喜聞樂見的 lib not found 錯誤)。

iOS Framework

除了上面提到的 .a 和 .dylib/.tbd 以外,Mac OS/iOS 平臺還可使用 Framework。Framework 其實是一種打包方式,將庫的二進制文件,頭文件和有關的資源文件打包到一塊兒,方便管理和分發。

在 iOS 8 以前,iOS 平臺不支持使用動態 Framework,開發者可使用的 Framework 只有蘋果自家的 UIKit.Framework,Foundation.Framework 等。這種限制多是出於安全的考慮(見這裏的討論)。換一個角度講,由於 iOS 應用都是運行在沙盒當中,不一樣的程序之間不能共享代碼,同時動態下載代碼又是被蘋果明令禁止的,沒辦法發揮出動態庫的優點,實際上動態庫也就沒有存在的必要了。

因爲上面提到的限制,開發者想要在 iOS 平臺共享代碼,惟一的選擇就是打包成靜態庫 .a 文件,同時附上頭文件(例如微信的SDK)。可是這樣的打包方式不夠方便,使用時也比較麻煩,你們仍是但願共享代碼都能能像 Framework 同樣,直接扔到工程裏就能夠用。因而人們想出了各類奇技淫巧去讓 Xcode Build 出 iOS 可使用的 Framework,具體作法參考這裏和這裏,這種方法產生的 Framework 還有 「僞」(Fake) Framework 和 「真」(Real) Framework 的區別。

iOS 8/Xcode 6 推出以後,iOS 平臺添加了動態庫的支持,同時 Xcode 6 也原生自帶了 Framework 支持(動態和靜態均可以),上面提到的的奇技淫巧也就沒有必要了(新的作法參考這裏)。爲何 iOS 8 要添加動態庫的支持?惟一的理由大概就是 Extension 的出現。Extension 和 App 是兩個分開的可執行文件,同時須要共享代碼,這種狀況下動態庫的支持就是必不可少的了。可是這種動態 Framework 和系統的 UIKit.Framework 仍是有很大區別。系統的 Framework 不須要拷貝到目標程序中,咱們本身作出來的 Framework 哪怕是動態的,最後也仍是要拷貝到 App 中(App 和 Extension 的 Bundle 是共享的),所以蘋果又把這種 Framework 稱爲 Embedded Framework

相關文章
相關標籤/搜索