共计 2668 个字符,预计需要花费 7 分钟才能阅读完成。
Xcode13 上统计启动时长的变量 DYLD_PRINT_STATISTICS 生效了。团队中须要保留每次的启动工夫以作测验优化规范。在网上找到上面文章,写了个获取启动工夫工具类。
import “AppLaunchTime.h”
import <sys/sysctl.h>
import <mach/mach.h>
@implementation AppLaunchTime
double __t1; // 创立过程工夫
double __t2; // before main
double __t3; // didfinsh
/// 获取过程创立工夫
-
(CFAbsoluteTime)processStartTime {
if (__t1 == 0) {
struct kinfo_proc procInfo; int pid = [[NSProcessInfo processInfo] processIdentifier]; int cmd[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid}; size_t size = sizeof(procInfo); if (sysctl(cmd, sizeof(cmd)/sizeof(*cmd), &procInfo, &size, NULL, 0) == 0) {__t1 = procInfo.kp_proc.p_un.__p_starttime.tv_sec * 1000.0 + procInfo.kp_proc.p_un.__p_starttime.tv_usec / 1000.0;}
}
return __t1;
}
/// 开始记录:在 DidFinish 中调用
-
(void)mark {
double __t1 = [LGAppLaunchTime processStartTime];
dispatch_async(dispatch_get_main_queue(), ^{ // 确保 didFihish 代码执行后调用
if (__t3 == 0) {__t3 = CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970;
}
double pret = __t2 - __t1 / 1000;
double didfinish = __t3 - __t2;
double total = __t3 - __t1 / 1000;
NSLog(@"----------App 启动 --------- 耗时:pre-main:%f",pret);
NSLog(@"----------App 启动 --------- 耗时:didfinish:%f",didfinish);
NSLog(@"----------App 启动 --------- 耗时:total:%f",total);
});
}
/// 构造方法在 main 调用前调用
/// 获取 pre-main()阶段的完结工夫点绝对容易,能够间接取 main()主函数的开始执行工夫点. 举荐应用__attribute__((constructor)) 构建器函数的被调用工夫点作为 pre-main()阶段完结工夫点:__t2 能最大水平实现解耦:
void static __attribute__((constructor)) before_main() {
if (__t2 == 0) {__t2 = CFAbsoluteTimeGetCurrent() + kCFAbsoluteTimeIntervalSince1970;
}
}
@end
复制代码
-
(BOOL)application:(UIApplication )application didFinishLaunchingWithOptions:(NSDictionary )launchOptions {
[LGAppLaunchTime mark];
…
复制代码
pre-main()阶段开始工夫:__t1 苹果公司并没有间接向开发者提供外部统计工夫字段以供开发者间接获取 App 的启动开始时刻点,目前行业内次要有两种规范作为 App 的启动工夫点:
第一种规范:以名称 +(void)load 办法被调用时的工夫点 因为 +(void)load 办法被调用的工夫点产生 Initializer 初始化配置阶段,依据 (CompileSources) 编译资源规定下动静库的加载程序程序的调用相应类下的 +(void)load 办法,因为动静库的加载程序是递归加载的,所以咱们只有找到最外部的叶子节点的动静库,而后在这个最外部叶子结点动静库中 +(void)load 办法重构以记录启动工夫作为开始工夫点。这种形式没有统计到 Initializer 初始化配置阶段后面局部所耗费的那些工夫,比方在 Initializer 初始化配置阶段后面减少动静库、Category 等造成的耗时并不能被及时发现统计。
第二种规范:获取整个过程创立 (从开始到完结) 耗费工夫 App 从源头配置直至运行整个过程实际上是一个逻辑过程,如果能获取到逻辑过程的起步创立工夫即 exec()可执行函数触发阶段的触动工夫点作为整个 app 逻辑过程的开始工夫点,可能更提前记录到 App 的启动开始工夫点。
pre-main()阶段完结工夫点:__t2
获取 pre-main()阶段的完结工夫点绝对容易,能够间接取 main()主函数的开始执行工夫点。举荐应用__attribute__((constructor)) 构建器函数的被调用工夫点作为 pre-main()阶段完结工夫点:__t2
为什么不必最初一个 load 办法执行工夫作为 pre-main()阶段的完结工夫点?因为在超大型工程中咱们没方法确定哪个名称 load 办法是最初一个被执行 load 办法。。。
启动动作正式实现对应的工夫点:__t3 启动动作正式实现对应的工夫点个别以 didFinishLaunchingWithOptions: 已实现启动对应的代理协定函数的完结工夫点,但 didFinishLaunchingWithOptions: 已实现启动对应的代理协定函数的完结工夫点 (仅仅对应着光点初步渲染呈现) 其实不包含光点呈现之后启动图动画渲染的工夫耗费,而启动图动画执行实现后的工夫点更加靠近于用户的感官。能够在执行 didFinishLaunchingWithOptions: 的 runloop 循环的前面的循环来获取工夫.
最初
如果你感觉此文对你有一丁点帮忙,点个赞。或者能够退出我的开发交换群:1025263163 互相学习,咱们会有业余的技术答疑解惑
如果你感觉这篇文章对你有点用的话,麻烦请给咱们的开源我的项目点点 star: https://gitee.com/ZhongBangKe… 不胜感激!