咱们这里按照第二种来去定义应用的启动时间。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进行操做会比较复杂且耗时,此次实践就没有作第三方的重排。静态插桩二进制重排仅是其中一种方式,本文是经过大佬的文章进行实操作的一个记录,也是一个学习过程的记录。