前言
页面加载时间指的页面从创建到可见的时间。严格意义上来说页面加载时间测试,更应该是页面的冷加载,不包含接口返回数据时间。
页面加载时间能反映代码中,创建页面视图是否有过度绘制或者绘制不合理导致创建视图时间过长的情况。
UIViewController是什么?
UIViewController是画面控制的中心类,包含导航条、标签条、工具条等多种功能界面,主要功能是用于控制画面的切换,其中的view属性管理整个画面的外观。
页面生命周期
viewDidLoad: 载入完成,可以进行自定义数据以及动态创建其他控件
viewWillAppear: 视图即将出现在屏幕之前
viewDidAppear: 视图已经在屏幕上渲染完成
viewWillDisappear: 视图即将从屏幕上移除
viewDidDisappear: 视图已经被从屏幕上移除
dealloc: 视图被销毁
image
测试方法
view基类打点
一般项目代码都会继承UIViewController做一些封装,然后其他页面继承这个view基类。
代码语言:javascript复制页面开始创建点
- (void)viewDidLoad {
[super viewDidLoad];
//继承了UIViewController的viewDidLoad方法
self.statusBarStyle = UIStatusBarStyleDefault;
[self.view setBackgroundColor:[UIColor whiteColor]];
NSLog(@"page-test-start:%@",NSStringFromClass([self class]));
self.StartTime = [[NSDate date] timeIntervalSince1970];
}
页面展示结束点
- (void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
//继承了UIViewController的viewDidAppear方法
NSLog(@"page-test-end:%@",NSStringFromClass([self class]));
// 用NSStringFromClass方法获取当前的页名称
self.EndTime = [[NSDate date] timeIntervalSince1970];
CGFloat rounded_up = round((self.EndTime-self.StartTime) * 1000);
NSLog(@"page-test-total:%.2lf",rounded_up);
}
操作app并且使用idevicesyslog过滤关键字:
idevicesyslog | grep -e page-test-end -e page-test-total
output:
Sep 30 16:33:06 xinxide-iPhone xxxxx[2195] <Notice>: page-test-end:HomeViewController
Sep 30 16:33:06 xinxide-iPhone xxxxx[2195] <Notice>: page-test-total:379.00
Sep 30 16:33:09 xinxide-iPhone xxxxx[2195] <Notice>: page-test-end:UserInfoViewControllerV2
Sep 30 16:33:09 xinxide-iPhone xxxxx[2195] <Notice>: page-test-total:239.00
Sep 30 16:33:12 xinxide-iPhone xxxxx[2195] <Notice>: page-test-end:LoginRegisterViewControllerV2
Sep 30 16:33:12 xinxide-iPhone xxxxx[2195] <Notice>: page-test-total:631.00
Sep 30 16:33:14 xinxide-iPhone xxxxx[2195] <Notice>: page-test-end:LoginViewControllerV2
Sep 30 16:33:14 xinxide-iPhone xxxxx[2195] <Notice>: page-test-total:567.00
hook机制
第一种方法在你需要知道view的基类叫什么名字并且在代码中打点,这样做每次都有麻烦。
所以想使用拦截viewDidLoad和viewDidAppear这两个函数,就拦截器中打印时间就可以了。
Aspects库是一个是iOS上的轻量级AOP库,
https://github.com/steipete/Aspects,另外Aspects封装了iOS runtime的特性。
什么是AOP?
简单来说,AOP(Aspect Oriented Programming)是面向切面编程,主要的功能是:日志记录,性能统计、安全控制、事务处理、异常处理等等。
什么是hook?
使它能够将自身的代码「融入」被勾住(Hook)的程序的进程中,成为目标进程的一个部分。API Hook 技术是一种用于改变 API 执行结果的技术,能够将系统的 API 函数执行重定向。
核心代码
在podfile引用如下:
代码语言:javascript复制
target "UICatalog" do
xcodeproj 'UICatalog.xcodeproj'
pod 'YYKit'
pod 'Aspects'
end
代码语言:javascript复制- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[[getperformance new] performancethread];//获取性能数据
/**
* 事件拦截
* 拦截UIViewController的viewDidLoad方法
*/
[UIViewController aspect_hookSelector:@selector(viewDidLoad) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo){
NSLog(@"%@ 对象的viewDidLoad调用了",aspectInfo.instance);
self.StartTime = [[NSDate date] timeIntervalSince1970];
/**
* 添加我们要执行的代码,由于withOptions是AspectPositionAfter。
* 所以每个控制器的viewDidLoad触发都会执行下面的方法
*/
} error:NULL];
/**
* 事件拦截
* 拦截UIViewController的viewDidAppear方法
*/
[UIViewController aspect_hookSelector:@selector(viewDidAppear:) withOptions:AspectPositionAfter usingBlock:^(id<AspectInfo> aspectInfo){
NSLog(@"%@ 对象的viewDidAppear调用了",aspectInfo.instance);
self.EndTime = [[NSDate date] timeIntervalSince1970];
CGFloat rounded_up = round((self.EndTime-self.StartTime) * 1000);
NSLog(@"%@页面,页面加载耗时:%.2lf",aspectInfo.instance,rounded_up);
/**
* 添加我们要执行的代码,由于withOptions是AspectPositionAfter。
* 所以每个控制器的viewDidLoad触发都会执行下面的方法
*/
} error:NULL];
return YES;
}
output:
2018-10-02 18:11:44.361065 0800 UICatalog[22872:5780236] <AAPLAlertViewController: 0x7ffd0cf2c870>页面,页面加载耗时:518.00
2018-10-02 18:11:50.289907 0800 UICatalog[22872:5780236] <AAPLPageControlViewController: 0x7ffd0ce39080>页面,页面加载耗时:523.00
2018-10-02 18:11:52.131938 0800 UICatalog[22872:5780236] <AAPLDatePickerController: 0x7ffd0ce3a550>页面,页面加载耗时:631.00
结语
客户端专项测试已经做了大半年了,从无到有、从有到持续优化。
对我而言有两点思考.
1、专项测试测出来的数据结果,其实并不是记录一个数值而已,更需求了解其背后的技术特性。
2、测试结果是否可能提供给开发同学优化的价值,换位思考如果你是开发,你面对一些冰冷的数据,你如何优化?