如何實(shí)現(xiàn) iOS App 的冷啟動(dòng)優(yōu)化

歡迎訪問我的博客原文

當(dāng) App 中的業(yè)務(wù)模塊越來越多、越來越復(fù)雜,集成了更多的三方庫,App 啟動(dòng)也會(huì)越來越慢,因此我們希望能在業(yè)務(wù)擴(kuò)張的同時(shí),保持較優(yōu)的啟動(dòng)速度,給用戶帶來良好的使用體驗(yàn)。

熱啟動(dòng)與冷啟動(dòng)

當(dāng)用戶按下 home 鍵,iOS App 不會(huì)立刻被 kill,而是存活一段時(shí)間,這段時(shí)間里用戶再打開 App,App 基本上不需要做什么,就能還原到退到后臺(tái)前的狀態(tài)。我們把 App 進(jìn)程還在系統(tǒng)中,無需開啟新進(jìn)程的啟動(dòng)過程稱為熱啟動(dòng)。

冷啟動(dòng)則是指 App 不在系統(tǒng)進(jìn)程中,比如設(shè)備重啟后,或是手動(dòng)殺死 App 進(jìn)程,又或是 App 長時(shí)間未打開過,用戶再點(diǎn)擊啟動(dòng) App 的過程,這時(shí)需要?jiǎng)?chuàng)建一個(gè)新進(jìn)程分配給 App。我們可以將冷啟動(dòng)看作一次完整的 App 啟動(dòng)過程,本文討論的就是冷啟動(dòng)的優(yōu)化。

冷啟動(dòng)概要

WWDC 2016 中首次出現(xiàn)了 App 啟動(dòng)優(yōu)化的話題,其中提到:

  • App 啟動(dòng)最佳速度是400ms以內(nèi),因?yàn)閺狞c(diǎn)擊 App 圖標(biāo)啟動(dòng),然后 Launch Screen 出現(xiàn)再消失的時(shí)間就是400ms;
  • App 啟動(dòng)最慢不得大于20s,否則進(jìn)程會(huì)被系統(tǒng)殺死;(啟動(dòng)時(shí)間最好以 App 所支持的最低配置設(shè)備為準(zhǔn)。)

冷啟動(dòng)的整個(gè)過程是指從用戶喚起 App 開始到 AppDelegate 中的 didFinishLaunchingWithOptions 方法執(zhí)行完畢為止,并以執(zhí)行 main() 函數(shù)的時(shí)機(jī)為分界點(diǎn),分為 pre-mainmain() 兩個(gè)階段。

也有一種說法是將整個(gè)冷啟動(dòng)階段以主 UI 框架的 viewDidAppear 函數(shù)執(zhí)行完畢才算結(jié)束。這兩種說法都可以,前者的界定范圍是 App 啟動(dòng)和初始化完畢,后者的界定范圍是用戶視角的啟動(dòng)完畢,也就是首屏已經(jīng)被加載出來。

注意:這里很多文章都會(huì)把第二個(gè)階段描述為 main 函數(shù)之后,個(gè)人認(rèn)為這種說法不是很好,容易讓人誤解。要知道 main 函數(shù)在 App 運(yùn)行過程中是不會(huì)退出的,無論是 AppDelegate 中的 didFinishLaunchingWithOptions 方法還是 ViewController 中的viewDidAppear 方法,都還是在 main 函數(shù)內(nèi)部執(zhí)行的。

pre-main 階段

pre-main 階段指的是從用戶喚起 App 到 main() 函數(shù)執(zhí)行之前的過程。

查看階段耗時(shí)

我們可以在 Xcode 中配置環(huán)境變量 DYLD_PRINT_STATISTICS 為 1(Edit Scheme → Run → Arguments → Environment Variables → +)。

設(shè)置環(huán)境變量

這時(shí)在 iOS 10 以上系統(tǒng)中運(yùn)行一個(gè) TestDemo,pre-main 階段的啟動(dòng)時(shí)間會(huì)在控制臺(tái)中打印出來。

Total pre-main time: 354.21 milliseconds (100.0%)
         dylib loading time:  25.52 milliseconds (7.2%)
        rebase/binding time:  12.70 milliseconds (3.5%)
            ObjC setup time: 152.74 milliseconds (43.1%)
           initializer time: 163.24 milliseconds (46.0%)
           slowest intializers :
             libSystem.B.dylib :   7.98 milliseconds (2.2%)
   libBacktraceRecording.dylib :  13.53 milliseconds (3.8%)
    libMainThreadChecker.dylib :  41.11 milliseconds (11.6%)
                      TestDemo :  88.76 milliseconds (25.0%)

如果要更詳細(xì)的信息,就設(shè)置 DYLD_PRINT_STATISTICS_DETAILS 為 1。

  total time: 1.6 seconds (100.0%)
  total images loaded:  388 (381 from dyld shared cache)
  total segments mapped: 23, into 413 pages
  total images loading time: 805.78 milliseconds (48.6%)
  total load time in ObjC: 152.74 milliseconds (9.2%)
  total debugger pause time: 780.26 milliseconds (47.1%)
  total dtrace DOF registration time:   0.00 milliseconds (0.0%)
  total rebase fixups:  54,265
  total rebase fixups time:  20.77 milliseconds (1.2%)
  total binding fixups: 527,211
  total binding fixups time: 513.54 milliseconds (31.0%)
  total weak binding fixups time:   0.31 milliseconds (0.0%)
  total redo shared cached bindings time: 521.93 milliseconds (31.5%)
  total bindings lazily fixed up: 0 of 0
  total time in initializers and ObjC +load: 163.24 milliseconds (9.8%)
                         libSystem.B.dylib :   7.98 milliseconds (0.4%)
               libBacktraceRecording.dylib :  13.53 milliseconds (0.8%)
                libMainThreadChecker.dylib :  41.11 milliseconds (2.4%)
              libViewDebuggerSupport.dylib :   6.68 milliseconds (0.4%)
                                  TestDemo :  88.76 milliseconds (5.3%)
total symbol trie searches:    1306942
total symbol table binary searches:    0
total images defining weak symbols:  41
total images using weak symbols:  105

這里統(tǒng)計(jì)到的啟動(dòng)耗時(shí)出現(xiàn)一定波動(dòng)是正常的,無須過分在意。

理論知識

為了更準(zhǔn)確地了解 App 啟動(dòng)的流程,我們先熟悉一下幾個(gè)概念。

Mach-O

Mach-O(Mach Object File Format)是一種用于記錄可執(zhí)行文件、對象代碼、共享庫、動(dòng)態(tài)加載代碼和內(nèi)存轉(zhuǎn)儲(chǔ)的文件格式。App 編譯生成的二進(jìn)制可執(zhí)行文件就是 Mach-O 格式的,iOS 工程所有的類編譯后會(huì)生成對應(yīng)的目標(biāo)文件 .o 文件,而這個(gè)可執(zhí)行文件就是這些 .o 文件的集合。

在 Xcode 的控制臺(tái)輸入以下命令,可以打印出運(yùn)行時(shí)所有加載進(jìn)應(yīng)用程序的 Mach-O 文件。

image list -o -f

Mach-O 文件主要由三部分組成:

  • Mach header:描述 Mach-O 的 CPU 架構(gòu)、文件類型以及加載命令等;
  • Load commands:描述了文件中數(shù)據(jù)的具體組織結(jié)構(gòu),不同的數(shù)據(jù)類型使用不同的加載命令;
  • Data:Data 中的每個(gè)段(segment)的數(shù)據(jù)都保存在這里,每個(gè)段都有一個(gè)或多個(gè) Section,它們存放了具體的數(shù)據(jù)與代碼,主要包含這三種類型:
    • __TEXT 包含 Mach header,被執(zhí)行的代碼和只讀常量(如C 字符串)。只讀可執(zhí)行(r-x)。
    • __DATA 包含全局變量,靜態(tài)變量等。可讀寫(rw-)。
    • __LINKEDIT 包含了加載程序的元數(shù)據(jù),比如函數(shù)的名稱和地址。只讀(r–-)。

dylib

dylib 也是一種 Mach-O 格式的文件,后綴名為 .dylib 的文件就是動(dòng)態(tài)庫(也叫動(dòng)態(tài)鏈接庫)。動(dòng)態(tài)庫是運(yùn)行時(shí)加載的,可以被多個(gè) App 的進(jìn)程共用。

如果想知道 TestDemo 中依賴的所有動(dòng)態(tài)庫,可以通過下面的指令實(shí)現(xiàn):

otool -L /TestDemo.app/TestDemo

動(dòng)態(tài)鏈接庫分為系統(tǒng) dylib內(nèi)嵌 dylib(embed dylib,即開發(fā)者手動(dòng)引入的動(dòng)態(tài)庫)。系統(tǒng) dylib 有:

  • iOS 中用到的所有系統(tǒng) framework,比如 UIKit、Foundation;
  • 系統(tǒng)級別的 libSystem(如 libdispatch(GCD) 和 libsystem_blocks(Block));
  • 加載 OC runtime 方法的 libobjc;
  • ……

dyld

dyld(Dynamic Link Editor):動(dòng)態(tài)鏈接器,其本質(zhì)也是 Mach-O 文件,一個(gè)專門用來加載 dylib 文件的庫。 dyld 位于 /usr/lib/dyld,可以在 mac 和越獄機(jī)中找到。dyld 會(huì)將 App 依賴的動(dòng)態(tài)庫和 App 文件加載到內(nèi)存后執(zhí)行。

dyld shared cache

dyld shared cache 就是動(dòng)態(tài)庫共享緩存。當(dāng)需要加載的動(dòng)態(tài)庫非常多時(shí),相互依賴的符號也更多了,為了節(jié)省解析處理符號的時(shí)間,OS X 和 iOS 上的動(dòng)態(tài)鏈接器使用了共享緩存。OS X 的共享緩存位于 /private/var/db/dyld/,iOS 的則在 /System/Library/Caches/com.apple.dyld/。

當(dāng)加載一個(gè) Mach-O 文件時(shí),dyld 首先會(huì)檢查是否存在于共享緩存,存在就直接取出使用。每一個(gè)進(jìn)程都會(huì)把這個(gè)共享緩存映射到了自己的地址空間中。這種方法大大優(yōu)化了 OS X 和 iOS 上程序的啟動(dòng)時(shí)間。

images

images 在這里不是指圖片,而是鏡像。每個(gè) App 都是以 images 為單位進(jìn)行加載的。images 類型包括:

  • executable:應(yīng)用的二進(jìn)制可執(zhí)行文件;
  • dylib:動(dòng)態(tài)鏈接庫;
  • bundle:資源文件,屬于不能被鏈接的 dylib,只能在運(yùn)行時(shí)通過 dlopen() 加載。

framework

framework 可以是動(dòng)態(tài)庫,也是靜態(tài)庫,是一個(gè)包含 dylib、bundle 和頭文件的文件夾。

啟動(dòng)過程分析與優(yōu)化

啟動(dòng)一個(gè)應(yīng)用時(shí),系統(tǒng)會(huì)通過 fork() 方法來新創(chuàng)建一個(gè)進(jìn)程,然后執(zhí)行鏡像通過 exec() 來替換為另一個(gè)可執(zhí)行程序,然后執(zhí)行如下操作:

  1. 把可執(zhí)行文件加載到內(nèi)存空間,從可執(zhí)行文件中能夠分析出 dyld 的路徑;
  2. 把 dyld 加載到內(nèi)存;
  3. dyld 從可執(zhí)行文件的依賴開始,遞歸加載所有的依賴動(dòng)態(tài)鏈接庫 dylib 并進(jìn)行相應(yīng)的初始化操作。

結(jié)合上面 pre-main 打印的結(jié)果,我們可以大致了解整個(gè)啟動(dòng)過程如下圖所示:

pre-main 階段啟動(dòng)過程

Load Dylibs

這一步,指的是動(dòng)態(tài)庫加載。在此階段,dyld 會(huì):

  1. 分析 App 依賴的所有 dylib;
  2. 找到 dylib 對應(yīng)的 Mach-O 文件;
  3. 打開、讀取這些 Mach-O 文件,并驗(yàn)證其有效性;
  4. 在系統(tǒng)內(nèi)核中注冊代碼簽名;
  5. 對 dylib 的每一個(gè) segment 調(diào)用 mmap()。

一般情況下,iOS App 需要加載 100-400 個(gè) dylibs。這些動(dòng)態(tài)庫包括系統(tǒng)的,也包括開發(fā)者手動(dòng)引入的。其中大部分 dylib 都是系統(tǒng)庫,系統(tǒng)已經(jīng)做了優(yōu)化,因此開發(fā)者更應(yīng)關(guān)心自己手動(dòng)集成的內(nèi)嵌 dylib,加載它們時(shí)性能開銷較大。

App 中依賴的 dylib 越少越好,Apple 官方建議盡量將內(nèi)嵌 dylib 的個(gè)數(shù)維持在6個(gè)以內(nèi)。

優(yōu)化方案

  • 盡量不使用內(nèi)嵌 dylib;
  • 合并已有內(nèi)嵌 dylib;
  • 檢查 framework 的 optionalrequired 設(shè)置,如果 framework 在當(dāng)前的 App 支持的 iOS 系統(tǒng)版本中都存在,就設(shè)為 required,因?yàn)樵O(shè)為 optional 會(huì)有額外的檢查;
  • 使用靜態(tài)庫作為代替;(不過靜態(tài)庫會(huì)在編譯期被打進(jìn)可執(zhí)行文件,造成可執(zhí)行文件體積增大,兩者各有利弊,開發(fā)者自行權(quán)衡。)
  • 懶加載 dylib。(但使用 dlopen() 對性能會(huì)產(chǎn)生影響,因?yàn)?App 啟動(dòng)時(shí)是原本是單線程運(yùn)行,系統(tǒng)會(huì)取消加鎖,但 dlopen() 開啟了多線程,系統(tǒng)不得不加鎖,這樣不僅會(huì)使性能降低,可能還會(huì)造成死鎖及未知的后果,不是很推薦這種做法。)

Rebase/Binding

這一步,做的是指針重定位。

在 dylib 的加載過程中,系統(tǒng)為了安全考慮,引入了 ASLR(Address Space Layout Randomization)技術(shù)和代碼簽名。由于 ASLR 的存在,鏡像會(huì)在新的隨機(jī)地址(actual_address)上加載,和之前指針指向的地址(preferred_address)會(huì)有一個(gè)偏差(slide,slide=actual_address-preferred_address),因此 dyld 需要修正這個(gè)偏差,指向正確的地址。具體通過這兩步實(shí)現(xiàn):

第一步:Rebase,在 image 內(nèi)部調(diào)整指針的指向。將 image 讀入內(nèi)存,并以 page 為單位進(jìn)行加密驗(yàn)證,保證不會(huì)被篡改,性能消耗主要在 IO。

第二步:Binding,符號綁定。將指針指向 image 外部的內(nèi)容。查詢符號表,設(shè)置指向鏡像外部的指針,性能消耗主要在 CPU 計(jì)算。

通過以下命令可以查看 rebase 和 bind 等信息:

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

通過 LC_DYLD_INFO_ONLY 可以查看各種信息的偏移量和大小。如果想要更方便直觀地查看,推薦使用 MachOView 工具。

指針數(shù)量越少,指針修復(fù)的耗時(shí)也就越少。所以,優(yōu)化該階段的關(guān)鍵就是減少 __DATA 段中的指針數(shù)量。

優(yōu)化方案

  • 減少 ObjC 類(class)、方法(selector)、分類(category)的數(shù)量,比如合并一些功能,刪除無效的類、方法和分類等(可以借助 AppCode 的 Inspect Code 功能進(jìn)行代碼瘦身);
  • 減少 C++ 虛函數(shù);(虛函數(shù)會(huì)創(chuàng)建 vtable,這也會(huì)在 __DATA 段中創(chuàng)建結(jié)構(gòu)。)
  • 多用 Swift Structs。(因?yàn)?Swift Structs 是靜態(tài)分發(fā)的,它的結(jié)構(gòu)內(nèi)部做了優(yōu)化,符號數(shù)量更少。)

ObjC Setup

完成 Rebase 和 Bind 之后,通知 runtime 去做一些代碼運(yùn)行時(shí)需要做的事情:

  • dyld 會(huì)注冊所有聲明過的 ObjC 類;
  • 將分類插入到類的方法列表中;
  • 檢查每個(gè) selector 的唯一性。

優(yōu)化方案

Rebase/Binding 階段優(yōu)化好了,這一步的耗時(shí)也會(huì)相應(yīng)減少。

Initializers

Rebase 和 Binding 屬于靜態(tài)調(diào)整(fix-up),修改的是 __DATA 段中的內(nèi)容,而這里則開始動(dòng)態(tài)調(diào)整,往堆和棧中寫入內(nèi)容。具體工作有:

  • 調(diào)用每個(gè) Objc 類和分類中的 +load 方法;
  • 調(diào)用 C/C++ 中的構(gòu)造器函數(shù)(用 attribute((constructor)) 修飾的函數(shù));
  • 創(chuàng)建非基本類型的 C++ 靜態(tài)全局變量。

優(yōu)化方案

  • 盡量避免在類的 +load 方法中初始化,可以推遲到 +initiailize 中進(jìn)行;(因?yàn)樵谝粋€(gè) +load 方法中進(jìn)行運(yùn)行時(shí)方法替換操作會(huì)帶來 4ms 的消耗)
  • 避免使用 __atribute__((constructor)) 將方法顯式標(biāo)記為初始化器,而是讓初始化方法調(diào)用時(shí)再執(zhí)行。比如用 dispatch_once()、pthread_once()std::once(),相當(dāng)于在第一次使用時(shí)才初始化,推遲了一部分工作耗時(shí)。:
  • 減少非基本類型的 C++ 靜態(tài)全局變量的個(gè)數(shù)。(因?yàn)檫@類全局變量通常是類或者結(jié)構(gòu)體,如果在構(gòu)造函數(shù)中有繁重的工作,就會(huì)拖慢啟動(dòng)速度)

總結(jié)一下 pre-main 階段可行的優(yōu)化方案:

  • 重新梳理架構(gòu),減少不必要的內(nèi)置動(dòng)態(tài)庫數(shù)量
  • 進(jìn)行代碼瘦身,合并或刪除無效的ObjC類、Category、方法、C++ 靜態(tài)全局變量等
  • 將不必須在 +load 方法中執(zhí)行的任務(wù)延遲到 +initialize
  • 減少 C++ 虛函數(shù)

main() 階段

對于 main() 階段,主要測量的就是從 main() 函數(shù)開始執(zhí)行到 didFinishLaunchingWithOptions 方法執(zhí)行結(jié)束的耗時(shí)。

查看階段耗時(shí)

這里介紹兩種查看 main() 階段耗時(shí)的方法。

方法一:手動(dòng)插入代碼,進(jìn)行耗時(shí)計(jì)算。

// 第一步:在 main() 函數(shù)里用變量 MainStartTime 記錄當(dāng)前時(shí)間
CFAbsoluteTime MainStartTime;
int main(int argc, char * argv[]) {
    MainStartTime = CFAbsoluteTimeGetCurrent();
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

// 第二步:在 AppDelegate.m 文件中用 extern 聲明全局變量 MainStartTime
extern CFAbsoluteTime MainStartTime;

// 第三步:在 didFinishLaunchingWithOptions 方法結(jié)束前,再獲取一下當(dāng)前時(shí)間,與 MainStartTime 的差值就是 main() 函數(shù)階段的耗時(shí)
double mainLaunchTime = (CFAbsoluteTimeGetCurrent() - MainStartTime);
NSLog(@"main() 階段耗時(shí):%.2fms", mainLaunchTime * 1000);

方法二:借助 Instruments 的 Time Profiler 工具查看耗時(shí)。

打開方式為:Xcode → Open Developer Tool → Instruments → Time Profiler。

Time Profiler

操作步驟:

  1. 配置 Scheme。點(diǎn)擊 Edit Scheme 找到 Profile 下的 Build Configuration,設(shè)置為 Debug。

  2. 配置 PROJECT。點(diǎn)擊 PROJECT,在 Build Settings 中找到 Build Options 選項(xiàng)里的 Debug Information Format,把 Debug 對應(yīng)的值改為 DWARF with dSYM File。

  3. 啟動(dòng) Time Profiler,點(diǎn)擊左上角紅色圓形按鈕開始檢測,然后就可以看到執(zhí)行代碼的完整路徑和對應(yīng)的耗時(shí)。

為了方面查看應(yīng)用程序中實(shí)際代碼的執(zhí)行耗時(shí)和代碼路徑實(shí)際所在的位置,可以勾選上 Call Tree 中的 Separate ThreadHide System Libraries。

查看啟動(dòng)耗時(shí)

啟動(dòng)優(yōu)化

main() 被調(diào)用之后,didFinishLaunchingWithOptions 階段,App 會(huì)進(jìn)行必要的初始化操作,而 viewDidAppear 執(zhí)行結(jié)束之前則是做了首頁內(nèi)容的加載和顯示。

關(guān)于 App 的初始化,除了統(tǒng)計(jì)、日志這種須要在 App 一啟動(dòng)就配置的事件,有一些配置也可以考慮延遲加載。如果你在 didFinishLaunchingWithOptions 中同時(shí)也涉及到了首屏的加載,那么可以考慮從這些角度優(yōu)化:

  • 用純代碼的方式,而不是 xib/Storyboard,來加載首頁視圖
  • 延遲暫時(shí)不需要的二方/三方庫加載;
  • 延遲執(zhí)行部分業(yè)務(wù)邏輯和 UI 配置;
  • 延遲加載/懶加載部分視圖;
  • 避免首屏加載時(shí)大量的本地/網(wǎng)絡(luò)數(shù)據(jù)讀??;
  • 在 release 包中移除 NSLog 打??;
  • 在視覺可接受的范圍內(nèi),壓縮頁面中的圖片大??;
  • ……

如果首屏為 H5 頁面,針對它的優(yōu)化,參考 VasSonic 的原理,可以從這幾個(gè)角度入手:

  • 終端耗時(shí)

    • webView 預(yù)加載:在 App 啟動(dòng)時(shí)期預(yù)先加載了一次 webView,通過創(chuàng)建空的 webView,預(yù)先啟動(dòng) Web 線程,完成一些全局性的初始化工作,對二次創(chuàng)建 webView 能有數(shù)百毫秒的提升。
  • 頁面耗時(shí)(靜態(tài)頁面)

    • 靜態(tài)直出:服務(wù)端拉取數(shù)據(jù)后通過 Node.js 進(jìn)行渲染,生成包含首屏數(shù)據(jù)的 HTML 文件,發(fā)布到 CDN 上,webView 直接從 CDN 上獲取;
    • 離線預(yù)推:使用離線包。
  • 頁面耗時(shí)(經(jīng)常需要?jiǎng)討B(tài)更新的頁面)

    • 并行加載:WebView 的打開和資源的請求并行;
    • 動(dòng)態(tài)緩存:動(dòng)態(tài)頁面緩存在客戶端,用戶下次打開的時(shí)候先打開緩存頁面,然后再刷新;
    • 動(dòng)靜分離:將頁面分為靜態(tài)模板和動(dòng)態(tài)數(shù)據(jù),根據(jù)不同的啟動(dòng)場景進(jìn)行不同的刷新方案;
    • 預(yù)加載:提前拉取需要的增量更新數(shù)據(jù)。

小結(jié)

隨著業(yè)務(wù)的增長,App 中的模塊越來越多,冷啟動(dòng)的時(shí)間也必不可少地增加。冷啟動(dòng)本就是一個(gè)比較復(fù)雜的流程,它的優(yōu)化沒有固定的公式,我們需要結(jié)合業(yè)務(wù),配合一些性能分析工具和線上監(jiān)控日志,有耐心、多維度地進(jìn)行分析和解決。


參考鏈接:

WWDC2016: Optimizing App Startup Time
WWDC2017: App Startup Time: Past, Present, and Future
優(yōu)化 App 的啟動(dòng)時(shí)間
今日頭條 iOS 客戶端啟動(dòng)速度優(yōu)化
VasSonic 源碼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 這是一篇 WWDC 2016 Session 406 的學(xué)習(xí)筆記,從原理到實(shí)踐講述了如何優(yōu)化 App 的啟動(dòng)時(shí)間。...
    MTDeveloper閱讀 826評論 0 1
  • 這是一篇 WWDC 2016 Session 406 的學(xué)習(xí)筆記,從原理到實(shí)踐講述了如何優(yōu)化 App 的啟動(dòng)時(shí)間。...
    茗涙閱讀 1,945評論 0 3
  • 本文分為理論【1-4】和實(shí)踐【5-6】兩部分: main()函數(shù)之前發(fā)生了什么 Mach-O格式 虛擬內(nèi)存基礎(chǔ)知識...
    WSJay閱讀 1,058評論 0 1
  • 探究App的啟動(dòng)過程,有助于我們優(yōu)化App的啟動(dòng)時(shí)間,從main函數(shù)之前和main函數(shù)之后兩個(gè)階段進(jìn)行分析一下。 ...
    沉江小魚閱讀 1,578評論 1 6
  • (有覺察的吵架) 昨天睡覺的時(shí)候,跟玖一直在溝通他獨(dú)立在高低床上面睡覺的事情(我和爸爸還是睡下面陪他)。 他勉強(qiáng)同...
    99媽閱讀 175評論 0 0

友情鏈接更多精彩內(nèi)容