iOS启动时间优化(二)
2020-05-12 本文已影响0人
ZAREMYDREAM
前言
之前 启动优化(一)说了二进制重排的原理,今天具体说下实现。
查看链接顺序
Xcode编译过程,会将.m编译成.o文件,然后链接器ld会把.o按Complie Sources中文件的顺序链接起来,同时链接器ld提供了一个order_file参数用于修改链接顺序。
ld的链接顺序可在 /Users/用户名/Library/Developer/Xcode/DerivedData/项目名-xxx/Build/Intermediates.noindex/项目名.build/Debug-iphoneos/项目名.build/ 中有个项目名-LinkMap的txt文件查看,可以找到这段:
# Symbols:
# Address Size File Name
0x1000058A4 0x000000C0 [ 3] _main
0x100005964 0x0000006C [ 2] -[ViewController viewDidLoad]
0x1000059D0 0x0000005C [ 2] +[ViewController load]
0x100005A2C 0x0000005C [ 1] +[AppDelegate load]
0x100005A88 0x000000B0 [ 1] -[AppDelegate application:didFinishLaunchingWithOptions:]
0x100005B38 0x0000012C [ 1] -[AppDelegate application:configurationForConnectingSceneSession:options:]
0x100005C64 0x000000A8 [ 1] -[AppDelegate application:didDiscardSceneSessions:]
符号链接顺序为:
1、main函数
2、ViewController的viewDidLoad
3、ViewController的load
4、AppDelegate的load
……
可以发现与Complie Sources文件顺序一致,如果拖动其中文件顺序,重新编译,会发现LinkMap中的顺序也会跟着改变。
Order File
在项目的根目录中创建一个.order文件,如symbolOrder.order,输入
-[ViewController viewDidLoad]
+[AppDelegate load]
_main
+[ViewController load]
- 顺序根据需要自己添加,类方法用+[类名 类方法],实例方法用-[类名 实例方法],C函数用_方法名,具体可参考LinkMap中的写法。
然后在项目中Build Settings中的Order File输入./symbolOrder.order (根据自己创建的order文件名与路径填写),然后先common+shift+k清空下,然后再编译,打开LinkMap会发现符号顺序发生了改变.
获取启动时调用的方法
可以通过clang插桩的方式,获取相关符号,具体可以参考clang的文档
1、首先在Build Settings中的Other C Flags中添加
-fsanitize-coverage=func,trace-pc-guard
2、在ViewController中添加
void __sanitizer_cov_trace_pc_guard_init(uint32_t *start,
uint32_t *stop) {
static uint64_t N;
if (start == stop || *start) return;
for (uint32_t *x = start; x < stop; x++)
*x = ++N;
}
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
if (!*guard) return;
void *PC = __builtin_return_address(0); //拿到函数结束的地址
Dl_info info;
dladdr(PC, &info);
printf(" dli_fnam:%s\n dli_fbase:%p\n dli_sname:%s\n dli_saddr:%p", info.dli_fname, info.dli_fbase, info.dli_sname, info.dli_saddr);
}
3、导入头文件
#import <dlfcn.h>
运行文件,会发现执行每个方法都会调用新加的C方法,其中Dl_info中有个dli_sname就是正在调用的方法。
所以将启动阶段的函数存下来加入order文件中,即可满足我们重排需求
二进制重排实现
#import "ViewController.h"
#import <dlfcn.h>
#import <libkern/OSAtomic.h> //用原子队列进行存储,避免多线程的影响
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSMutableArray <NSString *> * symbolNames = [NSMutableArray array];
//遍历链表
while (YES) {
SYNode * node = OSAtomicDequeue(&symbolList, offsetof(SYNode, next));
if (node == NULL) {
break;
}
Dl_info info;
dladdr(node->pc, &info);
NSString * name = @(info.dli_sname);
//C方法添加_
BOOL isObjc = [name hasPrefix:@"+["] || [name hasPrefix:@"-["];
NSString * symbolName = isObjc ? name: [@"_" stringByAppendingString:name];
[symbolNames addObject:symbolName];
}
//取反
NSEnumerator * emt = [symbolNames reverseObjectEnumerator];
//去重
NSMutableArray<NSString *> *funcs = [NSMutableArray arrayWithCapacity:symbolNames.count];
NSString * name;
while (name = [emt nextObject]) {
if (![funcs containsObject:name]) {
[funcs addObject:name];
}
}
//因为touchbegin也会触发,需要删掉该方法
[funcs removeObject:[NSString stringWithFormat:@"%s",__FUNCTION__]];
//将数组变成字符串
NSString * funcStr = [funcs componentsJoinedByString:@"\n"];
NSString * filePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"symbolOrder.order"];
NSData * fileContents = [funcStr dataUsingEncoding:NSUTF8StringEncoding];
[[NSFileManager defaultManager] createFileAtPath:filePath contents:fileContents attributes:nil];
}
//存储有多少符号
void __sanitizer_cov_trace_pc_guard_init(uint32_t *start,
uint32_t *stop) {
static uint64_t N;
if (start == stop || *start) return;
for (uint32_t *x = start; x < stop; x++)
*x = ++N;
}
//原子队列
static OSQueueHead symbolList = OS_ATOMIC_QUEUE_INIT;
//定义符号链表结构体
typedef struct {
void *pc;
void *next;
}SYNode;
void __sanitizer_cov_trace_pc_guard(uint32_t *guard) {
if (!*guard) return;
void *PC = __builtin_return_address(0);
SYNode *node = malloc(sizeof(SYNode));
*node = (SYNode){PC,NULL};
//写入队列
OSAtomicEnqueue(&symbolList, node, offsetof(SYNode, next));
}
@end
然后执行一次项目,并点击触发touch方法,会在tmp中创建symbolClass文件
第一步
选中对应项目,点Download Container
第二步
然后就可以在包内容中的tmp文件找到对应的order文件,放入项目,修改order file即可实现二进制重排