美文网首页
iOS启动时间优化(二)

iOS启动时间优化(二)

作者: ZAREMYDREAM | 来源:发表于2020-05-12 22:01 被阅读0次

    前言

    之前 启动优化(一)说了二进制重排的原理,今天具体说下实现。

    查看链接顺序

    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即可实现二进制重排

    相关文章

      网友评论

          本文标题:iOS启动时间优化(二)

          本文链接:https://www.haomeiwen.com/subject/sfwinhtx.html