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

這是一篇 WWDC 2016 Session 406 的學(xué)習(xí)筆記岗憋,從原理到實(shí)踐講述了如何優(yōu)化 App 的啟動(dòng)時(shí)間如失。

App 運(yùn)行理論

main()
執(zhí)行前發(fā)生的事
Mach-O 格式
虛擬內(nèi)存基礎(chǔ)
Mach-O 二進(jìn)制的加載

理論速成

  • Mach-O 術(shù)語
    Mach-O 是針對(duì)不同運(yùn)行時(shí)可執(zhí)行文件的文件類型倘是。

  • 文件類型:
    Executable: 應(yīng)用的主要二進(jìn)制
    Dylib: 動(dòng)態(tài)鏈接庫(又稱 DSO 或 DLL)
    Bundle: 不能被鏈接的 Dylib级遭,只能在運(yùn)行時(shí)使用 dlopen()
    加載,可當(dāng)做 macOS 的插件嘀韧。
    Image: executable秤标,dylib 或 bundleFramework: 包含 Dylib 以及資源文件和頭文件的文件夾

  • Mach-O 鏡像文件
    Mach-O 被劃分成一些 segement绝淡,每個(gè) segement 又被劃分成一些 section。segment 的名字都是大寫的苍姜,且空間大小為頁的整數(shù)牢酵。頁的大小跟硬件有關(guān),在 arm64 架構(gòu)一頁是 16KB衙猪,其余為 4KB馍乙。
    section 雖然沒有整數(shù)倍頁大小的限制,但是 section 之間不會(huì)有重疊垫释。

  • 幾乎所有 Mach-O 都包含這三個(gè)段(segment): __TEXT,__DATA
    和 __LINKEDIT:

  • __TEXT
    包含 Mach header丝格,被執(zhí)行的代碼和只讀常量(如C 字符串)。只讀可執(zhí)行(r-x)饶号。

  • __DATA
    包含全局變量铁追,靜態(tài)變量等∶4可讀寫(rw-)琅束。

  • __LINKEDIT
    包含了加載程序的『元數(shù)據(jù)』,比如函數(shù)的名稱和地址算谈。只讀(r–)涩禀。

  • Mach-O Universal 文件
    FAT 二進(jìn)制文件,將多種架構(gòu)的 Mach-O 文件合并而成然眼。它通過 Fat Header 來記錄不同架構(gòu)在文件中的偏移量艾船,F(xiàn)at Header 占一頁的空間。
    按分頁來存儲(chǔ)這些 segement 和 header 會(huì)浪費(fèi)空間高每,但這有利于虛擬內(nèi)存的實(shí)現(xiàn)屿岂。

  • 虛擬內(nèi)存
    虛擬內(nèi)存就是一層間接尋址(indirection)。軟件工程中有句格言就是任何問題都能通過添加一個(gè)間接層來解決鲸匿。虛擬內(nèi)存解決的是管理所有進(jìn)程使用物理 RAM 的問題爷怀。通過添加間接層來讓每個(gè)進(jìn)程使用邏輯地址空間,它可以映射到 RAM 上的某個(gè)物理頁上带欢。這種映射不是一對(duì)一的运授,邏輯地址可能映射不到 RAM 上,也可能有多個(gè)邏輯地址映射到同一個(gè)物理 RAM 上乔煞。針對(duì)第一種情況吁朦,當(dāng)進(jìn)程要存儲(chǔ)邏輯地址內(nèi)容時(shí)會(huì)觸發(fā) page fault;第二種情況就是多進(jìn)程共享內(nèi)存渡贾。
    對(duì)于文件可以不用一次性讀入整個(gè)文件逗宜,可以使用分頁映射(mmap()
    )的方式讀取。也就是把文件某個(gè)片段映射到進(jìn)程邏輯內(nèi)存的某個(gè)頁上空骚。當(dāng)某個(gè)想要讀取的頁沒有在內(nèi)存中锦溪,就會(huì)觸發(fā) page fault,內(nèi)核只會(huì)讀入那一頁府怯,實(shí)現(xiàn)文件的懶加載刻诊。也就是說 Mach-O 文件中的 __TEXT段可以映射到多個(gè)進(jìn)程,并可以懶加載牺丙,且進(jìn)程之間共享內(nèi)存则涯。__DATA段是可讀寫的。這里使用到了 Copy-On-Write 技術(shù)冲簿,簡(jiǎn)稱 COW粟判。也就是多個(gè)進(jìn)程共享一頁內(nèi)存空間時(shí),一旦有進(jìn)程要做寫操作峦剔,它會(huì)先將這頁內(nèi)存內(nèi)容復(fù)制一份出來档礁,然后重新映射邏輯地址到新的 RAM 頁上。也就是這個(gè)進(jìn)程自己擁有了那頁內(nèi)存的拷貝吝沫。這就涉及到了 clean/dirty page 的概念呻澜。dirty page 含有進(jìn)程自己的信息递礼,而 clean page 可以被內(nèi)核重新生成(重新讀磁盤)。所以 dirty page 的代價(jià)大于 clean page羹幸。

  • Mach-O 鏡像 加載
    所以在多個(gè)進(jìn)程加載 Mach-O 鏡像時(shí) __TEXT和 __LINKEDIT因?yàn)橹蛔x脊髓,都是可以共享內(nèi)存的。而 __DATA 因?yàn)榭勺x寫栅受,就會(huì)產(chǎn)生 dirty page将硝。當(dāng) dyld 執(zhí)行結(jié)束后,__LINKEDIT 就沒用了屏镊,對(duì)應(yīng)的內(nèi)存頁會(huì)被回收依疼。

  • 安全
    ASLR(Address Space Layout Randomization):地址空間布局隨機(jī)化,鏡像會(huì)在隨機(jī)的地址上加載而芥。這其實(shí)是一二十年前的舊技術(shù)了律罢。
    代碼簽名:可能我們認(rèn)為 Xcode 會(huì)把整個(gè)文件都做加密 hash 并用做數(shù)字簽名。其實(shí)為了在運(yùn)行時(shí)驗(yàn)證 Mach-O 文件的簽名蔚出,并不是每次重復(fù)讀入整個(gè)文件弟翘,而是把每頁內(nèi)容都生成一個(gè)單獨(dú)的加密散列值,并存儲(chǔ)在 __LINKEDIT中骄酗。這使得文件每頁的內(nèi)容都能及時(shí)被校驗(yàn)確并保不被篡改稀余。

  • 從 exec() 到 main()
    exec() 是一個(gè)系統(tǒng)調(diào)用。系統(tǒng)內(nèi)核把應(yīng)用映射到新的地址空間趋翻,且每次起始位置都是隨機(jī)的(因?yàn)槭褂?ASLR)睛琳。并將起始到 0x000000
    這段范圍的進(jìn)程權(quán)限都標(biāo)記為不可讀寫不可執(zhí)行。如果是 32 位進(jìn)程踏烙,這個(gè)范圍至少是 4KB师骗;對(duì)于 64 位進(jìn)程則至少是 4GB。NULL 指針引用和指針截?cái)嗾`差都是會(huì)被它捕獲讨惩。

  • dyld

  • 加載 dylib 文件
    Unix 的前二十年很安逸辟癌,因?yàn)槟菚r(shí)還沒有發(fā)明動(dòng)態(tài)鏈接庫。有了動(dòng)態(tài)鏈接庫后荐捻,一個(gè)用于加載鏈接庫的幫助程序被創(chuàng)建黍少。在蘋果的平臺(tái)里是 dyld,其他 Unix 系統(tǒng)也有 ld.so
    处面。 當(dāng)內(nèi)核完成映射進(jìn)程的工作后會(huì)將名字為 dyld的Mach-O 文件映射到進(jìn)程中的隨機(jī)地址厂置,它將 PC 寄存器設(shè)為 dyld 的地址并運(yùn)行。dyld 在應(yīng)用進(jìn)程中運(yùn)行的工作是加載應(yīng)用依賴的所有動(dòng)態(tài)鏈接庫魂角,準(zhǔn)備好運(yùn)行所需的一切昵济,它擁有的權(quán)限跟應(yīng)用一樣。

  • 下面的步驟構(gòu)成了 dyld 的時(shí)間線:

Load dylibs -> Rebase -> Bind -> ObjC -> Initializers

  • 加載 Dylib
    從主執(zhí)行文件的 header 獲取到需要加載的所依賴動(dòng)態(tài)庫列表,而 header 早就被內(nèi)核映射過访忿。然后它需要找到每個(gè) dylib瞧栗,然后打開文件讀取文件起始位置,確保它是 Mach-O 文件醉顽。接著會(huì)找到代碼簽名并將其注冊(cè)到內(nèi)核沼溜。然后在 dylib 文件的每個(gè) segment 上調(diào)用 mmap()平挑。應(yīng)用所依賴的 dylib 文件可能會(huì)再依賴其他 dylib游添,所以 dyld 所需要加載的是動(dòng)態(tài)庫列表一個(gè)遞歸依賴的集合。一般應(yīng)用會(huì)加載 100 到 400 個(gè) dylib 文件通熄,但大部分都是系統(tǒng) dylib唆涝,它們會(huì)被預(yù)先計(jì)算和緩存起來,加載速度很快唇辨。

  • Fix-ups
    在加載所有的動(dòng)態(tài)鏈接庫之后廊酣,它們只是處在相互獨(dú)立的狀態(tài),需要將它們綁定起來赏枚,這就是 Fix-ups亡驰。代碼簽名使得我們不能修改指令,那樣就不能讓一個(gè) dylib 的調(diào)用另一個(gè) dylib饿幅。這時(shí)需要加很多間接層》踩瑁現(xiàn)代 code-gen 被叫做動(dòng)態(tài) PIC(Position Independent Code),意味著代碼可以被加載到間接的地址上栗恩。當(dāng)調(diào)用發(fā)生時(shí)透乾,code-gen 實(shí)際上會(huì)在 __DATA 段中創(chuàng)建一個(gè)指向被調(diào)用者的指針,然后加載指針并跳轉(zhuǎn)過去磕秤。所以 dyld 做的事情就是修正(fix-up)指針和數(shù)據(jù)乳乌。

  • Fix-up 有兩種類型,rebasing 和 binding市咆。
    Rebasing 和 Binding
    Rebasing:在鏡像內(nèi)部調(diào)整指針的指向Binding:將指針指向鏡像外部的內(nèi)容
    可以通過命令行查看 rebase 和 bind 等信息:

xcrun dyldinfo -rebase -bind -lazy_bind myapp.app/myapp

通過這個(gè)命令可以查看所有的 Fix-up汉操。rebase,bind蒙兰,weak_bind磷瘤,lazy_bind 都存儲(chǔ)在 __LINKEDIT 段中,并可通過 LC_DYLD_INFO_ONLY 查看各種信息的偏移量和大小癞己。建議用 MachOView 查看更加方便直觀膀斋。從 dyld
源碼層面簡(jiǎn)要介紹下 Rebasing 和 Binding 的流程。

ImageLoader是一個(gè)用于加載可執(zhí)行文件的基類痹雅,它負(fù)責(zé)鏈接鏡像仰担,但不關(guān)心具體文件格式,因?yàn)檫@些都交給子類去實(shí)現(xiàn)。每個(gè)可執(zhí)行文件都會(huì)對(duì)應(yīng)一個(gè) ImageLoader 實(shí)例摔蓝。ImageLoaderMachO 是用于加載 Mach-O 格式文件的 ImageLoader 子類赂苗,而 ImageLoaderMachOClassic 和 ImageLoaderMachOCompressed 都繼承于 ImageLoaderMachO,分別用于加載那些 __LINKEDIT 段為傳統(tǒng)格式和壓縮格式的 Mach-O 文件贮尉。
因?yàn)?dylib 之間有依賴關(guān)系拌滋,所以 ImageLoader 中的好多操作都是沿著依賴鏈遞歸操作的,Rebasing 和 Binding 也不例外猜谚,分別對(duì)應(yīng)著 recursiveRebase() 和 recursiveBind() 這兩個(gè)方法败砂。因?yàn)槭沁f歸,所以會(huì)自底向上地分別調(diào)用 doRebase() 和 doBind() 方法魏铅,這樣被依賴的 dylib 總是先于依賴它的 dylib 執(zhí)行 Rebasing 和 Binding昌犹。傳入 doRebase() 和 doBind() 的參數(shù)包含一個(gè) LinkContext 上下文,存儲(chǔ)了可執(zhí)行文件的一堆狀態(tài)和相關(guān)的函數(shù)览芳。
在 Rebasing 和 Binding 前會(huì)判斷是否已經(jīng) Prebinding斜姥。如果已經(jīng)進(jìn)行過預(yù)綁定(Prebinding),那就不需要 Rebasing 和 Binding 這些 Fix-up 流程了沧竟,因?yàn)橐呀?jīng)在預(yù)先綁定的地址加載好了铸敏。

ImageLoaderMachO 實(shí)例不使用預(yù)綁定會(huì)有四個(gè)原因:
Mach-O Header 中 MH_PREBOUND 標(biāo)志位為 0

鏡像加載地址有偏移(這個(gè)后面會(huì)講到)
依賴的庫有變化
鏡像使用 flat-namespace,預(yù)綁定的一部分會(huì)被忽略
LinkContext 的環(huán)境變量禁止了預(yù)綁定

ImageLoaderMachO 中 doRebase() 做的事情大致如下:
如果使用預(yù)綁定悟泵,fgImagesWithUsedPrebinding
計(jì)數(shù)加一杈笔,并 return;否則進(jìn)入第二步
如果 MH_PREBOUND 標(biāo)志位為 1(也就是可以預(yù)綁定但沒使用),且鏡像在共享內(nèi)存中魁袜,重置上下文中所有的 lazy pointer桩撮。(如果鏡像在共享內(nèi)存中,稍后會(huì)在 Binding 過程中綁定峰弹,所以無需重置)
如果鏡像加載地址偏移量為0店量,則無需 Rebasing,直接 return鞠呈;否則進(jìn)入第四步調(diào)用 rebase() 方法融师,這才是真正做 Rebasing 工作的方法。如果開啟 TEXT_RELOC_SUPPORT 宏蚁吝,會(huì)允許 rebase() 方法對(duì) __TEXT 段做寫操作來對(duì)其進(jìn)行 Fix-up旱爆。所以其實(shí) __TEXT 只讀屬性并不是絕對(duì)的。

ImageLoaderMachOClassic 和 ImageLoaderMachOCompressed 分別實(shí)現(xiàn)了自己的 doRebase() 方法窘茁。實(shí)現(xiàn)邏輯大同小異怀伦,同樣會(huì)判斷是否使用預(yù)綁定,并在真正的 Binding 工作時(shí)判斷 TEXT_RELOC_SUPPORT 宏來決定是否對(duì) __TEXT 段做寫操作山林。最后都會(huì)調(diào)用 setupLazyPointerHandler 在鏡像中設(shè)置 dyld 的 entry point房待,放在最后調(diào)用是為了讓主可執(zhí)行文件設(shè)置好 __dyld 或 __program_vars。

  • Rebasing
    在過去,會(huì)把 dylib 加載到指定地址桑孩,所有指針和數(shù)據(jù)對(duì)于代碼來說都是對(duì)的拜鹤,dyld 就無需做任何 fix-up 了。如今用了 ASLR 后悔將 dylib 加載到新的隨機(jī)地址(actual_address)流椒,這個(gè)隨機(jī)的地址跟代碼和數(shù)據(jù)指向的舊地址(preferred_address)會(huì)有偏差敏簿,dyld
    需要修正這個(gè)偏差(slide),做法就是將 dylib 內(nèi)部的指針地址都加上這個(gè)偏移量宣虾,偏移量的計(jì)算方法如下:

Slide = actual_address - preferred_address

然后就是重復(fù)不斷地對(duì) __DATA 段中需要 rebase 的指針加上這個(gè)偏移量惯裕。這就又涉及到 page fault 和 COW。這可能會(huì)產(chǎn)生 I/O 瓶頸安岂,但因?yàn)?rebase 的順序是按地址排列的轻猖,所以從內(nèi)核的角度來看這是個(gè)有次序的任務(wù)帆吻,它會(huì)預(yù)先讀入數(shù)據(jù)域那,減少 I/O 消耗。

  • Binding
    Binding 是處理那些指向 dylib 外部的指針猜煮,它們實(shí)際上被符號(hào)(symbol)名稱綁定次员,也就是個(gè)字符串。之前提到 __LINKEDIT
    段中也存儲(chǔ)了需要 bind 的指針王带,以及指針需要指向的符號(hào)淑蔚。dyld
    需要找到 symbol 對(duì)應(yīng)的實(shí)現(xiàn),這需要很多計(jì)算愕撰,去符號(hào)表里查找刹衫。找到后會(huì)將內(nèi)容存儲(chǔ)到 __DATA 段中的那個(gè)指針中。Binding 看起來計(jì)算量比 Rebasing 更大搞挣,但其實(shí)需要的 I/O 操作很少带迟,因?yàn)橹?Rebasing 已經(jīng)替 Binding 做過了。

  • ObjC Runtime
    Objective-C 中有很多數(shù)據(jù)結(jié)構(gòu)都是靠 Rebasing 和 Binding 來(fix-up)的博肋,比如 Class 中指向超類的指針和指向方法的指針伴找。
    ObjC 是個(gè)動(dòng)態(tài)語言脐恩,可以用類的名字來實(shí)例化一個(gè)類的對(duì)象。這意味著 ObjC Runtime 需要維護(hù)一張映射類名與類的全局表搀继。當(dāng)加載一個(gè) dylib 時(shí),其定義的所有的類都需要被注冊(cè)到這個(gè)全局表中翠语。
    C++ 中有個(gè)問題叫做易碎的基類(fragile base class)叽躯。ObjC 就沒有這個(gè)問題,因?yàn)闀?huì)在加載時(shí)通過 fix-up 動(dòng)態(tài)類中改變實(shí)例變量的偏移量肌括。
    在 ObjC 中可以通過定義類別(Category)的方式改變一個(gè)類的方法点骑。有時(shí)你想要添加方法的類在另一個(gè) dylib 中,而不在你的鏡像中(也就是對(duì)系統(tǒng)或別人的類動(dòng)刀),這時(shí)也需要做些 fix-up畔况。
    ObjC 中的 selector 必須是唯一的鲸鹦。

  • Initializers
    C++ 會(huì)為靜態(tài)創(chuàng)建的對(duì)象生成初始化器。而在 ObjC 中有個(gè)叫 +load
    的方法跷跪,然而它被廢棄了馋嗜,現(xiàn)在建議使用 +initialize。對(duì)比詳見:http://stackoverflow.com/questions/13326435/nsobject-load-and-initialize-what-do-they-do
    現(xiàn)在有了主執(zhí)行文件吵瞻,一堆 dylib葛菇,其依賴關(guān)系構(gòu)成了一張巨大的有向圖,那么執(zhí)行初始化器的順序是什么橡羞?自頂向上眯停!按照依賴關(guān)系,先加載葉子節(jié)點(diǎn)卿泽,然后逐步向上加載中間節(jié)點(diǎn)莺债,直至最后加載根節(jié)點(diǎn)。這種加載順序確保了安全性签夭,加載某個(gè) dylib 前齐邦,其所依賴的其余 dylib 文件肯定已經(jīng)被預(yù)先加載。

  • 最后 dyld 會(huì)調(diào)用 main() 函數(shù)第租。main() 會(huì)調(diào)用 UIApplicationMain()措拇。

改善啟動(dòng)時(shí)間

從點(diǎn)擊 App 圖標(biāo)到加載 App 閃屏之間會(huì)有個(gè)動(dòng)畫,我們希望 App 啟動(dòng)速度比這個(gè)動(dòng)畫更快慎宾。雖然不同設(shè)備上 App 啟動(dòng)速度不一樣丐吓,但啟動(dòng)時(shí)間最好控制在 400ms。需要注意的是啟動(dòng)時(shí)間一旦超過 20s趟据,系統(tǒng)會(huì)認(rèn)為發(fā)生了死循環(huán)并殺掉 App 進(jìn)程券犁。當(dāng)然啟動(dòng)時(shí)間最好以 App 所支持的最低配置設(shè)備為準(zhǔn)。直到 applicationWillFinishLaunching
被調(diào)動(dòng)之宿,App 才啟動(dòng)結(jié)束族操。

測(cè)量啟動(dòng)時(shí)間

Warm launch: App 和數(shù)據(jù)已經(jīng)在內(nèi)存中Cold launch: App 不在內(nèi)核緩沖存儲(chǔ)器中冷啟動(dòng)(Cold launch)耗時(shí)才是我們需要測(cè)量的重要數(shù)據(jù),為了準(zhǔn)確測(cè)量冷啟動(dòng)耗時(shí)比被,測(cè)量前需要重啟設(shè)備色难。在 main()
方法執(zhí)行前測(cè)量是很難的,好在 dyld
提供了內(nèi)建的測(cè)量方法:

在 Xcode 中 Edit scheme -> Run -> Auguments 將環(huán)境變量 DYLD_PRINT_STATISTICS 設(shè)為 1
等缀〖侠颍控制臺(tái)輸出的內(nèi)容如下:
Total pre-main time: 228.41 milliseconds (100.0%) dylib loading time: 82.35 milliseconds (36.0%) rebase/binding time: 6.12 milliseconds (2.6%) ObjC setup time: 7.82 milliseconds (3.4%) initializer time: 132.02 milliseconds (57.8%) slowest intializers : libSystem.B.dylib : 122.07 milliseconds (53.4%) CoreFoundation : 5.59 milliseconds (2.4%)

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

可以針對(duì) App 啟動(dòng)前的每個(gè)步驟進(jìn)行相應(yīng)的優(yōu)化工作。

加載 Dylib之前提到過加載系統(tǒng)的 dylib 很快尺迂,因?yàn)橛袃?yōu)化笤妙。但加載內(nèi)嵌(embedded)的 dylib 文件很占時(shí)間冒掌,所以盡可能把多個(gè)內(nèi)嵌 dylib 合并成一個(gè)來加載,或者使用 static archive蹲盘。使用 dlopen() 來在運(yùn)行時(shí)懶加載是不建議的股毫,這么做可能會(huì)帶來一些問題,并且總的開銷更大召衔。

Rebase/Binding
之前提過 Rebaing 消耗了大量時(shí)間在 I/O 上铃诬,而在之后的 Binding 就不怎么需要 I/O 了,而是將時(shí)間耗費(fèi)在計(jì)算上苍凛。所以這兩個(gè)步驟的耗時(shí)是混在一起的趣席。之前說過可以從查看 __DATA 段中需要修正(fix-up)的指針,所以減少指針數(shù)量才會(huì)減少這部分工作的耗時(shí)醇蝴。對(duì)于 ObjC 來說就是減少 Class,selector 和 category 這些元數(shù)據(jù)的數(shù)量宣肚。從編碼原則和設(shè)計(jì)模式之類的理論都會(huì)鼓勵(lì)大家多寫精致短小的類和方法,并將每部分方法獨(dú)立出一個(gè)類別悠栓,其實(shí)這會(huì)增加啟動(dòng)時(shí)間霉涨。對(duì)于 C++ 來說需要減少虛方法,因?yàn)樘摲椒〞?huì)創(chuàng)建 vtable闸迷,這也會(huì)在 __DATA 段中創(chuàng)建結(jié)構(gòu)嵌纲。雖然 C++ 虛方法對(duì)啟動(dòng)耗時(shí)的增加要比 ObjC 元數(shù)據(jù)要少,但依然不可忽視腥沽。最后推薦使用 Swift 結(jié)構(gòu)體,它需要 fix-up 的內(nèi)容較少鸠蚪。

ObjC Setup
針對(duì)這步所能事情很少今阳,幾乎都靠 Rebasing 和 Binding 步驟中減少所需 fix-up 內(nèi)容。因?yàn)榍懊娴墓ぷ饕矔?huì)使得這步耗時(shí)減少茅信。

Initializer
顯式初始化
使用 +initialize 來替代 +load

不要使用 atribute((constructor)) 將方法顯式標(biāo)記為初始化器盾舌,而是讓初始化方法調(diào)用時(shí)才執(zhí)行。比如使用 dispatch_once(),pthread_once() 或 std::once()蘸鲸。也就是在第一次使用時(shí)才初始化妖谴,推遲了一部分工作耗時(shí)。

隱式初始化
對(duì)于帶有復(fù)雜(non-trivial)構(gòu)造器的 C++ 靜態(tài)變量:
在調(diào)用的地方使用初始化器酌摇。
只用簡(jiǎn)單值類型賦值(POD:Plain Old Data)膝舅,這樣靜態(tài)鏈接器會(huì)預(yù)先計(jì)算 __DATA 中的數(shù)據(jù),無需再進(jìn)行 fix-up 工作窑多。
使用編譯器 warning 標(biāo)志 -Wglobal-constructors 來發(fā)現(xiàn)隱式初始化代碼仍稀。
使用 Swift 重寫代碼,因?yàn)?Swift 已經(jīng)預(yù)先處理好了埂息,強(qiáng)力推薦技潘。

不要在初始化方法中調(diào)用 dlopen()遥巴,對(duì)性能有影響。因?yàn)?dyld 在 App 開始前運(yùn)行享幽,由于此時(shí)是單線程運(yùn)行所以系統(tǒng)會(huì)取消加鎖铲掐,但 dlopen() 開啟了多線程,系統(tǒng)不得不加鎖值桩,這就嚴(yán)重影響了性能迹炼,還可能會(huì)造成死鎖以及產(chǎn)生未知的后果。所以也不要在初始化器中創(chuàng)建線程颠毙。

Reference

http://yulingtianxia.com/blog/2016/10/30/Optimizing-App-Startup-Time
/https://developer.apple.com/videos/play/wwdc2016/406/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末斯入,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子蛀蜜,更是在濱河造成了極大的恐慌刻两,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件滴某,死亡現(xiàn)場(chǎng)離奇詭異磅摹,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)霎奢,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門户誓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人幕侠,你說我怎么就攤上這事帝美。” “怎么了晤硕?”我有些...
    開封第一講書人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵悼潭,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我舞箍,道長(zhǎng)舰褪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任疏橄,我火速辦了婚禮占拍,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘捎迫。我一直安慰自己晃酒,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開白布立砸。 她就那樣靜靜地躺著掖疮,像睡著了一般。 火紅的嫁衣襯著肌膚如雪颗祝。 梳的紋絲不亂的頭發(fā)上浊闪,一...
    開封第一講書人閱讀 51,146評(píng)論 1 297
  • 那天恼布,我揣著相機(jī)與錄音,去河邊找鬼搁宾。 笑死折汞,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的盖腿。 我是一名探鬼主播爽待,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼翩腐!你這毒婦竟也來了鸟款?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤茂卦,失蹤者是張志新(化名)和其女友劉穎何什,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體等龙,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡处渣,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蛛砰。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片罐栈。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖泥畅,靈堂內(nèi)的尸體忽然破棺而出荠诬,到底是詐尸還是另有隱情,我是刑警寧澤涯捻,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布浅妆,位于F島的核電站,受9級(jí)特大地震影響障癌,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜辩尊,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一涛浙、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧摄欲,春花似錦轿亮、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至迟隅,卻和暖如春但骨,著一層夾襖步出監(jiān)牢的瞬間励七,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工奔缠, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留掠抬,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓校哎,卻偏偏與公主長(zhǎng)得像两波,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子闷哆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

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