前言

在刚接触 iOS 专项测试的时候就开始调研测试 iOS 启动时间,经过一段时间的学习和实践,介绍两种 iOS 启动时间测试的思路.

iOS 应用生命周期

状态如下:

下图是程序状态变化图:

image

iOS app 启动过程

main 函数之前

main 函数之前加载动态库、配置环境等,这个阶段基本上没有开发同学的代码,都是系统的事.

image

main 函数之后

wx20181211-160723.png

ApplicationDelegate

在 main 函数执行之后会走到 ApplicationDelegate 类中,这是开发同学第一个能控制的类.下面介绍几个 ApplicationDelegate 中的回调方法.

- (BOOL)application:(UIApplication *)application willFinishLaunchingWithOptions:(NSDictionary *)launchOptions
      告诉代理进程启动但还没进入状态保存

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
     告诉代理启动基本完成程序准备开始运行

- (void)applicationWillResignActive:(UIApplication *)application
    当应用程序将要入非活动状态执行,在此期间,应用程序不接收消息或事件,比如来电话了

- (void)applicationDidBecomeActive:(UIApplication *)application 
     当应用程序入活动状态执行,这个刚好跟上面那个方法相反

- (void)applicationDidEnterBackground:(UIApplication *)application
    当程序被推送到后台的时候调用。所以要设置后台继续运行,则在这个函数里面设置即可

- (void)applicationWillEnterForeground:(UIApplication *)application
    当程序从后台将要重新回到前台时候调用,这个刚好跟上面的那个方法相。

- (void)applicationWillTerminate:(UIApplication *)application
    当程序将要退出是被调用,通常是用来保存数据和一些退出前的清理工作。这个需要要设置UIApplicationExitsOnSuspend的键值。

- (void)applicationDidFinishLaunching:(UIApplication*)application
    当程序载入后执行

测试

main 函数之前测试

xcode 提供了一个很赞的方法,只需要在 Edit scheme -> Run -> Arguments 中将环境变量
DYLD_PRINT_STATISTICS 设 1,就可以看到 main 之前各个阶段的时间消耗.

image

output:
2018-10-08 11:25:41.101432+0800 xxxxxx[2567:1049814] [DYMTLInitPlatform] platform initialization successful
Total pre-main time: 363.78 milliseconds (100.0%)
         dylib loading time:  56.61 milliseconds (15.5%)
        rebase/binding time:  43.80 milliseconds (12.0%)
            ObjC setup time:  50.18 milliseconds (13.7%)
           initializer time: 213.00 milliseconds (58.5%)
           slowest intializers :
             libSystem.B.dylib :   8.25 milliseconds (2.2%)
   libBacktraceRecording.dylib :   9.38 milliseconds (2.5%)
    libMainThreadChecker.dylib :  13.30 milliseconds (3.6%)
          libglInterpose.dylib :  78.40 milliseconds (21.5%)
         libMTLInterpose.dylib :  21.70 milliseconds (5.9%)
                    LuoJiFMIOS : 107.63 milliseconds (29.5%)

main 函数之后测试

这是用打 log 的方式记录时间.

1.首先在 main.m 添加如下代码

CFAbsoluteTime StartTime;
int main(int argc, char * argv[]) {
    StartTime = CFAbsoluteTimeGetCurrent();

2.然后在 AppDelegate.m 的开头声明
extern CFAbsoluteTime StartTime;
最后在 AppDelegate.m 的
didFinishLaunchingWithOptions 中第一行添加

OptionsStartTime = CFAbsoluteTimeGetCurrent();
NSLog(@"AppLanuch--mainBefore--%f",(OptionsStartTime - StartTime));

这里统计的是 main 函数之前后到 didFinishLaunchingWithOptions 开始.

didFinishLaunchingWithOptions 测试

最后在 AppDelegate.m 的
didFinishLaunchingWithOptions 中最后一行添加

OptionsEndTime = CFAbsoluteTimeGetCurrent();
  NSLog(@"AppLanuch--didOptionsTotal--%f",(OptionsEndTime - OptionsStartTime));

这里是统计这个 didFinishLaunchingWithOptions 函数耗时.

image

统计启动耗时

总耗时=main 函数之前 +main 函数之后 +didFinishLaunchingWithOptions

time profile 工具

time profile 是 xcode 中 Instruments 中的一个测试工具,可以用来测试函数耗时.

time profile

image

启动 app

image

main 方法

image

函数列表

在左侧可以看到每个函数的耗时
image

点击函数

image

跳转到具体函数

image

自动化方案测试

使用 appium 启动 app,同时 idevicesyslog 查看 log 日志并找出启动结束的点.这种方案目前我还没有找出确定的结束点,后续再研究下.

踩坑

在 didFinishLaunchingWithOptions 中注册函数太多

启动以后就在 didFinishLaunchingWithOptions 注册各种函数,可以适当把非必须函数置后注册.

在 didFinishLaunchingWithOptions 中使用第三方 sdk

会注册第三方 sdk,比如 umeng 和 bugly 等注册会导致耗时的可能,如果更换第三方 sdk,需要对其进行性能测试.

结语

想做好 iOS 专项测试,还是要深入了解 iOS 的一些框架和原理,要不很难深入测试和二次开发.

参考文章:

iOS 应用程序生命周期 (前后台切换,应用的各种状态) 详解
https://blog.csdn.net/totogo2010/article/details/8048652

iOS 启动时间优化

https://mp.weixin.qq.com/s/lRBdtwh7BmO6i3yDBRGTwA

iOS-main 函数之前

https://www.jianshu.com/p/7239f13753aa

如何精确度量 iOS App 的启动时间

https://www.jianshu.com/p/c14987eee107

[iOS] 一次立竿见影的启动时间优化

https://juejin.im/post/5a31190751882559e225a775

iOS App 冷启动治理:来自美团外卖的实践

https://mp.weixin.qq.com/s/jN3jaNrvXczZoYIRCWZs7w

time profile 工具使用

https://www.raywenderlich.com/397-instruments-tutorial-with-swift-getting-started


↙↙↙阅读原文可查看相关链接,并与作者交流