实现思路:
- 方法打桩,记录启动时调用的方法,
- 按时间排序,生成order文件。
方法打桩:
编写脚本通过正则表达式,匹配Object-C方法,在每一个方法中都插入记录代码,配合dispatch_once,一次启动即可记录到目标时间点所有执行的方法。
脚本核心代码(python实现):
下边是oc方法的正则插入记录代码的脚本,还需要考虑oc工程中不少的c方法,Swift也可以如法炮制。
def change(value):
global current_string
line = value.group()
if '\\' in line:
return line
temp = line + ' \\'
if temp in current_string:
return line
temp2 = line + '\\'
if temp2 in current_string:
return line
methods_record_code = " // =========方法打桩 Start =======\n\
static dispatch_once_t onceTokenRecordMethods;\n\
dispatch_once(&onceTokenRecordMethods, ^{\n\
NSMutableArray *array_ccc = [[NSClassFromString(@\"GCTRecordMethods\") performSelector:NSSelectorFromString(@\"new\")] valueForKey:@\"array\"];\n\
NSDictionary *dictionary_ccc = @{@\"name\":[NSString stringWithFormat:@\"%s\", __FUNCTION__],@\"time\":[NSDate date],@\"sel\":NSStringFromSelector(_cmd)};\n\
[array_ccc addObject:dictionary_ccc];\n\
});\n\
// =========方法打桩 End =======\n"
return line + '\n' + methods_record_code
current_string = file_object.read()//读取目标类型文件
re.sub(r"\n(\-|\+)[\s]*([(][\s]*(.*)[\s]*[*]?[\s]*[)])[\s]*[^{;]+[\s]*?{", change,
current_string)
current_string = file_object.write()//正则替换后再写入
如何记录
项目中添加辅助类,实现一个单例,把辅助类放在compile source的第一个,保证程序启动时辅助类在其他类之前加载,启动打桩的标记存入辅助类单例的属性数组中,然后在目标时间点(比如首页的viewDidAppear),取出数组中的数据,按时间排序后生成字符串写入本地txt文件,通过xcode拉出手机中的txt文件即可获得原始数据。
NSArray *array = [[[NSClassFromString(@"GCTRecordMethods") performSelector:NSSelectorFromString(@"new")] valueForKey:@"array"] mutableCopy];
[array sortedArrayUsingComparator:^NSComparisonResult(NSDictionary * _Nonnull obj1, NSDictionary * _Nonnull obj2) {
NSDate *time1 = obj1[@"time"];
NSDate *time2 = obj2[@"time"];
return [time1 timeIntervalSinceDate:time2] > 0;
}];
NSDateFormatter *dateFmt = [[NSDateFormatter alloc]init];
dateFmt.dateFormat = @"yyyy-MM-dd HH:mm:ss";
NSMutableString *mstring = [[NSMutableString alloc] init];
for (NSDictionary *dictionary in array) {
NSString *name = dictionary[@"name"];
name = [name stringByReplacingOccurrencesOfString:@"_block_invoke" withString:@""];
[mstring appendString:name];
[mstring appendString:@"\n"];
}
[self writeToTXTFileWithString:mstring fileName:@"iLifeMethod"];
获取到的txt文件示例:
+[AppDelegate(Debug) load]
+[GCTTest1 test]
+[GCTTest2 test]
+[GCTTest3 test]
_instanceMethodDefaultSwizzling
__alias_of_selector
_instanceMethodSwizzling
+[CommandQueue(Auth) load]
+[XXXViewController(Extension) load]
改名xx.order,在xcode中order file配置order文件的相对路径即可。
如何确认:
打开xcode配置项linkmap,配置order文件前和配置order文件后编译生成的linkmap比对即可,贴两张图:
验证重排效果
Instrument的App Launch测试,筛选主线程,选择virtual memory,下图是未设置order文件的测试结果,[AppDelegate load]方法中三个测试方法导致了三次缺页中断,设置order文件后,再次测试找到[AppDelegate load]的File Backed Page In执行堆栈,三个方法没有了,对就是不见了,说明这三个方法没有再导致缺页中断,这三个测试方法和[AppDelegate load]被加载进同一片4K的内存,[AppDelegate load]方法执行时间减少了523.83us,假设App启动一共执行了3000个方法,二进制重排优化了1000个方法没有导致缺页中断(缺页中断不能避免,只能减少,因为一片4K内存装不下所有的方法),二进制重排总减少启动时长就是523830us=523.830ms=0.523830s,当然这些都是理论值,一般App的总启动时间也不会超过500ms。
废话
项目越大,启动执行方法越多,提升越明显。
后续更新
具体的总体提升数据等待完善后再更新。
相关理论参考下边这些文章
《iOS 优化篇 - 启动优化之Clang插桩实现二进制重排》
《简谈二进制重排》
《抖音研发实践:基于二进制文件重排的解决方案 APP启动速度提升超15%》
《iOS App启动时间优化 二进制重排和PGO》