App启动项优化治理附源码

启动项优化它位于APP冷启动阶段,各种SDK的初始化以及我们APP的配置初始化都算启动项,一般我们将他们直接堆在AppDelegate- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions方法下。项目初期还好,一旦项目迭代版本多了,功能及各种SDK的启动急剧增加,这个时候我们就会发现冷APP启动变慢了,非常影响用户体验,我也是受美团的启动优化文章影响,写了这篇文章(主要是美团没发源码~)。

启动项优化治理需要用到的知识点有:

  • Mach-O
  • 编译指令 __attribute__

Mach-O

Mach-O为Mach Object文件格式的缩写,它是一种用于可执行文件,目标代码,动态库,内核转储的文件格式。作为a.out格式的替代,Mach-O提供了更强的扩展性,并提升了符号表中信息的访问速度。【摘自百度百科】

简而言之,Mach-O就是Mac/iOS上的可执行文件,我们平常编译iOS项目后就会产生一个xxx.app的文件,右键显示包内容,就能找到一个同名xx的Mach-O文件。

命令otool -l xxx看查看Mach-O的文件内容:

以下为Mach-O文件内容的节选

EasyLaunch:
Mach header
      magic cputype cpusubtype  caps    filetype ncmds sizeofcmds      flags
 0xfeedfacf 16777223          3  0x00           2    22       3344 0x00200085
Load command 0
      cmd LC_SEGMENT_64
  cmdsize 72
  segname __PAGEZERO
   vmaddr 0x0000000000000000
   vmsize 0x0000000100000000
  fileoff 0
 filesize 0
  maxprot 0x00000000
 initprot 0x00000000
   nsects 0
    flags 0x0
Load command 1
      cmd LC_SEGMENT_64
  cmdsize 872
  segname __TEXT
   vmaddr 0x0000000100000000
   vmsize 0x0000000000004000
  fileoff 0
 filesize 16384
  maxprot 0x00000005
 initprot 0x00000005
   nsects 10
    flags 0x0
Section
  sectname __text
   segname __TEXT
      addr 0x0000000100001540
      size 0x0000000000000c43
    offset 5440
     align 2^4 (16)
    reloff 0
    nreloc 0
     flags 0x80000400
 reserved1 0
 reserved2 0
 ...
复制代码

attribute

__attribute__ 是一个编译指令,可以指定在声明时的错误检查及高级优化的特性。

其位置约束为: 放于声明的尾部“;” 之前

attribute 书写特征为: attribute 前后都有两个下划线,并切后面会紧跟一对原括弧,括弧里面是相应的__attribute__ 参数。

attribute 语法格式为: attribute ((attribute-list))

标记为attribute__((used))的函数被标记在目标文件中,以避免链接器删除未使用的节。

标记__attribute__((section(“new_section”)))的函数或变量会被编译器放入到DATA段的new_section 中,而不会放到TEXT段。

原理

通过__attribute__将定义启动项的函数指针放入到DATA段的_easy_x下,在项目运行的适当时机在读取对应section下的内容,执行函数即可实现启动项的优化治理。

实现

启动函数入口定义:

#define EASY_REGISTER_LAUNCH_FUNCATION(key) __EASY_REGISTER_LAUNCH_FUNCATION(key)
#define __EASY_REGISTER_LAUNCH_FUNCATION(key) \
    static void __EASY_REGISTER_LAUNCH_##key(void); \
    __attribute__((used, section("__DATA," "_easy_" #key))) \
    static const void * __EASY__##key = __EASY_REGISTER_LAUNCH_##key; \
    static void __EASY_REGISTER_LAUNCH_##key(void) \
复制代码

启动函数使用,在某个.m文件中实现:

EASY_REGISTER_LAUNCH_FUNCATION(LaunchSection_didFinishLaunching) {
    NSLog(@"执行 LaunchSection_didFinishLaunching");
}
复制代码

编译后可以查看Mach-O文件多出几个section:
img

执行制定启动项:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    [EasyLaunch executeWithSection:LaunchSection_didFinishLaunching];
    return YES;
}
复制代码

读取制定启动项函数指针的实现:

#ifndef __LP64__
#define mach_header_c mach_header
#else
#define mach_header_c mach_header_64
#endif

void _executeWithSection(long section) {
    Dl_info info;
    if (dladdr(&_executeWithSection, &info)) {
        struct mach_header_c *machHeader = (struct mach_header_c *)info.dli_fbase;
        NSString *s = [NSString stringWithFormat:@"_easy_%zd",section];
#ifndef __LP64__
        unsigned long size = 0;
        uintptr_t *data = (uintptr_t *)getsectiondata(machHeader, "__DATA", s.UTF8String, &size);
        if (data == NULL) {
            return;
        }
        int count = (int)(size / sizeof(void *));
        for (int i = 0; i < count; ++i) {
            void (*function)(void) = (void (*)(void))data[i];
            (function)();
        }
#else
        const struct section_64 * section64 = getsectbynamefromheader_64(machHeader, "__DATA", s.UTF8String);
        if (section64 == nil) {
            return;
        }
        uint16_t step = sizeof(void *);
        for (uint16_t offset = section64->offset; offset < section64->offset + section64->size; offset += step) {
            void (**function)(void) = (void (**)(void))((uint64_t)machHeader + offset);
            (*function)();
        }
#endif
    }
}
复制代码

附上源码:EasyLaunch

参考:

美团外卖iOS App冷启动治理

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享