国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統(tǒng) > iOS > 正文

ios學習--從今日頭條iOS客戶端啟動速度優(yōu)化中學習

2019-11-09 16:16:59
字體:
供稿:網(wǎng)友

應用啟動時間,直接影響用戶對一款應用的判斷和使用體驗。頭條主app本身就包含非常多并且復雜度高的業(yè)務模塊(如新聞、視頻等),也接入了很多第三方的插件,這勢必會拖慢應用的啟動時間,本著精益求精的態(tài)度和對用戶體驗的追求,我們希望在業(yè)務擴張的同時最大程度的優(yōu)化啟動時間。

技術(shù)調(diào)研

先說結(jié)論,t(App總啟動時間) = t1(main()之前的加載時間) + t2(main()之后的加載時間)。t1 = 系統(tǒng)dylib(動態(tài)鏈接庫)和自身App可執(zhí)行文件的加載;t2 = main方法執(zhí)行之后到AppDelegate類中的- (BOOL)application:(UIApplication *)Application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions方法執(zhí)行結(jié)束前這段時間,主要是構(gòu)建第一個界面,并完成渲染展示。

main()調(diào)用之前的加載過程

App開始啟動后, 系統(tǒng)首先加載可執(zhí)行文件(自身App的所有.o文件的集合),然后加載動態(tài)鏈接庫dyld,dyld是一個專門用來加載動態(tài)鏈接庫的庫。 執(zhí)行從dyld開始,dyld從可執(zhí)行文件的依賴開始, 遞歸加載所有的依賴動態(tài)鏈接庫。動態(tài)鏈接庫包括:iOS 中用到的所有系統(tǒng) framework,加載OC runtime方法的libobjc,系統(tǒng)級別的libSystem,例如libdispatch(GCD)和libsystem_blocks (Block)。

其實無論對于系統(tǒng)的動態(tài)鏈接庫還是對于App本身的可執(zhí)行文件而言,他們都算是image(鏡像),而每個App都是以image(鏡像)為單位進行加載的,那么image究竟包括哪些呢?

什么是image

1.executable可執(zhí)行文件 比如.o文件。2.dylib 動態(tài)鏈接庫 framework就是動態(tài)鏈接庫和相應資源包含在一起的一個文件夾結(jié)構(gòu)。3.bundle 資源文件 只能用dlopen加載,不推薦使用這種方式加載。

除了我們App本身的可行性文件,系統(tǒng)中所有的framework比如UIKit、Foundation等都是以動態(tài)鏈接庫的方式集成進App中的。

系統(tǒng)使用動態(tài)鏈接有幾點好處:

代碼共用:很多程序都動態(tài)鏈接了這些 lib,但它們在內(nèi)存和磁盤中中只有一份。易于維護:由于被依賴的 lib 是程序執(zhí)行時才鏈接的,所以這些 lib 很容易做更新,比如libSystem.dylib 是 libSystem.B.dylib 的替身,哪天想升級直接換成libSystem.C.dylib 然后再替換替身就行了。減少可執(zhí)行文件體積:相比靜態(tài)鏈接,動態(tài)鏈接在編譯時不需要打進去,所以可執(zhí)行文件的體積要小很多。

如上圖所示,不同進程之間共用系統(tǒng)dylib的_TEXT區(qū),但是各自維護對應的_DATA區(qū)。

所有動態(tài)鏈接庫和我們App中的靜態(tài)庫.a和所有類文件編譯后的.o文件最終都是由dyld(the dynamic link editor),Apple的動態(tài)鏈接器來加載到內(nèi)存中。每個image都是由一個叫做ImageLoader的類來負責加載(一一對應),那么ImageLoader又是什么呢?

什么是ImageLoader

image 表示一個二進制文件(可執(zhí)行文件或 so 文件),里面是被編譯過的符號、代碼等,所以 ImageLoader 作用是將這些文件加載進內(nèi)存,且每一個文件對應一個ImageLoader實例來負責加載。兩步走:在程序運行時它先將動態(tài)鏈接的 image 遞歸加載 (也就是上面測試棧中一串的遞歸調(diào)用的時刻)。再從可執(zhí)行文件 image 遞歸加載所有符號。

當然所有這些都發(fā)生在我們真正的main函數(shù)執(zhí)行前。

動態(tài)鏈接庫加載的具體流程

動態(tài)鏈接庫的加載步驟具體分為5步:

load dylibs image 讀取庫鏡像文件Rebase imageBind imageObjc setupinitializers

load dylibs image

在每個動態(tài)庫的加載過程中, dyld需要:

分析所依賴的動態(tài)庫找到動態(tài)庫的mach-o文件打開文件驗證文件在系統(tǒng)核心注冊文件簽名對動態(tài)庫的每一個segment調(diào)用mmap()

通常的,一個App需要加載100到400個dylibs, 但是其中的系統(tǒng)庫被優(yōu)化,可以很快的加載。針對這一步驟的優(yōu)化有:

減少非系統(tǒng)庫的依賴合并非系統(tǒng)庫使用靜態(tài)資源,比如把代碼加入主程序

rebase/bind

由于ASLR(address space layout randomization)的存在,可執(zhí)行文件和動態(tài)鏈接庫在虛擬內(nèi)存中的加載地址每次啟動都不固定,所以需要這2步來修復鏡像中的資源指針,來指向正確的地址。rebase修復的是指向當前鏡像內(nèi)部的資源指針; 而bind指向的是鏡像外部的資源指針。rebase步驟先進行,需要把鏡像讀入內(nèi)存,并以page為單位進行加密驗證,保證不會被篡改,所以這一步的瓶頸在IO。bind在其后進行,由于要查詢符號表,來指向跨鏡像的資源,加上在rebase階段,鏡像已被讀入和加密驗證,所以這一步的瓶頸在于CPU計算。通過命令行可以查看相關(guān)的資源指針:

xcrun dyldinfo -rebase -bind -lazy_bind myApp.App/myApp

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

減少Objc類數(shù)量, 減少selector數(shù)量減少C++虛函數(shù)數(shù)量轉(zhuǎn)而使用swift stuct(其實本質(zhì)上就是為了減少符號的數(shù)量)

Objc setup

這一步主要工作是:

注冊Objc類 (class registration)把category的定義插入方法列表 (category registration)保證每一個selector唯一 (selctor uniquing)

由于之前2步驟的優(yōu)化,這一步實際上沒有什么可做的。

initializers

以上三步屬于靜態(tài)調(diào)整(fix-up),都是在修改__DATA segment中的內(nèi)容,而這里則開始動態(tài)調(diào)整,開始在堆和堆棧中寫入內(nèi)容。在這里的工作有:

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

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

上圖是在自定義的類XXViewController的+load方法斷點的調(diào)用堆棧,清楚的看到整個調(diào)用棧和順序:

dyld 開始將程序二進制文件初始化交由 ImageLoader 讀取 image,其中包含了我們的類、方法等各種符號由于 runtime 向 dyld 綁定了回調(diào),當 image 加載到內(nèi)存后,dyld 會通知 runtime 進行處理runtime 接手后調(diào)用 map_images 做解析和處理,接下來 load_images 中調(diào)用 call_load_methods 方法,遍歷所有加載進來的 Class,按繼承層級依次調(diào)用 Class 的 +load 方法和其 Category 的 +load 方法

至此,可執(zhí)行文件中和動態(tài)庫所有的符號(Class,PRotocol,Selector,IMP,…)都已經(jīng)按格式成功加載到內(nèi)存中,被 runtime 所管理,再這之后,runtime 的那些方法(動態(tài)添加 Class、swizzle 等等才能生效)。

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

如果程序剛剛被運行過,那么程序的代碼會被dyld緩存,因此即使殺掉進程再次重啟加載時間也會相對快一點,如果長時間沒有啟動或者當前dyld的緩存已經(jīng)被其他應用占據(jù),那么這次啟動所花費的時間就要長一點,這就分別是熱啟動和冷啟動的概念,如下圖所示:

main()之前的加載時間如何衡量

那么問題就來了,那怎么衡量main()之前也就是time1的耗時呢,蘋果官方提供了一種方法,那就是在真機調(diào)試的時候勾選dyld_PRINT_STATISTICS選項。

會得到如下形式的輸出:

由此可見對于系統(tǒng)級別的動態(tài)鏈接庫,因為蘋果做了優(yōu)化,所以耗時并不多,在這個awesome的例子中,自身App中的代碼占用了整體時間的94.2%我們應用中一次典型的Log如下:

由此可見,最多的用時還是在image加載和OC類的初始化,共占用總時長的79.3%,精簡framework的引入和OC類有優(yōu)化的空間。

總結(jié)一下:對于main()調(diào)用之前的耗時我們可以優(yōu)化的點有:

減少不必要的framework,因為動態(tài)鏈接比較耗時check framework應當設為optional和required,如果該framework在當前App支持的所有iOS系統(tǒng)版本都存在,那么就設為required,否則就設為optional,因為optional會有些額外的檢查合并或者刪減一些OC類,關(guān)于清理項目中沒用到的類,使用工具AppCode代碼檢查功能,查到當前項目中沒有用到的類如下:

刪減一些無用的靜態(tài)變量

刪減沒有被調(diào)用到或者已經(jīng)廢棄的方法

方法見:http://stackoverflow.com/questions/35233564/how-to-find-unused-code-in-xcode-7https://developer.Apple.com/library/ios/documentation/ToolsLanguages/Conceptual/Xcode_Overview/CheckingCodeCoverage.html

將不必須在+load方法中做的事情延遲到+initialize中

盡量不要用C++虛函數(shù)(創(chuàng)建虛函數(shù)表有開銷)

main()調(diào)用之后的加載時間

在main()被調(diào)用之后,App的主要工作就是初始化必要的服務,顯示首頁內(nèi)容等。而我們的優(yōu)化也是圍繞如何能夠快速展現(xiàn)首頁來開展。App通常在AppDelegate類中的- (BOOL)Application:(UIApplication )Application didFinishLaunchingWithOptions:(NSDictionary )launchOptions方法中創(chuàng)建首頁需要展示的view,然后在當前runloop的末尾,主動調(diào)用CA::Transaction::commit完成視圖的渲染。而視圖的渲染主要涉及三個階段:

準備階段 這里主要是圖片的解碼布局階段 首頁所有UIView的- (void)layoutSubViews()運行繪制階段 首頁所有UIView的- (void)drawRect:(CGRect)rect運行再加上啟動之后必要服務的啟動、必要數(shù)據(jù)的創(chuàng)建和讀取,這些就是我們可以嘗試優(yōu)化的地方

因此,對于main()函數(shù)調(diào)用之前我們可以優(yōu)化的點有:

不使用xib,直接視用代碼加載首頁視圖NSUserDefaults實際上是在Library文件夾下會生產(chǎn)一個plist文件,如果文件太大的話一次能讀取到內(nèi)存中可能很耗時,這個影響需要評估,如果耗時很大的話需要拆分(需考慮老版本覆蓋安裝兼容問題)每次用NSLog方式打印會隱式的創(chuàng)建一個Calendar,因此需要刪減啟動時各業(yè)務方打的log,或者僅僅針對內(nèi)測版輸出log梳理應用啟動時發(fā)送的所有網(wǎng)絡請求,是否可以統(tǒng)一在異步線程請求

實測數(shù)據(jù)

建立了一個空的HelloWorld工程,只加入了pods中的代碼,不包含主端的業(yè)務邏輯代碼,一次典型的冷啟動基本接近2s iphone6 iOS9.3.5系統(tǒng)測試主要時間在加載動態(tài)庫,類/方法的初始化還有符號地址綁定階段。

一次典型的熱啟動數(shù)據(jù)如下:可以看到因為系統(tǒng)做了緩存方面的優(yōu)化,比冷啟動快了500ms加上頭條主端業(yè)務邏輯代碼之后一次典型的熱啟動耗時2.1s。

以上用時均為main()之前的加載耗時。

main函數(shù)之后加載時間優(yōu)化記錄

NSUserDefaults是否是瓶頸

蘋果官方文檔提到NSUserDefaults加載的時候是整個plist配置文件全部load到內(nèi)存中,目前頭條主端當中NSUserDefaults存儲了200多項緩存數(shù)據(jù),因此懷疑可能拖慢啟動速度,但是測試結(jié)果顯示并不會。通過符號斷點+[NSUserDefaults standardUserDefaults]確定最早一次的+load()從執(zhí)行到結(jié)束耗時1.8ms,可見NSUserDefaults的初始化僅耗時1.8ms,并不是啟動耗時的瓶頸。

如何找到拖慢啟動應用時長的瓶頸

為了找到瓶頸,我們在啟動之后的didFinishLauhcning方法開始執(zhí)行到首頁列表頁的NewsListViewController的viewDidAppear方法,幾乎每個可能比較耗時的流程進行拆分和統(tǒng)計,得到統(tǒng)計數(shù)據(jù)之后發(fā)現(xiàn):主要耗時在首頁UI構(gòu)造和渲染(storyboard加載,tabBar/topBar渲染,開屏廣告加載/cell注冊/日志模塊初始化這幾個步驟)。

具體優(yōu)化點

因此,針對于今日頭條這個App我們可以優(yōu)化的點如下:

純代碼方式而不是storyboard加載首頁UI。對didFinishLaunching里的函數(shù)考慮能否挖掘可以延遲加載或者懶加載,需要與各個業(yè)務方pm和rd共同check 對于一些已經(jīng)下線的業(yè)務,刪減冗余代碼。對于一些與UI展示無關(guān)的業(yè)務,如微博認證過期檢查、圖片最大緩存空間設置等做延遲加載對實現(xiàn)了+load()方法的類進行分析,盡量將load里的代碼延后調(diào)用。上面統(tǒng)計數(shù)據(jù)顯示展示feed的導航控制器頁面(NewsListViewController)比較耗時,對于viewDidLoad以及viewWillAppear方法中盡量去嘗試少做,晚做,不做。

優(yōu)化結(jié)果

之前曾經(jīng)有一位同事已經(jīng)做了一定的優(yōu)化,比如啟動之后展示閃屏廣告圖的同時初始化首頁的列表頁,當廣告展示完成之后列表頁也就渲染完成了。經(jīng)過這一次優(yōu)化之后的main()之后的啟動總時長通過上線之后收集數(shù)據(jù)的驗證達到了預期的效果。

應用啟動時間,直接影響用戶對一款應用的判斷和使用體驗。頭條主app本身就包含非常多并且復雜度高的業(yè)務模塊(如新聞、視頻等),也接入了很多第三方的插件,這勢必會拖慢應用的啟動時間,本著精益求精的態(tài)度和對用戶體驗的追求,我們希望在業(yè)務擴張的同時最大程度的優(yōu)化啟動時間。

技術(shù)調(diào)研

先說結(jié)論,t(App總啟動時間) = t1(main()之前的加載時間) + t2(main()之后的加載時間)。t1 = 系統(tǒng)dylib(動態(tài)鏈接庫)和自身App可執(zhí)行文件的加載;t2 = main方法執(zhí)行之后到AppDelegate類中的- (BOOL)Application:(UIApplication *)Application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions方法執(zhí)行結(jié)束前這段時間,主要是構(gòu)建第一個界面,并完成渲染展示。

main()調(diào)用之前的加載過程

App開始啟動后, 系統(tǒng)首先加載可執(zhí)行文件(自身App的所有.o文件的集合),然后加載動態(tài)鏈接庫dyld,dyld是一個專門用來加載動態(tài)鏈接庫的庫。 執(zhí)行從dyld開始,dyld從可執(zhí)行文件的依賴開始, 遞歸加載所有的依賴動態(tài)鏈接庫。動態(tài)鏈接庫包括:iOS 中用到的所有系統(tǒng) framework,加載OC runtime方法的libobjc,系統(tǒng)級別的libSystem,例如libdispatch(GCD)和libsystem_blocks (Block)。

其實無論對于系統(tǒng)的動態(tài)鏈接庫還是對于App本身的可執(zhí)行文件而言,他們都算是image(鏡像),而每個App都是以image(鏡像)為單位進行加載的,那么image究竟包括哪些呢?

什么是image

1.executable可執(zhí)行文件 比如.o文件。2.dylib 動態(tài)鏈接庫 framework就是動態(tài)鏈接庫和相應資源包含在一起的一個文件夾結(jié)構(gòu)。3.bundle 資源文件 只能用dlopen加載,不推薦使用這種方式加載。

除了我們App本身的可行性文件,系統(tǒng)中所有的framework比如UIKit、Foundation等都是以動態(tài)鏈接庫的方式集成進App中的。

系統(tǒng)使用動態(tài)鏈接有幾點好處:

代碼共用:很多程序都動態(tài)鏈接了這些 lib,但它們在內(nèi)存和磁盤中中只有一份。易于維護:由于被依賴的 lib 是程序執(zhí)行時才鏈接的,所以這些 lib 很容易做更新,比如libSystem.dylib 是 libSystem.B.dylib 的替身,哪天想升級直接換成libSystem.C.dylib 然后再替換替身就行了。減少可執(zhí)行文件體積:相比靜態(tài)鏈接,動態(tài)鏈接在編譯時不需要打進去,所以可執(zhí)行文件的體積要小很多。

如上圖所示,不同進程之間共用系統(tǒng)dylib的_TEXT區(qū),但是各自維護對應的_DATA區(qū)。

所有動態(tài)鏈接庫和我們App中的靜態(tài)庫.a和所有類文件編譯后的.o文件最終都是由dyld(the dynamic link editor),Apple的動態(tài)鏈接器來加載到內(nèi)存中。每個image都是由一個叫做ImageLoader的類來負責加載(一一對應),那么ImageLoader又是什么呢?

什么是ImageLoader

image 表示一個二進制文件(可執(zhí)行文件或 so 文件),里面是被編譯過的符號、代碼等,所以 ImageLoader 作用是將這些文件加載進內(nèi)存,且每一個文件對應一個ImageLoader實例來負責加載。兩步走:在程序運行時它先將動態(tài)鏈接的 image 遞歸加載 (也就是上面測試棧中一串的遞歸調(diào)用的時刻)。再從可執(zhí)行文件 image 遞歸加載所有符號。

當然所有這些都發(fā)生在我們真正的main函數(shù)執(zhí)行前。

動態(tài)鏈接庫加載的具體流程

動態(tài)鏈接庫的加載步驟具體分為5步:

load dylibs image 讀取庫鏡像文件Rebase imageBind imageObjc setupinitializers

load dylibs image

在每個動態(tài)庫的加載過程中, dyld需要:

分析所依賴的動態(tài)庫找到動態(tài)庫的mach-o文件打開文件驗證文件在系統(tǒng)核心注冊文件簽名對動態(tài)庫的每一個segment調(diào)用mmap()

通常的,一個App需要加載100到400個dylibs, 但是其中的系統(tǒng)庫被優(yōu)化,可以很快的加載。針對這一步驟的優(yōu)化有:

減少非系統(tǒng)庫的依賴合并非系統(tǒng)庫使用靜態(tài)資源,比如把代碼加入主程序

rebase/bind

由于ASLR(address space layout randomization)的存在,可執(zhí)行文件和動態(tài)鏈接庫在虛擬內(nèi)存中的加載地址每次啟動都不固定,所以需要這2步來修復鏡像中的資源指針,來指向正確的地址。rebase修復的是指向當前鏡像內(nèi)部的資源指針; 而bind指向的是鏡像外部的資源指針。rebase步驟先進行,需要把鏡像讀入內(nèi)存,并以page為單位進行加密驗證,保證不會被篡改,所以這一步的瓶頸在IO。bind在其后進行,由于要查詢符號表,來指向跨鏡像的資源,加上在rebase階段,鏡像已被讀入和加密驗證,所以這一步的瓶頸在于CPU計算。通過命令行可以查看相關(guān)的資源指針:

xcrun dyldinfo -rebase -bind -lazy_bind myApp.App/myApp

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

減少Objc類數(shù)量, 減少selector數(shù)量減少C++虛函數(shù)數(shù)量轉(zhuǎn)而使用swift stuct(其實本質(zhì)上就是為了減少符號的數(shù)量)

Objc setup

這一步主要工作是:

注冊Objc類 (class registration)把category的定義插入方法列表 (category registration)保證每一個selector唯一 (selctor uniquing)

由于之前2步驟的優(yōu)化,這一步實際上沒有什么可做的。

initializers

以上三步屬于靜態(tài)調(diào)整(fix-up),都是在修改__DATA segment中的內(nèi)容,而這里則開始動態(tài)調(diào)整,開始在堆和堆棧中寫入內(nèi)容。在這里的工作有:

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

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

上圖是在自定義的類XXViewController的+load方法斷點的調(diào)用堆棧,清楚的看到整個調(diào)用棧和順序:

dyld 開始將程序二進制文件初始化交由 ImageLoader 讀取 image,其中包含了我們的類、方法等各種符號由于 runtime 向 dyld 綁定了回調(diào),當 image 加載到內(nèi)存后,dyld 會通知 runtime 進行處理runtime 接手后調(diào)用 map_images 做解析和處理,接下來 load_images 中調(diào)用 call_load_methods 方法,遍歷所有加載進來的 Class,按繼承層級依次調(diào)用 Class 的 +load 方法和其 Category 的 +load 方法

至此,可執(zhí)行文件中和動態(tài)庫所有的符號(Class,Protocol,Selector,IMP,…)都已經(jīng)按格式成功加載到內(nèi)存中,被 runtime 所管理,再這之后,runtime 的那些方法(動態(tài)添加 Class、swizzle 等等才能生效)。

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

如果程序剛剛被運行過,那么程序的代碼會被dyld緩存,因此即使殺掉進程再次重啟加載時間也會相對快一點,如果長時間沒有啟動或者當前dyld的緩存已經(jīng)被其他應用占據(jù),那么這次啟動所花費的時間就要長一點,這就分別是熱啟動和冷啟動的概念,如下圖所示:

main()之前的加載時間如何衡量

那么問題就來了,那怎么衡量main()之前也就是time1的耗時呢,蘋果官方提供了一種方法,那就是在真機調(diào)試的時候勾選dyld_PRINT_STATISTICS選項。

會得到如下形式的輸出:

由此可見對于系統(tǒng)級別的動態(tài)鏈接庫,因為蘋果做了優(yōu)化,所以耗時并不多,在這個awesome的例子中,自身App中的代碼占用了整體時間的94.2%我們應用中一次典型的Log如下:

由此可見,最多的用時還是在image加載和OC類的初始化,共占用總時長的79.3%,精簡framework的引入和OC類有優(yōu)化的空間。

總結(jié)一下:對于main()調(diào)用之前的耗時我們可以優(yōu)化的點有:

減少不必要的framework,因為動態(tài)鏈接比較耗時check framework應當設為optional和required,如果該framework在當前App支持的所有iOS系統(tǒng)版本都存在,那么就設為required,否則就設為optional,因為optional會有些額外的檢查合并或者刪減一些OC類,關(guān)于清理項目中沒用到的類,使用工具AppCode代碼檢查功能,查到當前項目中沒有用到的類如下:

刪減一些無用的靜態(tài)變量

刪減沒有被調(diào)用到或者已經(jīng)廢棄的方法

方法見:http://stackoverflow.com/questions/35233564/how-to-find-unused-code-in-xcode-7https://developer.Apple.com/library/ios/documentation/ToolsLanguages/Conceptual/Xcode_Overview/CheckingCodeCoverage.html

將不必須在+load方法中做的事情延遲到+initialize中

盡量不要用C++虛函數(shù)(創(chuàng)建虛函數(shù)表有開銷)

main()調(diào)用之后的加載時間

在main()被調(diào)用之后,App的主要工作就是初始化必要的服務,顯示首頁內(nèi)容等。而我們的優(yōu)化也是圍繞如何能夠快速展現(xiàn)首頁來開展。App通常在AppDelegate類中的- (BOOL)Application:(UIApplication )Application didFinishLaunchingWithOptions:(NSDictionary )launchOptions方法中創(chuàng)建首頁需要展示的view,然后在當前runloop的末尾,主動調(diào)用CA::Transaction::commit完成視圖的渲染。而視圖的渲染主要涉及三個階段:

準備階段 這里主要是圖片的解碼布局階段 首頁所有UIView的- (void)layoutSubViews()運行繪制階段 首頁所有UIView的- (void)drawRect:(CGRect)rect運行再加上啟動之后必要服務的啟動、必要數(shù)據(jù)的創(chuàng)建和讀取,這些就是我們可以嘗試優(yōu)化的地方

因此,對于main()函數(shù)調(diào)用之前我們可以優(yōu)化的點有:

不使用xib,直接視用代碼加載首頁視圖NSUserDefaults實際上是在Library文件夾下會生產(chǎn)一個plist文件,如果文件太大的話一次能讀取到內(nèi)存中可能很耗時,這個影響需要評估,如果耗時很大的話需要拆分(需考慮老版本覆蓋安裝兼容問題)每次用NSLog方式打印會隱式的創(chuàng)建一個Calendar,因此需要刪減啟動時各業(yè)務方打的log,或者僅僅針對內(nèi)測版輸出log梳理應用啟動時發(fā)送的所有網(wǎng)絡請求,是否可以統(tǒng)一在異步線程請求

實測數(shù)據(jù)

建立了一個空的HelloWorld工程,只加入了pods中的代碼,不包含主端的業(yè)務邏輯代碼,一次典型的冷啟動基本接近2s iPhone6 iOS9.3.5系統(tǒng)測試主要時間在加載動態(tài)庫,類/方法的初始化還有符號地址綁定階段。

一次典型的熱啟動數(shù)據(jù)如下:可以看到因為系統(tǒng)做了緩存方面的優(yōu)化,比冷啟動快了500ms加上頭條主端業(yè)務邏輯代碼之后一次典型的熱啟動耗時2.1s。

以上用時均為main()之前的加載耗時。

main函數(shù)之后加載時間優(yōu)化記錄

NSUserDefaults是否是瓶頸

蘋果官方文檔提到NSUserDefaults加載的時候是整個plist配置文件全部load到內(nèi)存中,目前頭條主端當中NSUserDefaults存儲了200多項緩存數(shù)據(jù),因此懷疑可能拖慢啟動速度,但是測試結(jié)果顯示并不會。通過符號斷點+[NSUserDefaults standardUserDefaults]確定最早一次的+load()從執(zhí)行到結(jié)束耗時1.8ms,可見NSUserDefaults的初始化僅耗時1.8ms,并不是啟動耗時的瓶頸。

如何找到拖慢啟動應用時長的瓶頸

為了找到瓶頸,我們在啟動之后的didFinishLauhcning方法開始執(zhí)行到首頁列表頁的NewsListViewController的viewDidAppear方法,幾乎每個可能比較耗時的流程進行拆分和統(tǒng)計,得到統(tǒng)計數(shù)據(jù)之后發(fā)現(xiàn):主要耗時在首頁UI構(gòu)造和渲染(storyboard加載,tabBar/topBar渲染,開屏廣告加載/cell注冊/日志模塊初始化這幾個步驟)。

具體優(yōu)化點

因此,針對于今日頭條這個App我們可以優(yōu)化的點如下:

純代碼方式而不是storyboard加載首頁UI。對didFinishLaunching里的函數(shù)考慮能否挖掘可以延遲加載或者懶加載,需要與各個業(yè)務方pm和rd共同check 對于一些已經(jīng)下線的業(yè)務,刪減冗余代碼。對于一些與UI展示無關(guān)的業(yè)務,如微博認證過期檢查、圖片最大緩存空間設置等做延遲加載對實現(xiàn)了+load()方法的類進行分析,盡量將load里的代碼延后調(diào)用。上面統(tǒng)計數(shù)據(jù)顯示展示feed的導航控制器頁面(NewsListViewController)比較耗時,對于viewDidLoad以及viewWillAppear方法中盡量去嘗試少做,晚做,不做。

優(yōu)化結(jié)果

之前曾經(jīng)有一位同事已經(jīng)做了一定的優(yōu)化,比如啟動之后展示閃屏廣告圖的同時初始化首頁的列表頁,當廣告展示完成之后列表頁也就渲染完成了。經(jīng)過這一次優(yōu)化之后的main()之后的啟動總時長通過上線之后收集數(shù)據(jù)的驗證達到了預期的效果。

原文地址:http://techblog.toutiao.com/archives/2017/01/iosspeed/?ref=myread


發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 娱乐| 清新县| 贡嘎县| 贡觉县| 应城市| 江北区| 靖宇县| 禹州市| 金溪县| 平顶山市| 原阳县| 蓝田县| 昌吉市| 尉氏县| 建瓯市| 玉环县| 通化市| 姚安县| 安多县| 酉阳| 江永县| 汤原县| 平塘县| 堆龙德庆县| 项城市| 和田市| 乌恰县| 图们市| 淮阳县| 襄城县| 武邑县| 建平县| 永定县| 密山市| 朝阳市| 乃东县| 麟游县| 庄浪县| 西乡县| 乐至县| 聂拉木县|