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

F8A0E88A-7F17-42CD-87DE-E61A3B6A1B5F.png

冷啟動(dòng)

定義

從用戶點(diǎn)擊App圖標(biāo)開(kāi)始到appDelegate didFinishLaunching方法執(zhí)行完成為止碴开。

分為兩個(gè)階段:
T1pre-main階段揍堕,即main()函數(shù)之前,即操作系統(tǒng)加載App可執(zhí)行文件到內(nèi)存铝量,然后執(zhí)行一系列的加載&鏈接等工作抑进,最后執(zhí)行至App的main()函數(shù)骂倘;
T2main()函數(shù)之后,即從main()開(kāi)始赏僧,到appDelegatedidFinishLaunchingWithOptions方法執(zhí)行完畢前這段時(shí)間大猛,主要是構(gòu)建第一個(gè)界面,并完成渲染淀零。

從用戶點(diǎn)擊App圖標(biāo)開(kāi)始到用戶能看到App主界面內(nèi)容為止這個(gè)過(guò)程挽绩,即T1+T2。

main()函數(shù)階段的優(yōu)化

思路: 在main()函數(shù)之后的didFinishLaunchingWithOptions方法里執(zhí)行了各種業(yè)務(wù)驾中,有很多業(yè)務(wù)不是一定要在這里執(zhí)行唉堪,我們可以延遲加載,防止影響啟動(dòng)時(shí)間肩民。

didFinishLaunchingWithOptions方法里我們一般做一下邏輯:

  • 初始化第三方sdk
  • 配置App運(yùn)行需要的環(huán)境
  • 自己的一些工具類的初始化 等等

main階段的優(yōu)化大致有以下幾點(diǎn):

  • 減少啟動(dòng)初始化的流程唠亚,能懶加載的懶加載,能放后臺(tái)初始化的放后臺(tái)持痰,能延遲初始化的延遲灶搜,不要卡主線程的啟動(dòng)時(shí)間;
  • 優(yōu)化代碼邏輯工窍,去除一些非必要的邏輯和代碼占调,減少每個(gè)流程所消耗的時(shí)間;
  • 啟動(dòng)階段能使用多線程來(lái)進(jìn)行初始化移剪,就使用多線程究珊;
  • 使用純代碼而不是xib或者storyboard來(lái)進(jìn)行UI框架的搭建,尤其是主UI框架比如TabBarController這種纵苛,盡量避免使用xib或者storyboard剿涮,因?yàn)樗鼈円策€是要解析成代碼才去渲染頁(yè)面,多了一些步驟攻人;

上面這些優(yōu)化點(diǎn)取试,都是前人們總結(jié)出來(lái)的,在自己的項(xiàng)目實(shí)際優(yōu)化的過(guò)程中怀吻,還是需要結(jié)合業(yè)務(wù)邏輯來(lái)處理瞬浓。

筆者在實(shí)際操作的過(guò)程中,先是通過(guò)工具檢測(cè)出這個(gè)過(guò)程中蓬坡,找出所有方法的耗時(shí)時(shí)長(zhǎng)猿棉,然后根據(jù)具體的業(yè)務(wù)邏輯去優(yōu)化的磅叛。

耗時(shí)方法的檢測(cè)

其實(shí)這階段的優(yōu)化很明顯,只要我們找出耗時(shí)操作萨赁,然后對(duì)其進(jìn)行相應(yīng)的分析做處理弊琴,該延遲調(diào)用的延遲,該懶加載的懶加載杖爽,便能縮短啟動(dòng)時(shí)間敲董。

可以通過(guò)instrumentTime profile工具來(lái)分析耗時(shí)。如下是我實(shí)踐的過(guò)程慰安。
首先對(duì)Xcode進(jìn)行配置:
步驟一

image.png

步驟二

image.png

步驟三
對(duì)項(xiàng)目進(jìn)行command + shit + k清除操作腋寨,然后command + R運(yùn)行,最后進(jìn)行instrument工具的喚起化焕,利用快捷鍵command + I即可精置。

image.png

選擇Time Profiler,點(diǎn)擊Choose即可锣杂。

為了能夠更加直觀的觀察脂倦,我們可以進(jìn)行下面的配置


image.png

然后點(diǎn)擊左上角的紅色圓圈便可進(jìn)行耗時(shí)檢測(cè),如下圖:


image.png

從上面能夠非常直觀的看到每個(gè)方法以及對(duì)應(yīng)的耗時(shí)元莫,從這里赖阻,我們便能夠找到哪些是我們所要優(yōu)化的。

首頁(yè)的頁(yè)面渲染

如果你在上一步的檢測(cè)過(guò)程中踱蠢,發(fā)現(xiàn)TabBarControllerviewDidLoad耗時(shí)較長(zhǎng)火欧,那么就要進(jìn)行下面的檢測(cè)。

首頁(yè)的viewDidLoad以及viewWillAppear方法中盡量去嘗試少做茎截,晚做苇侵,或者采取異步的方式去做。

如下一段代碼:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    NSLog(@"didFinishLaunchingWithOptions 開(kāi)始執(zhí)行");
    self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
    ISTabBarController *tabBarVc = [[ISTabBarController alloc]init];
    self.window.rootViewController = tabBarVc;
    [self.window makeKeyAndVisible];
    NSLog(@"didFinishLaunchingWithOptions 跑完了");
    return YES;
}

然后來(lái)到ISTabBarControllerviewDidLoad方法里進(jìn)行他的viewControllers的設(shè)置企锌,然后再進(jìn)入到每個(gè)viewControllerviewDidLoad方法里進(jìn)行更多的初始化操作榆浓。

2020-02-17 11:17:17.024481+0800 InnotechShop[3776:1477241] didFinishLaunchingWithOptions 開(kāi)始執(zhí)行
2020-02-17 11:17:17.034835+0800 InnotechShop[3776:1477241] 開(kāi)始加載 ISTabBarController 的 viewDidLoad
2020-02-17 11:17:17.034934+0800 InnotechShop[3776:1477241] didFinishLaunchingWithOptions 跑完了
2020-02-17 11:17:17.034965+0800 InnotechShop[3776:1477241] 開(kāi)始加載 ISViewController 的 viewDidLoad, 然后執(zhí)行一堆初始化的操作

這種情況是能保證我們不在ISTabBarController中操作ISViewControllerview, 如果我們?cè)?code>ISTabBarController中操作ISViewControllerview的話撕攒,那么調(diào)用順序?qū)?huì)是下面這樣:

2020-02-17 11:23:42.018824+0800 InnotechShop[3796:1480231] didFinishLaunchingWithOptions 開(kāi)始執(zhí)行
2020-02-17 11:23:42.018883+0800 InnotechShop[3796:1480231] 開(kāi)始加載 ISTabBarController 的 viewDidLoad
2020-02-17 11:23:42.018957+0800 InnotechShop[3796:1480231] 開(kāi)始加載 ISViewController 的 viewDidLoad, 然后執(zhí)行一堆初始化的操作
2020-02-17 11:23:42.019020+0800 InnotechShop[3796:1480231] didFinishLaunchingWithOptions 跑完了

這樣的話陡鹃,我們就把界面的初始化、網(wǎng)絡(luò)請(qǐng)求抖坪、數(shù)據(jù)解析萍鲸、視圖渲染等操作都放在了viewDidLoad方法里,那么每次啟動(dòng)App的時(shí)候擦俐,在用戶看到第一個(gè)頁(yè)面之前脊阴,我們都要把這些事情全部處理完成,才會(huì)進(jìn)入到視圖渲染階段。

由于筆者項(xiàng)目的業(yè)務(wù)邏輯并不是那么的復(fù)雜嘿期,所以在實(shí)踐中大概做了一下幾點(diǎn):

  • 把一些沒(méi)有必要在didFinishLaunchingWithOptions進(jìn)行初始化的操作品擎,延遲到首頁(yè)渲染完成以后調(diào)用
  • 友盟的分享服務(wù),沒(méi)有必要在啟動(dòng)的時(shí)候去初始化秽五,初始化任務(wù)丟到異步線程解決孽查,大概節(jié)省幾百毫秒饥悴;
  • 主UI框架tabBarControllerviewDidLoad函數(shù)里坦喘,去掉一些不必要的函數(shù)調(diào)用;

優(yōu)化前后耗時(shí)對(duì)比:


image.png

小結(jié)

對(duì)于didFinishLaunchingWithOptions西设,這里面的初始化是必須執(zhí)行的瓣铣,但是我們可以適當(dāng)?shù)母鶕?jù)功能的不同對(duì)應(yīng)的適當(dāng)延遲啟動(dòng)的時(shí)機(jī)。對(duì)于我們項(xiàng)目贷揽,我將初始化分為三個(gè)類型:

  • 日志棠笑、統(tǒng)計(jì)等必須在 APP 一啟動(dòng)就最先配置的事件
  • 項(xiàng)目配置、環(huán)境配置禽绪、用戶信息的初始化 蓖救、推送、IM等事件
  • 其他 SDK 和配置事件

對(duì)于第一類印屁,由于這類事件的特殊性循捺,所以必須第一時(shí)間啟動(dòng),仍然把它留在didFinishLaunchingWithOptions 里啟動(dòng)雄人。第二類事件从橘,這些功能在用戶進(jìn)入APP主體的之前是必須要加載完的,所以我們可以把它放在第二批础钠,也就是用戶已經(jīng)看到廣告頁(yè)面恰力,再進(jìn)行廣告倒計(jì)時(shí)的時(shí)候再啟動(dòng)。第三類事件旗吁,由于不是必須的踩萎,所以我們可以放在第一個(gè)界面渲染完成以后的viewDidAppear方法里,這里完全不會(huì)影響到啟動(dòng)時(shí)間很钓。

閃屏優(yōu)化

現(xiàn)在許多App在啟動(dòng)時(shí)并不直接進(jìn)入首頁(yè)驻民,而是會(huì)向用戶展示一個(gè)持續(xù)一小段時(shí)間的閃屏頁(yè),如果使用恰當(dāng)履怯,這個(gè)閃屏頁(yè)就能幫我們節(jié)省一些啟動(dòng)時(shí)間回还。
下面看兩組閃屏的流程對(duì)比即可發(fā)現(xiàn)好處:
未優(yōu)化的閃屏流程:

image.png

優(yōu)化的閃屏流程:

image.png

具體可以參考這里

pre-main 階段優(yōu)化

以下為iPhone 7p正常啟動(dòng)消耗的pre-main時(shí)間(蘋(píng)果提供了內(nèi)建的測(cè)量方法,在 Xcode 中 Edit scheme -> Run -> Auguments -> Environment Variables點(diǎn)擊+添加環(huán)境變量 DYLD_PRINT_STATISTICS 設(shè)為 1):

Total pre-main time: 608.72 milliseconds (100.0%)
         dylib loading time: 308.40 milliseconds (50.6%)
        rebase/binding time:  28.92 milliseconds (4.7%)
            ObjC setup time:  22.50 milliseconds (3.6%)
           initializer time: 248.89 milliseconds (40.8%)
           slowest intializers :
             libSystem.B.dylib :   3.75 milliseconds (0.6%)
    libMainThreadChecker.dylib :  31.74 milliseconds (5.2%)
          libglInterpose.dylib : 135.63 milliseconds (22.2%)
                  HelpDeskLite :  21.11 milliseconds (3.4%)
                     InnoAVKit :  20.81 milliseconds (3.4%)
                  InnotechShop :  29.62 milliseconds (4.8%)

解讀:
1叹洲、main()函數(shù)之前總共用時(shí)608.72ms
2柠硕、在608.72ms中,加載動(dòng)態(tài)庫(kù)使用了308.4ms,指針重定位用了28.92ms蝗柔,ObjC類初始化使用了22.50ms闻葵,各種初始化使用了248.89ms
3、在初始化用時(shí)的248.89ms中癣丧,用時(shí)較多的幾個(gè)初始化是libglInterpose.dylib槽畔、ibMainThreadChecker.dylib、InnotechShop胁编、InnoAVKit等

pre-main階段的原理

main()函數(shù)之前厢钧,基本上所有的工作都是系統(tǒng)完成的,開(kāi)發(fā)者能夠處理的地方不多嬉橙,所以想要對(duì)這部分進(jìn)行優(yōu)化早直,那么就需要了解一下這一過(guò)程系統(tǒng)都做了哪些事情,(原理部分的內(nèi)容基本上都是網(wǎng)上摘錄的)市框。
這部分比較晦澀難懂霞扬,需要細(xì)品

pre-main

image.png

可執(zhí)行文件的內(nèi)核流程

如圖,當(dāng)啟動(dòng)一個(gè)應(yīng)用程序時(shí)枫振,系統(tǒng)最后會(huì)根據(jù)你的行為調(diào)用兩個(gè)函數(shù)喻圃,forkexecvefork功能創(chuàng)建一個(gè)進(jìn)程粪滤;execve功能加載和運(yùn)行程序斧拍。這里有多個(gè)不同的功能,比如execl,execv和exect额衙,每個(gè)功能提供了不同傳參和環(huán)境變量的方法到程序中饮焦。在OSX中,每個(gè)這些其他的exec路徑最終調(diào)用了內(nèi)核路徑execve窍侧。

image.png

1县踢、執(zhí)行exec系統(tǒng)調(diào)用,一般都是這樣伟件,用fork()函數(shù)新建立一個(gè)進(jìn)程硼啤,然后讓進(jìn)程去執(zhí)行exec調(diào)用。我們知道斧账,在fork()建立新進(jìn)程之后谴返,父進(jìn)程與子進(jìn)程共享代碼段,但數(shù)據(jù)空間是分開(kāi)的咧织,但父進(jìn)程會(huì)把自己數(shù)據(jù)空間的內(nèi)容copy到子進(jìn)程中去嗓袱,還有上下文也會(huì)copy到子進(jìn)程中去。
2习绢、為了提高效率渠抹,采用一種寫(xiě)時(shí)copy的策略蝙昙,即創(chuàng)建子進(jìn)程的時(shí)候,并不copy父進(jìn)程的地址空間梧却,父子進(jìn)程擁有共同的地址空間奇颠,只有當(dāng)子進(jìn)程需要寫(xiě)入數(shù)據(jù)時(shí)(如向緩沖區(qū)寫(xiě)入數(shù)據(jù)),這時(shí)候會(huì)復(fù)制地址空間放航,復(fù)制緩沖區(qū)到子進(jìn)程中去烈拒。從而父子進(jìn)程擁有獨(dú)立的地址空間。而對(duì)于fork()之后執(zhí)行exec后广鳍,這種策略能夠很好的提高效率荆几,如果一開(kāi)始就copy,那么exec之后搜锰,子進(jìn)程的數(shù)據(jù)會(huì)被放棄伴郁,被新的進(jìn)程所代替

動(dòng)態(tài)鏈接庫(kù)dyld

什么是dyld耿战?

動(dòng)態(tài)鏈接庫(kù)的加載過(guò)程主要由dyld來(lái)完成蛋叼,dyld是蘋(píng)果的動(dòng)態(tài)鏈接器
系統(tǒng)先讀取App的可執(zhí)行文件(Mach-O文件),從里面獲得dyld的路徑剂陡,然后加載dyld狈涮,dyld去初始化運(yùn)行環(huán)境,開(kāi)啟緩存策略鸭栖,加載程序相關(guān)依賴庫(kù)(其中也包含我們的可執(zhí)行文件)歌馍,并對(duì)這些庫(kù)進(jìn)行鏈接,最后調(diào)用每個(gè)依賴庫(kù)的初始化方法晕鹊,在這一步松却,runtime被初始化。當(dāng)所有依賴庫(kù)的初始化后溅话,輪到最后一位(程序可執(zhí)行文件)進(jìn)行初始化晓锻,在這時(shí)runtime會(huì)對(duì)項(xiàng)目中所有類進(jìn)行類結(jié)構(gòu)初始化,然后調(diào)用所有的load方法飞几。最后dyld返回main函數(shù)地址砚哆,main函數(shù)被調(diào)用,我們便來(lái)到了熟悉的程序入口屑墨。

dyld共享庫(kù)緩存

當(dāng)你構(gòu)建一個(gè)真正的程序時(shí)躁锁,將會(huì)鏈接各種各樣的庫(kù)。它們又會(huì)依賴其他一些framework和動(dòng)態(tài)庫(kù)卵史。需要加載的動(dòng)態(tài)庫(kù)會(huì)非常多战转。而對(duì)于相互依賴的符號(hào)就更多了∫郧可能將會(huì)有上千個(gè)符號(hào)需要解析處理槐秧,這將花費(fèi)很長(zhǎng)的時(shí)間
為了縮短這個(gè)處理過(guò)程所花費(fèi)時(shí)間,OS X 和 iOS 上的動(dòng)態(tài)鏈接器使用了共享緩存,OS X的共享緩存位于/private/var/db/dyld/色鸳,iOS的則在/System/Library/Caches/com.apple.dyle/社痛。
對(duì)于每一種架構(gòu),操作系統(tǒng)都有一個(gè)單獨(dú)的文件命雀,文件中包含了絕大多數(shù)的動(dòng)態(tài)庫(kù)蒜哀,這些庫(kù)都已經(jīng)鏈接為一個(gè)文件,并且已經(jīng)處理好了它們之間的符號(hào)關(guān)系吏砂。當(dāng)加載一個(gè) Mach-O 文件 (一個(gè)可執(zhí)行文件或者一個(gè)庫(kù)) 時(shí)撵儿,動(dòng)態(tài)鏈接器首先會(huì)檢查共享緩存看看是否存在其中,如果存在狐血,那么就直接從共享緩存中拿出來(lái)使用淀歇。每一個(gè)進(jìn)程都把這個(gè)共享緩存映射到了自己的地址空間中。這個(gè)方法大大優(yōu)化了 OS X 和 iOS 上程序的啟動(dòng)時(shí)間匈织。

dyld加載過(guò)程

dyld的加載過(guò)程主要分為下面幾個(gè)步驟:

1浪默、Load dylibs image

在每個(gè)動(dòng)態(tài)庫(kù)的加載過(guò)程中,dyld需要做下面工作:

  1. 分析所以來(lái)的動(dòng)態(tài)庫(kù)
  2. 找到動(dòng)態(tài)庫(kù)的mach-o文件
  3. 打開(kāi)文件
  4. 驗(yàn)證文件
  5. 在系統(tǒng)核心注冊(cè)文件簽名
  6. 對(duì)動(dòng)態(tài)庫(kù)的每一個(gè)segment調(diào)用mmap()

針對(duì)這一步的優(yōu)化:

  1. 減少非系統(tǒng)庫(kù)的依賴
  2. 合并非系統(tǒng)庫(kù)

看下筆者項(xiàng)目依賴的共享動(dòng)態(tài)庫(kù)
輸入命令:otool -L XXXX

image.png

2缀匕、Rebase/Bind image

由于ASLR(address space layout randomization)的存在纳决,可執(zhí)行文件和動(dòng)態(tài)鏈接庫(kù)在虛擬內(nèi)存中的加載地址每次啟動(dòng)都不固定,所以需要這2步來(lái)修復(fù)鏡像中的資源指針乡小,來(lái)指向正確的地址阔加。 rebase修復(fù)的是指向當(dāng)前鏡像內(nèi)部的資源指針; 而bind指向的是鏡像外部的資源指針满钟。

rebase步驟先進(jìn)行胜榔,需要把鏡像讀入內(nèi)存,并以page為單位進(jìn)行加密驗(yàn)證湃番,保證不會(huì)被篡改夭织,所以這一步的瓶頸在IO。bind在其后進(jìn)行牵辣,由于要查詢符號(hào)表摔癣,來(lái)指向跨鏡像的資源,加上在rebase階段纬向,鏡像已被讀入和加密驗(yàn)證择浊,所以這一步的瓶頸在于CPU計(jì)算。

優(yōu)化該階段的關(guān)鍵在于減少__DATA segment中的指針數(shù)量逾条。我們可以優(yōu)化的點(diǎn)有:

  1. 減少Objc類數(shù)量琢岩, 減少selector數(shù)量
  2. 減少C++虛函數(shù)數(shù)量
3、Objc setup

Objc setup主要是在objc_init完成的师脂,objc_init是在libsystem中的一個(gè)initialize方法libsystem_initializer中初始化了libdispatch担孔,然后libdispatch_init調(diào)用了_os_object_int江锨, 最終調(diào)用了_objc_init

image.png

void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    environ_init();
    tls_init();
    static_init();
    lock_init();
    exception_init();

    _dyld_objc_notify_register(&map_2_images, load_images, unmap_image);
}

通過(guò)上面代碼可以知道糕篇,runtime_objc_initdyld綁定了3個(gè)回調(diào)函數(shù)啄育,分別是map_2_images,load_images和unmap_image

1、dyld在binding操作結(jié)束之后拌消,會(huì)發(fā)出dyld_image_state_bound通知挑豌,然后與之綁定的回調(diào)函數(shù)map_2_images就會(huì)被調(diào)用,它主要做以下幾件事來(lái)完成Objc Setup

  • 讀取二進(jìn)制文件的 DATA 段內(nèi)容墩崩,找到與 objc 相關(guān)的信息
  • 注冊(cè) Objc 類
  • 確保 selector 的唯一性
  • 讀取 protocol 以及 category 的信息

2氓英、load_images函數(shù)作用就是調(diào)用Objc的load方法,它監(jiān)聽(tīng)dyld_image_state_dependents_initialize通知
3鹦筹、unmap_image可以理解為map_2_images的逆向操作

由于之前2步驟的優(yōu)化铝阐,這一步實(shí)際上沒(méi)有什么可做的。幾乎都靠 Rebasing 和 Binding 步驟中減少所需 fix-up 內(nèi)容铐拐。因?yàn)榍懊娴墓ぷ饕矔?huì)使得這步耗時(shí)減少徘键。

4、initializers

以上三步屬于靜態(tài)調(diào)整余舶,都是在修改__DATA segment中的內(nèi)容啊鸭,而這里則開(kāi)始動(dòng)態(tài)調(diào)整锹淌,開(kāi)始在堆和棧中寫(xiě)入內(nèi)容匿值。 工作主要有:

1、 Objc的+load()函數(shù)
2赂摆、 C++的構(gòu)造函數(shù)屬性函數(shù) 形如attribute((constructor)) void DoSomeInitializationWork()
3挟憔、 非基本類型的C++靜態(tài)全局變量的創(chuàng)建(通常是類或結(jié)構(gòu)體)(non-trivial initializer) 比如一個(gè)全局靜態(tài)結(jié)構(gòu)體的構(gòu)建,如果在構(gòu)造函數(shù)中有繁重的工作烟号,那么會(huì)拖慢啟動(dòng)速度

Objc的load函數(shù)和C++的靜態(tài)構(gòu)造器采用由底向上的方式執(zhí)行绊谭,來(lái)保證每個(gè)執(zhí)行的方法,都可以找到所依賴的動(dòng)態(tài)庫(kù)

1汪拥、 dyld開(kāi)始將程序二進(jìn)制文件初始化
2达传、 交由ImageLoader讀取image,其中包含了我們的類迫筑、方法等各種符號(hào)
3宪赶、 由于runtime向dyld綁定了回調(diào),當(dāng)image加載到內(nèi)存后脯燃,dyld會(huì)通知runtime進(jìn)行處理
4搂妻、 runtime接手后調(diào)用map images做解析和處理,接下來(lái)load images中調(diào)用 callloadmethods方法辕棚,遍歷所有加載進(jìn)來(lái)的Class欲主,按繼承層級(jí)依次調(diào)用Class+load方法和其 Category+load方法

整個(gè)事件由dyld主導(dǎo)邓厕,完成運(yùn)行環(huán)境的初始化后,配合ImageLoader 將二進(jìn)制文件按格式加載到內(nèi)存扁瓢,動(dòng)態(tài)鏈接依賴庫(kù)详恼,并由runtime負(fù)責(zé)加載成objc 定義的結(jié)構(gòu),所有初始化工作結(jié)束后引几,dyld調(diào)用真正的main函數(shù)

這一步可做的優(yōu)化有:

  • 使用+initialize來(lái)代替+load
  • 不要使用atribute((constructor)) 將方法顯式標(biāo)記為初始化器单雾,而是讓初始化方法調(diào)用時(shí)才執(zhí)行。比如使用 dispatch_once()她紫、pthread_once() 或 std::once()硅堆。也就是在第一次使用時(shí)才初始化,推遲了一部分工作耗時(shí)贿讹。也盡量不要用到C++的靜態(tài)對(duì)象渐逃。

pre-main階段具體優(yōu)化

1、刪除無(wú)用代碼(未被調(diào)用的靜態(tài)變量民褂、類和方法)

  • 可以使用AppCode對(duì)工程進(jìn)行掃描茄菊,刪除無(wú)用代碼
  • 刪減一些無(wú)用的靜態(tài)變量
  • 刪減沒(méi)有被調(diào)用或者已經(jīng)廢棄的方法


    image.png

2、+load方法處理

+load()方法赊堪,用于在App啟動(dòng)執(zhí)行一些操作面殖,+load()方法在Initializers階段被執(zhí)行,但過(guò)多的+load()方法則會(huì)拖慢啟動(dòng)速度哭廉。
分析+load()方法脊僚,看是否可以延遲到App冷啟動(dòng)后的某個(gè)時(shí)間節(jié)點(diǎn)。

筆者在處理這個(gè)問(wèn)題的過(guò)程中遇到一個(gè)坑遵绰,項(xiàng)目里有防crash的類辽幌,里面有大量的系統(tǒng)類的load方法,針對(duì)系統(tǒng)的load方法椿访,我們不用去優(yōu)化乌企,因?yàn)樵趩?dòng)的過(guò)程中,有可能initialize方法也會(huì)被調(diào)用成玫,并起不到優(yōu)化的作用加酵,反而還是出現(xiàn)各種各樣的問(wèn)題;另外一點(diǎn)需要注意的問(wèn)題是initialize的重復(fù)調(diào)用問(wèn)題哭当,能用dispatch_once()來(lái)完成的猪腕,就盡量不要用到load方法

3、針對(duì)減少不必要的庫(kù)

統(tǒng)計(jì)了各個(gè)庫(kù)所占的size(安裝包size優(yōu)化的腳本)荣病,基本上一個(gè)公共庫(kù)越大码撰,類越多,啟動(dòng)時(shí)在pre-main階段所需的時(shí)間也越多个盆。
統(tǒng)計(jì)結(jié)果如下:

pod有源碼的庫(kù)(靜態(tài)庫(kù)):


image.png

第三方framework(其實(shí)也是靜態(tài)庫(kù)脖岛,只是腳本分開(kāi)統(tǒng)計(jì)):


image.png

筆者項(xiàng)目中使用cocoapods并沒(méi)有設(shè)置use_frameworks朵栖,所以pod管理的有源碼的第三方庫(kù)都是靜態(tài)庫(kù)的形式,而framework形式的靜態(tài)庫(kù)基本都是第三方公司提供的服務(wù)

這個(gè)過(guò)程并沒(méi)有做任何優(yōu)化柴梆,對(duì)庫(kù)進(jìn)行了逐一排查陨溅,均為正在使用,顧這個(gè)環(huán)節(jié)沒(méi)有優(yōu)化绍在,只是記錄了一下门扇。

4、合并功能類似的類和擴(kuò)展(Category)

由于Category的實(shí)現(xiàn)原理偿渡,和ObjC的動(dòng)態(tài)綁定有很強(qiáng)的關(guān)系臼寄,所以實(shí)際上類的擴(kuò)展是比較占用啟動(dòng)時(shí)間的。盡量合并一些擴(kuò)展溜宽,會(huì)對(duì)啟動(dòng)有一定的優(yōu)化作用吉拳。不過(guò)個(gè)人認(rèn)為也不能因?yàn)樗加脝?dòng)時(shí)間而去逃避使用擴(kuò)展,畢竟程序員的時(shí)間比CPU的時(shí)間值錢(qián)适揉,這里只是強(qiáng)調(diào)要合并一些在工程留攒、架構(gòu)上沒(méi)有太大意義的擴(kuò)展。

5嫉嘀、壓縮資源圖片

壓縮圖片為什么能加快啟動(dòng)速度呢炼邀?因?yàn)閱?dòng)的時(shí)候大大小小的圖片加載個(gè)十來(lái)二十個(gè)是很正常的,圖片小了剪侮,IO操作量就小了拭宁,啟動(dòng)當(dāng)然就會(huì)快了。

以上內(nèi)容就是本次在做啟動(dòng)時(shí)間優(yōu)化所涉及的內(nèi)容票彪,理論知識(shí)都是從網(wǎng)上查詢得知红淡,具體實(shí)踐,筆者都一一嘗試降铸,作為記錄。

參考資料:
美團(tuán)外賣iOS App冷啟動(dòng)治理
iOS啟動(dòng)優(yōu)化-凌云的博客
iOS啟動(dòng)時(shí)間優(yōu)化-第七章
iOS啟動(dòng)時(shí)間優(yōu)化-PerTerbin
iOS App 啟動(dòng)性能優(yōu)化

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末摇零,一起剝皮案震驚了整個(gè)濱河市推掸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌驻仅,老刑警劉巖谅畅,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異噪服,居然都是意外死亡毡泻,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)粘优,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)仇味,“玉大人呻顽,你說(shuō)我怎么就攤上這事〉つ” “怎么了廊遍?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)贩挣。 經(jīng)常有香客問(wèn)我喉前,道長(zhǎng),這世上最難降的妖魔是什么王财? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任卵迂,我火速辦了婚禮,結(jié)果婚禮上绒净,老公的妹妹穿的比我還像新娘狭握。我一直安慰自己,他們只是感情好疯溺,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布论颅。 她就那樣靜靜地躺著,像睡著了一般囱嫩。 火紅的嫁衣襯著肌膚如雪恃疯。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,165評(píng)論 1 299
  • 那天墨闲,我揣著相機(jī)與錄音今妄,去河邊找鬼。 笑死鸳碧,一個(gè)胖子當(dāng)著我的面吹牛盾鳞,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播瞻离,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼腾仅,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了套利?” 一聲冷哼從身側(cè)響起推励,我...
    開(kāi)封第一講書(shū)人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎肉迫,沒(méi)想到半個(gè)月后验辞,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡喊衫,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年跌造,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片族购。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡壳贪,死狀恐怖陵珍,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情撑碴,我是刑警寧澤撑教,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站醉拓,受9級(jí)特大地震影響伟姐,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜亿卤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一愤兵、第九天 我趴在偏房一處隱蔽的房頂上張望杈绸。 院中可真熱鬧几苍,春花似錦、人聲如沸纪铺。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至街氢,卻和暖如春扯键,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背珊肃。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工荣刑, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人伦乔。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓厉亏,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親烈和。 傳聞我的和親對(duì)象是個(gè)殘疾皇子爱只,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353

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