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]

获取启动时调用的方法

可以通过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即可实现二进制重排
上一篇下一篇

猜你喜欢

热点阅读