一、二進(jìn)制重排介紹
1、App啟動
進(jìn)程如果能直接訪問物理內(nèi)存無疑是很不安全的,所以操作系統(tǒng)在物理內(nèi)存的上又建立了一層虛擬內(nèi)存。蘋果在這個基礎(chǔ)上還有 ASLR(Address Space Layout Randomization) 技術(shù)的保護(hù),不過不是這次的重點(diǎn)。
iOS系統(tǒng)中虛擬內(nèi)存到物理內(nèi)存的映射都是以頁為最小單位的。當(dāng)進(jìn)程訪問一個虛擬內(nèi)存Page而對應(yīng)的物理內(nèi)存卻不存在時,就會出現(xiàn)Page Fault缺頁中斷,然后加載這一頁。雖然本身這個處理速度是很快的,但是在一個App的啟動過程中可能出現(xiàn)上千(甚至更多)次Page Fault,這個時間積累起來會比較明顯了
另外,還有兩個重要的概念:冷啟動、熱啟動??赡苡行┩瑢W(xué)認(rèn)為殺掉再重啟App就是冷啟動了,其實(shí)是不對的。
- 冷啟動:程序完全退出,之間加載的分頁數(shù)據(jù)被其他進(jìn)程所使用覆蓋之后,或者重啟設(shè)備、第一次安裝,才算是冷啟動。
- 熱啟動:程序殺掉之后,馬上又重新啟動。這個時候相應(yīng)的物理內(nèi)存中仍然保留之前加載過的分頁數(shù)據(jù),可以進(jìn)行重用,不需要全部重新加載。所以熱啟動的速度比較快。
2、二進(jìn)制重排原理
編譯器在生成二進(jìn)制代碼的時候,默認(rèn)按照鏈接的Object File(.o)順序?qū)懳募?,按照Object File內(nèi)部的函數(shù)順序?qū)懞瘮?shù)。
靜態(tài)庫文件.a就是一組.o文件的ar包,可以用
ar -t查看.a包含的所有.o。
簡化問題:假設(shè)我們只有兩個page:page1/page2,其中綠色的method1和method5啟動時候需要調(diào)用,為了執(zhí)行對應(yīng)的代碼,系統(tǒng)必須進(jìn)行兩個Page Fault。
但如果我們把method1和method5排布到一起,那么只需要一個Page Fault即可,這就是二進(jìn)制文件重排的核心原理。

實(shí)際項(xiàng)目中的做法是將啟動時需要調(diào)用的函數(shù)放到一起 ( 比如 前10頁中 ) 以盡可能減少 page fault , 達(dá)到優(yōu)化目的 . 而這個做法就叫做 : 二進(jìn)制重排 。
注意:在iOS生產(chǎn)環(huán)境的app,在發(fā)生Page Fault進(jìn)行重新加載時,iOS系統(tǒng)還會對其做一次簽名驗(yàn)證,因此 iOS 生產(chǎn)環(huán)境的 Page Fault 比Debug環(huán)境下所產(chǎn)生的耗時更多
二、重排的order文件
1、文件順序
Build Phases 中 Compile Sources 列表順序決定了文件執(zhí)行的順序(可以調(diào)整)。如果不進(jìn)行重排,文件的順序決定了方法、函數(shù)的執(zhí)行順序。我們在 ViewController 和 AppDelegate 中加入以下代碼:
+ (void)load {
NSLog(@"%s", __FUNCTION__);
}
我們調(diào)整 Compile Sources 中這兩個類的順序,然后分別執(zhí)行對比??梢钥吹?,隨著 Compile Sources 中的文件順序的修改,+load 方法的執(zhí)行順序也發(fā)生了改變。

2、符號表順序
Link Map 是iOS編譯過程的中間產(chǎn)物,記錄了二進(jìn)制文件的布局,需要在Xcode的Build Settings 里開啟Write Link Map File,Link Map主要包含三部分:
-
Object Files生成二進(jìn)制用到的link單元的路徑和文件編號 -
Sections記錄Mach-O每個Segment/section的地址范圍 -
Symbols按順序記錄每個符號的地址范圍
1)Build Settings中修改Write Link Map File為YES
2)查找Link Map符號表txt文件
編譯后會生成一個Link Map符號表txt文件,選擇Product中的App,在Finder中打開,選擇Intermediates.noindex文件夾,找到LinkMap文件,這里是ZJHBinaryLaunchDemo-LinkMap-normal-x86_64.txt。。詳細(xì)路徑請看下圖。

3)查看Link Map符號表txt文件
打開文件之后來到第一部分的最后。我們可以看到這個順序和我們Compile Sources中的順序是一致的。接下來的部分:

可以看到,整體的順序和Compile Sources的中的順序是一樣的,并且方法是按照文件中方法的順序進(jìn)行鏈接的。ViewController中的方法添加完后,才是AppDelegate中的方法,以此類推。
-
Address? 表示文件中方法的地址。 -
Size表示方法的大小。 -
File表示在第幾個文件中。 -
Name表示方法名。
3、導(dǎo)入order文件
ld是Xcode使用的鏈接器,有一個參數(shù)order_file,我們可以通過在Build Settings -> Order File配置一個后綴為order的文件路徑.在這個order文件中,將所需要的符號按照順序?qū)懺诶锩妫陧?xiàng)目編譯時,會按照這個文件的順序進(jìn)行加載,以此來達(dá)到我們的優(yōu)化
來到工程根目錄 , 新建一個文件 touch ZJHBinaryLaunchDemo.order . 隨便挑選幾個啟動時就需要加載的方法 , 例如我這里選了以下幾個 .
+[AppDelegate load]
+[ViewController load]
_main
-[ViewController someMethod]
然后在Build Settings中找到Order File,填入./ZJHBinaryLaunchDemo.order。然后重新比納音,再次查看生成符號表txt文件。

可以看到Link Map中的最上面幾個方法和我們在ZJHBinaryLaunchDemo.order文件中設(shè)置的方法順序一致!
Xcode的連接器ld還忽略掉了不存在的方法 -[ViewController someMethod]。如果提供了link選項(xiàng) -order_file_statistics,會以warning的形式把這些沒找到的符號打印在日志里。
注意:有部分同學(xué)可能配置完運(yùn)行會發(fā)現(xiàn)報(bào)錯說
can't open這個order file. 是因?yàn)槲募袷降膯栴} . 不用使用mac自帶的文本編輯 . 使用命令工具touch創(chuàng)建即可 .
三、檢測啟動時方法
要真正的實(shí)現(xiàn)二進(jìn)制重排,我們需要拿到啟動時所有用到的方法、函數(shù)等符號,并保存其順序,然后寫入order文件,實(shí)現(xiàn)二進(jìn)制重排。這里我們使用Clang插樁的方式
1、Clang插樁原理
其實(shí)就是一個代碼覆蓋工具,更多信息可以查看官網(wǎng)。
1)首先 , 添加編譯設(shè)置
Build Settings中 Other C Flags添加配置
-fsanitize-coverage=trace-pc-guard
編譯的話會報(bào)以下錯誤,意思是找不到這兩個函數(shù)
Undefined symbol: ___sanitizer_cov_trace_pc_guard_init
Undefined symbol: ___sanitizer_cov_trace_pc_guard
2)添加 hook 代碼
我們把面的代碼,添加到 ViewController.m 中
#include <stdint.h>
#include <stdio.h>
#include <sanitizer/coverage_interface.h>
void __sanitizer_cov_trace_pc_guard_init(uint32_t *start, uint32_t *stop) {
static uint64_t N; // Counter for the guards.
if (start == stop || *start) return; // Initialize only once.
printf("INIT: %p %p\n", start, stop);
for (uint32_t *x = start; x < stop; x++)
*x = ++N; // Guards should start from 1.
}
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
if (!*guard) return; // Duplicate the guard check.
// void *PC = __builtin_return_address(0);
char PcDescr[1024];
// __sanitizer_symbolize_pc(PC, "%p %F %L", PcDescr, sizeof(PcDescr));
printf("guard: %p %x PC %s\n", guard, *guard, PcDescr);
}
3)運(yùn)行工程 , 查看打印
INIT: 0x1024153e0 0x102415420
guard: 0x1024153f8 7 PC
guard: 0x1024153ec 4 PC ?
guard: 0x102415414 e PC
guard: 0x102415418 f PC ?
guard: 0x102415414 e PC
guard: 0x102415414 e PC
guard: 0x1024153fc 8 PC $
guard: 0x102415414 e PC
guard: 0x102415414 e PC ?
guard: 0x102415414 e PC \300\202\3605?
代碼命名 INIT 后面打印的兩個指針地址叫 start 和 stop . 那么我們通過 lldb 來查看下從 start 到 stop 這個內(nèi)存地址里面所存儲的到底是啥 .
4)驗(yàn)證 start 到 stop 內(nèi)存地址存儲值
在viewDidLoad方法中添加斷點(diǎn),執(zhí)行項(xiàng)目。在lldb分別輸入 x start地址 和 x stop地址-0x4,讀取內(nèi)存地址。 x stop地址-0x4 是結(jié)束位置,按顯示是4位的,所以向前移動4位,打印出來的應(yīng)該就是最后一位。

發(fā)現(xiàn)存儲的是從 1 到 16(0x10) 這個序號 ,我們再新增兩個方法,再次運(yùn)行查看:
INIT: 0x102fb93f0 0x102fb9438
(lldb) x 0x102fb9438-0x4
0x102fb9434: 12 00 00 00 f0 92 0c 03 01 00 00 00 00 00 00 00 ................
0x102fb9444: 00 00 00 00 5f 33 fb 02 01 00 00 00 00 00 00 00 ...._3..........
(lldb)
發(fā)現(xiàn)從 0x10 變成了 0x12 . 也就是說存儲的 1 到 16 這個序號變成了 1 到 18 。那么我們得到一個猜想 , 這個內(nèi)存區(qū)間保存的就是工程所有符號的個數(shù) 。
5)驗(yàn)證guard調(diào)用次數(shù)
在 touchesBegan 方法中,打印語句,點(diǎn)擊屏幕。每點(diǎn)擊一次就會調(diào)用一次 guard :。而且 guard :是在前面調(diào)用的。
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"touchesBegan");
}
// 控制臺的輸出
guard: 0x1006053fc 4 PC ?
2021-04-21 13:29:51.936925+0800 ZJHBinaryLaunchDemo[13644:5077278] touchesBegan
在 touchesBegan 方法中,執(zhí)行調(diào)用test1方法,會發(fā)現(xiàn) guard :調(diào)用了兩次
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSLog(@"touchesBegan");
[self test1];
}
- (void)test1 {
NSLog(@"test1");
}
// 控制臺的輸出
guard: 0x102d893f8 3 PC ?
2021-04-21 13:31:57.152720+0800 ZJHBinaryLaunchDemo[13646:5077923] touchesBegan
guard: 0x102d893fc 4 PC d?\330?\201\226P\314\370\223\330??
2021-04-21 13:31:57.152915+0800 ZJHBinaryLaunchDemo[13646:5077923] test1
由此,發(fā)現(xiàn)我們實(shí)際調(diào)用幾個方法 , 就會打印幾次 guard :。
6)查看匯編代碼驗(yàn)證
我們在 touchesBegan:touches withEvent: 開頭設(shè)置一個斷點(diǎn),并開啟匯編顯示(菜單欄Debug → Debug Workflow → Always Show Disassembly)。

通過匯編我們發(fā)現(xiàn) , 在每個函數(shù)調(diào)用的第一句實(shí)際代碼 ( 棧平衡與寄存器數(shù)據(jù)準(zhǔn)備除外 ) , 被添加進(jìn)去了一個 bl 調(diào)用到 __sanitizer_cov_trace_pc_guard 這個函數(shù)中來 。而實(shí)際上這也是靜態(tài)插樁的原理和名稱由來 。
靜態(tài)插樁總結(jié):靜態(tài)插樁實(shí)際上是在編譯期就在每一個函數(shù)內(nèi)部二進(jìn)制源數(shù)據(jù)添加
hook代碼 ( 我們添加的__sanitizer_cov_trace_pc_guard函數(shù) ) 來實(shí)現(xiàn)全局的方法hook的效果 .
2、獲取所有函數(shù)符號
1)獲取原函數(shù)地址
我們在 __sanitizer_cov_trace_pc_guard 函數(shù)中的這一句代碼 :
void *PC = __builtin_return_address(0);
它的作用其實(shí)就是去讀取 x30 中所存儲的要返回時下一條指令的地址 . 所以他名稱叫做 __builtin_return_address . 換句話說 , 這個地址就是我當(dāng)前這個函數(shù)執(zhí)行完畢后 , 要返回到哪里去 。也就是說 , 我們現(xiàn)在可以在 __sanitizer_cov_trace_pc_guard 這個函數(shù)中 , 通過 __builtin_return_address 數(shù)拿到原函數(shù)調(diào)用 __sanitizer_cov_trace_pc_guard 這句匯編代碼的下一條指令的地址 。及上圖中,拿到-[ViewController touchesBegan:withEvent:] 地址
2)根據(jù)內(nèi)存地址獲取函數(shù)名稱
拿到了函數(shù)內(nèi)部一行代碼的地址 , 如何獲取函數(shù)名稱呢 ? 熟悉安全攻防 , 逆向的同學(xué)可能會清楚 . 我們?yōu)榱朔乐鼓承┨囟ǖ姆椒ū粍e人使用 fishhook hook 掉 , 會利用 dlopen 打開動態(tài)庫 , 拿到一個句柄 , 進(jìn)而拿到函數(shù)的內(nèi)存地址直接調(diào)用 .是不是跟我們這個流程有點(diǎn)相似 , 只是我們好像是反過來的 . 其實(shí)反過來也是可以的 .
與 dlopen 相同 , 在 dlfcn.h 中有一個方法如下 :
typedef struct dl_info {
const char *dli_fname; /* 所在文件 */
void *dli_fbase; /* 文件地址 */
const char *dli_sname; /* 符號名稱 */
void *dli_saddr; /* 函數(shù)起始地址 */
} Dl_info;
//這個函數(shù)能通過函數(shù)內(nèi)部地址找到函數(shù)符號
int dladdr(const void *, Dl_info *);
緊接著我們來實(shí)驗(yàn)一下 , 先導(dǎo)入頭文件#import <dlfcn.h> , 然后修改代碼如下 :
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
if (!*guard) return; // Duplicate the guard check.
void *PC = __builtin_return_address(0);
Dl_info info;
dladdr(PC, &info);
printf("fname=%s \nfbase=%p \nsname=%s\nsaddr=%p \n",info.dli_fname,info.dli_fbase,info.dli_sname,info.dli_saddr);
char PcDescr[1024];
printf("guard: %p %x PC %s\n", guard, *guard, PcDescr);
}
查看打印結(jié)果,可以看到我們要找的符號了 :

3、可能遇到的問題
1)多線程問題
項(xiàng)目各個方法肯定有可能會在不同的函數(shù)執(zhí)行 , 因此 __sanitizer_cov_trace_pc_guard 這個函數(shù)也有可能受多線程影響 , 所以你當(dāng)然不可能簡簡單單用一個數(shù)組來接收所有的符號就搞定了。考慮到這個方法會來特別多次 , 使用鎖會影響性能 , 這里使用蘋果底層的原子隊(duì)列 ( 底層實(shí)際上是個棧結(jié)構(gòu) , 利用隊(duì)列結(jié)構(gòu) + 原子性來保證順序 ) 來實(shí)現(xiàn) .
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
//遍歷出隊(duì)
while (true) {
//offsetof 就是針對某個結(jié)構(gòu)體找到某個屬性相對這個結(jié)構(gòu)體的偏移量
SymbolNode * node = OSAtomicDequeue(&symboList, offsetof(SymbolNode, next));
if (node == NULL) break;
Dl_info info;
dladdr(node->pc, &info);
printf("%s \n",info.dli_sname);
}
}
//原子隊(duì)列
static OSQueueHead symboList = OS_ATOMIC_QUEUE_INIT;
//定義符號結(jié)構(gòu)體
typedef struct{
void * pc;
void * next;
}SymbolNode;
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
if (!*guard) return; // Duplicate the guard check.
void *PC = __builtin_return_address(0);
SymbolNode * node = malloc(sizeof(SymbolNode));
*node = (SymbolNode){PC,NULL};
//入隊(duì)
// offsetof 用在這里是為了入隊(duì)添加下一個節(jié)點(diǎn)找到 前一個節(jié)點(diǎn)next指針的位置
OSAtomicEnqueue(&symboList, node, offsetof(SymbolNode, next));
}
但此方法會導(dǎo)致死循環(huán)的問題
2)死循環(huán)問題
通過匯編會查看到 一個帶有 while 循環(huán)的方法 , 會被靜態(tài)加入多次 __sanitizer_cov_trace_pc_guard 調(diào)用 , 導(dǎo)致死循環(huán).
Other C Flags 修改為如下 :
-fsanitize-coverage=func,trace-pc-guard
代表進(jìn)針對 func 進(jìn)行 hook . 再次運(yùn)行就可以了。
3) load 方法不打印問題
load 方法時 , __sanitizer_cov_trace_pc_guard 函數(shù)的參數(shù) guard 是 0。上述打印并沒有發(fā)現(xiàn) load .
解決 : 屏蔽掉 __sanitizer_cov_trace_pc_guard 函數(shù)中的
if (!*guard) return;
效果如下
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
// if (!*guard) return; // Duplicate the guard check.
void *PC = __builtin_return_address(0);
SymbolNode * node = malloc(sizeof(SymbolNode));
*node = (SymbolNode){PC,NULL};
//入隊(duì)
// offsetof 用在這里是為了入隊(duì)添加下一個節(jié)點(diǎn)找到 前一個節(jié)點(diǎn)next指針的位置
OSAtomicEnqueue(&symboList, node, offsetof(SymbolNode, next));
}
// 打印結(jié)果
INIT: 0x104d8d400 0x104d8d444
-[ViewController touchesBegan:withEvent:]
-[SceneDelegate sceneDidBecomeActive:]
-[SceneDelegate sceneWillEnterForeground:]
-[ViewController viewDidLoad]
-[SceneDelegate window]
-[SceneDelegate window]
-[SceneDelegate window]
-[SceneDelegate scene:willConnectToSession:options:]
-[SceneDelegate window]
-[SceneDelegate window]
-[SceneDelegate setWindow:]
-[SceneDelegate window]
-[AppDelegate application:didFinishLaunchingWithOptions:]
main
+[AppDelegate load]
+[ViewController load]
這樣的話,load 方法就有了。這里也為我們提供了一點(diǎn)啟示:如果我們希望從某個函數(shù)之后/之前開始優(yōu)化 , 通過一個全局靜態(tài)變量 , 在特定的時機(jī)修改其值 , 在 __sanitizer_cov_trace_pc_guard 這個函數(shù)中做好對應(yīng)的處理即可 .
4、符號信息寫入文件
完整代碼如下 :
#import "ViewController.h"
#import <dlfcn.h>
#import <libkern/OSAtomic.h>
@interface ViewController ()
@end
@implementation ViewController
+ (void)load{
}
- (void)viewDidLoad {
[super viewDidLoad];
testCFunc();
[self testOCFunc];
}
- (void)testOCFunc{
NSLog(@"oc函數(shù)");
}
void testCFunc(){
LBBlock();
}
void(^LBBlock)(void) = ^(void){
NSLog(@"block");
};
void __sanitizer_cov_trace_pc_guard_init(uint32_t *start,
uint32_t *stop) {
static uint64_t N; // Counter for the guards.
if (start == stop || *start) return; // Initialize only once.
printf("INIT: %p %p\n", start, stop);
for (uint32_t *x = start; x < stop; x++)
*x = ++N; // Guards should start from 1.
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
NSMutableArray<NSString *> * symbolNames = [NSMutableArray array];
while (true) {
//offsetof 就是針對某個結(jié)構(gòu)體找到某個屬性相對這個結(jié)構(gòu)體的偏移量
SymbolNode * node = OSAtomicDequeue(&symboList, offsetof(SymbolNode, next));
if (node == NULL) break;
Dl_info info;
dladdr(node->pc, &info);
NSString * name = @(info.dli_sname);
// 添加 _
BOOL isObjc = [name hasPrefix:@"+["] || [name hasPrefix:@"-["];
NSString * symbolName = isObjc ? name : [@"_" stringByAppendingString:name];
//去重
if (![symbolNames containsObject:symbolName]) {
[symbolNames addObject:symbolName];
}
}
//取反
NSArray * symbolAry = [[symbolNames reverseObjectEnumerator] allObjects];
NSLog(@"%@",symbolAry);
//將結(jié)果寫入到文件
NSString * funcString = [symbolAry componentsJoinedByString:@"\n"];
NSString * filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"lb.order"];
NSData * fileContents = [funcString dataUsingEncoding:NSUTF8StringEncoding];
BOOL result = [[NSFileManager defaultManager] createFileAtPath:filePath contents:fileContents attributes:nil];
if (result) {
NSLog(@"%@",filePath);
}else{
NSLog(@"文件寫入出錯");
}
}
//原子隊(duì)列
static OSQueueHead symboList = OS_ATOMIC_QUEUE_INIT;
//定義符號結(jié)構(gòu)體
typedef struct{
void * pc;
void * next;
}SymbolNode;
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
//if (!*guard) return; // Duplicate the guard check.
void *PC = __builtin_return_address(0);
SymbolNode * node = malloc(sizeof(SymbolNode));
*node = (SymbolNode){PC,NULL};
//入隊(duì)
// offsetof 用在這里是為了入隊(duì)添加下一個節(jié)點(diǎn)找到 前一個節(jié)點(diǎn)next指針的位置
OSAtomicEnqueue(&symboList, node, offsetof(SymbolNode, next));
}
@end
文件寫入到了 tmp 路徑下 , 運(yùn)行 , 打開手機(jī)下載查看 :

5、swift 工程 / 混編工程問題
通過如上方式適合純 OC 工程獲取符號方式 .由于 swift 的編譯器前端是自己的 swift 編譯前端程序 , 因此配置稍有不同 .
搜索 Other Swift Flags , 添加兩條配置即可 :
-sanitize-coverage=func-sanitize=undefined
swift 類通過上述方法同樣可以獲取符號 .
四、驗(yàn)證
1、打印啟動時間
在系統(tǒng)執(zhí)行應(yīng)用程序的main函數(shù)并調(diào)用應(yīng)用程序委托函數(shù)(applicationWillFinishLaunching)之前,會發(fā)生很多事情。我們可以通過添加環(huán)境變量可以打印處APP的啟動分析(Edit scheme -> Run -> Argument).
DYLD_PRINT_STATISTICS設(shè)置為1(dyld_print_statistics)。如果需要更詳細(xì)的信息,那就將DYLD_PRINT_STATISTICS_DETAILS設(shè)置為1
運(yùn)行一下,對比控制臺的輸出(因筆者是在demo驗(yàn)證,時間優(yōu)化的效果不明顯,這里就不做對比展示了):
Total pre-main time: 97.73 milliseconds (100.0%)
dylib loading time: 28.02 milliseconds (28.6%)
rebase/binding time: 21.70 milliseconds (22.2%)
ObjC setup time: 5.16 milliseconds (5.2%)
initializer time: 42.85 milliseconds (43.8%)
slowest intializers :
libSystem.B.dylib : 6.26 milliseconds (6.4%)
libBacktraceRecording.dylib : 9.88 milliseconds (10.1%)
libMainThreadChecker.dylib : 22.10 milliseconds (22.6%)
ZJHBinaryLaunchDemo : 2.81 milliseconds (2.8%)
2、System Trace
日常開發(fā)中性能分析是用最多的工具無疑是Time Profiler,但Time Profiler是基于采樣的,并且只能統(tǒng)計(jì)線程實(shí)際在運(yùn)行的時間,而發(fā)生Page Fault的時候線程是被blocked,所以我們需要用一個不常用但功能卻很強(qiáng)大的工具:System Trace。
選中主線程,在VM Activity中的File Backed Page In次數(shù)就是Page Fault次數(shù),并且雙擊還能按時序看到引起Page Fault的堆棧:

五、CocoaPods相關(guān)
對于 cocoapod 工程引入的庫 , 由于針對不同的 target . 那么我們在主程序中的 target 添加的編譯設(shè)置 Write Link Map File , -fsanitize-coverage=func,trace-pc-guard 以及 order file 等設(shè)置肯定是不會生效的 . 解決方法就是針對需要的 target 去做對應(yīng)的設(shè)置即可 .
對于直接手動導(dǎo)入到工程里的 sdk , 不管是 靜態(tài)庫 .a 還是 動態(tài)庫 , 默認(rèn)主工程的設(shè)置就可以了 , 是可以拿到符號的 .
更多CocoaPods相關(guān)問題,可參考這篇文章:https://juejin.cn/post/6844904192193085448
參考鏈接:
iOS 啟動優(yōu)化之Clang插樁實(shí)現(xiàn)二進(jìn)制重排
我是如何讓微博綠洲的啟動速度提升30%的
懶人版二進(jìn)制重排
抖音研發(fā)實(shí)踐:基于二進(jìn)制文件重排的解決方案 APP啟動速度提升超15%
懶人版二進(jìn)制重排
手淘架構(gòu)組最新實(shí)踐 | iOS基于靜態(tài)庫插樁的?進(jìn)制重排啟動優(yōu)化