原理
App包數(shù)據(jù)并不是在啟動(dòng)的時(shí)候一次全部加載到內(nèi)存中的,而是類(lèi)似于懶加載的方式,以每頁(yè)16KB的數(shù)據(jù)進(jìn)行分頁(yè)加載。啟動(dòng)的時(shí)刻,也是缺頁(yè)加載次數(shù)最多的時(shí)刻。因?yàn)閱?dòng)用到的類(lèi)和方法,并不是全部集中在某幾頁(yè)數(shù)據(jù)中,而是根據(jù)編譯順序,分散到不確定的分頁(yè)數(shù)據(jù)中。我們做二進(jìn)制重拍,也就是要讓啟動(dòng)用到的函數(shù),集中到最前邊的幾張表中,減少分頁(yè)加載的次數(shù),也就節(jié)約了啟動(dòng)時(shí)間。
那么為什么減少分頁(yè)加載的次數(shù),可以節(jié)省啟動(dòng)時(shí)間呢?
這是因?yàn)?,每?yè)數(shù)據(jù)加載到內(nèi)存中,還需要進(jìn)行重綁定的過(guò)程,因?yàn)锳SLR(地址空間布局隨機(jī)化),每次啟動(dòng)后指針地址值并不是MachO中編譯后的地址,還需要加上這個(gè)隨機(jī)偏移地址,也就是rebase(重綁定)的過(guò)程。啟動(dòng)時(shí)刻加載的分頁(yè)越多,重綁定的地址也就越多,拖慢了應(yīng)用的啟動(dòng)時(shí)間。
查看排列順序
① 打開(kāi)編譯配置 Build Settings —> Write Link Map File —> 修改成 YES。

② 打開(kāi)編譯后生產(chǎn)的 .app 文件,在上兩層目錄找 Intermediates.noindex
Intermediates.noindex/xxx(項(xiàng)目根目錄名稱(chēng)).build/Debug-iphoneos/xxx(項(xiàng)目根目錄名稱(chēng)).build/xxx-LinkMap-normal-arm64.txt
這個(gè)txt就是默認(rèn)的排列順序,我們配置.order后,這個(gè)文件的排列順序會(huì)按照.order給定的順序編譯后排序。


配置.order?文件
打開(kāi) Build Setting,搜索 Order File 配置生成的.order文件路徑。
.order里主要寫(xiě)啟動(dòng)時(shí)調(diào)用的方法(獲取在后面會(huì)說(shuō))

?利用插樁獲取重排符號(hào)
注意的是:獲取到符號(hào)表后,以下配置我們就可以刪除掉了。
插樁獲取符號(hào)可以參考LLVM官網(wǎng)的介紹。
原理就是在每一個(gè)OC的方法、函數(shù)、block 的匯編代碼中,插入一條__sanitizer_cov_trace_pc_guard匯編指令,讀取pc寄存器的指令指針,回調(diào)到我們自己添加的C函數(shù)__sanitizer_cov_trace_pc_guard,然后根據(jù)指針恢復(fù)出OC的方法名、函數(shù)名、block。
配置pc寄存器跟蹤配置
Build Settings —> Other C Flags —> 添加?-fsanitize-coverage=trace-pc-guard

引入插樁函數(shù)在AppDelegate里面
#include <stdint.h>
#include <stdio.h>
#include <sanitizer/coverage_interface.h>
#import <dlfcn.h>
#import <libkern/OSAtomic.h>
/*
?- start:起始位置
?- stop:并不是最后一個(gè)符號(hào)的地址,而是整個(gè)符號(hào)表的最后一個(gè)地址,最后一個(gè)符號(hào)的地址=stop-4(因?yàn)槭菑母叩刂吠偷刂纷x取的,且stop是一個(gè)無(wú)符號(hào)int類(lèi)型,占4個(gè)字節(jié))。stop存儲(chǔ)的值是符號(hào)的
?*/
void __sanitizer_cov_trace_pc_guard_init(uint32_t *start,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? uint32_t*stop) {
? ? staticuint64_tN;
? ? if(start == stop || *start)return;
? ? printf("INIT: %p - %p\n", start, stop);
? ? for(uint32_t*x = start; x < stop; x++) {
? ? ? ? *x = ++N;
? ? }
}
/*
?可以全面hook方法、函數(shù)、以及block調(diào)用,用于捕捉符號(hào),是在多線程進(jìn)行的,這個(gè)方法中只存儲(chǔ)pc,以鏈表的形式
?- guard 是一個(gè)哨兵,告訴我們是第幾個(gè)被調(diào)用的
?*/
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
//? ? if (!*guard) return;//將load方法過(guò)濾掉了,所以需要注釋掉
? ? //獲取PC
? ? /*
?? ? - PC 當(dāng)前函數(shù)返回上一個(gè)調(diào)用的地址
?? ? - 0 當(dāng)前這個(gè)函數(shù)地址,即當(dāng)前函數(shù)的返回地址
?? ? - 1 當(dāng)前函數(shù)調(diào)用者的地址,即上一個(gè)函數(shù)的返回地址
? ? */
? ? void *PC = __builtin_return_address(0);
? ? Dl_infoinfo;
? ? dladdr(PC, &info);
? ? printf("%s \n", info.dli_sname);
}
然后把打印出來(lái)的方法寫(xiě)入到order文件