启动项优化它位于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:
执行制定启动项:
- (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
参考: