咱們這裏按照第二種來去定義應用的啓動時間。node
因爲啓動動畫時長爲400ms,因此通常狀況下app的啓動最佳時間是400ms內
複製代碼
下面直接進入正題,其餘概念方面的東西很少作贅述。緩存
看了不少大佬的文章,抖音大佬主要講了二進制重排的大概原理和一些實現的思路,主要採用的是靜態掃描的方式去獲取函數符號,文章比較粗略。戳我👉🏻markdown
本記錄主要是經過此文章進行的實踐戳戳戳👉🏻這篇文章的做者包括概念性的東西都講述的很詳細。app
咱們不墨跡直接開始操做!函數
程序在編譯的時候,會有一個默認的符號順序的列表,這個列表包含了項目中全部類的函數的邏輯地址,內存分頁就是按照該內存地址進行排列。工具
二進制重排的最終目的其實就是改變這個列表的排序,讓咱們在程序啓動的時候須要調用的函數的邏輯地址順序排列起來。oop
首先咱們先經過Xcode的配置去獲取這個符號列表。學習
就是按照CompileSources類的順序來排列的。優化
好的,既然咱們知道了Xcode編譯產生的mapFile的原樣了。咱們接下來就是須要找到app在點擊運行的時候到app第一個頁面出來的時候調用了哪些函數,並將它從新排列在這個txt裏面,使它的邏輯地址連續,從而讓他們在同一個內存分頁中連續。動畫
咱們直接上代碼,原理能夠看以前提到的原文戳戳戳大佬原文👉🏻
二、第一點只是聲明,可是沒有實體文件,咱們須要手動建立一下,建立lb.order ,咱們cd到項目根目錄下命令好執行 touch lb.order 生成對應的lb.order文件。
三、build Setting 中直接搜索 Other C Flags 找到Apple Clang - Custom Compiler Flags 添加如下代碼配置
-fsanitize-coverage=func,trace-pc-guard
複製代碼
.h
@interface ClangInsertStaticPile : NSObject
+ (void)startWriteToFileOfClangInsertStaticPile;
@end
複製代碼
.m
#import "ClangInsertStaticPile.h"
#import <dlfcn.h>
#import <libkern/OSAtomic.h>
@implementation ClangInsertStaticPile
+ (void)load{
}
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)startWriteToFileOfClangInsertStaticPile{
NSMutableArray<NSString *> * symbolNames = [NSMutableArray array];
while (true) {
//offsetof 就是針對某個結構體找到某個屬性相對這個結構體的偏移量
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);
//將結果寫入到文件
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(@"文件寫入出錯");
}
}
//原子隊列
static OSQueueHead symboList = OS_ATOMIC_QUEUE_INIT;
//定義符號結構體
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};
//入隊
// offsetof 用在這裏是爲了入隊添加下一個節點找到 前一個節點next指針的位置
OSAtomicEnqueue(&symboList, node, offsetof(SymbolNode, next));
}
@end
複製代碼
選中項目下載包
下載下來的包右鍵顯示包內容
把這個lb.order的內容拷貝到項目目錄中的lb.order裏面就完成了。
clean 一下再跑一下項目,咱們再按最開始的方式去看一下 # Symbols的符號順序,發現咱們已經重排成功了!咱們對比一下:
再用system Trace看一下對比一下:
因爲個人項目工程並不大,而且該二進制重排方式僅僅只能優化本體項目的分頁內容,因此其實優化效果並不明顯,咱們的二進制重排其實並不完全。
經過配置工程,DYLD_PRINT_STATISTICS 能夠看到pre-maind的啓動時間
其實優化的方式多種多樣,咱們也能夠從動態庫入手,對於私有動態庫,能夠才用合併動態庫的方式進行優化等。對於類的初始化方法,咱們能夠少使用load方法,儘量使用initializer在類使用到的時候再進行初始化等等方式。
若是進行完全的二進制重排鬚要對第三方的framework也進行上述重排方式,一個一個framework進行操做會比較複雜且耗時,此次實踐就沒有作第三方的重排。靜態插樁二進制重排僅是其中一種方式,本文是經過大佬的文章進行實操作的一個記錄,也是一個學習過程的記錄。