??最近在搞app的性能监控。主要从启动耗时,首屏耗时,操作耗时的几个指标进行监控,后续会增加其他维度的指标
启动耗时
??启动耗时主要分为冷启动,热启动。 其中冷启动又分为首次启动,非首次启动。 冷启动:从main函数开始,到第一个用户自定义的页面出现为止(备注:这个过程中要区分一下是否是首次启动) 热启动:从app即将进入前台到,app进入前台的这个过程。涉及到的函数。具体下:
- (void)applicationWillEnterForeground:(UIApplication *)application
{
// 此处调用热启动开始的方法
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
// 此处调用热启动结束的统计方法
}
首屏耗时
?? 首屏耗时是指从一个页面初始化到到页面获取数据后第一次渲染完成结束。 初始化的时机:hook UIViewController 的init方法,获取调用的时机 示例代码如下:
- (instancetype)performance_init
{
NSString *className = NSStringFromClass([self class]);
//过滤掉系统类
if ([className hasPrefix:@"UI"]) {
return [self performance_init];
}
// 过滤掉黑名单
if ([JKPerformanceManager isInBlackList:className]) {
return [self performance_init];
}
JKPagePerformanceModel *firstScreen_pagePerformanceModel = [JKPagePerformanceModel new];
firstScreen_pagePerformanceModel.element_type = JKPageTypeFirstScreen;
firstScreen_pagePerformanceModel.start_time = [[NSDate date] timeIntervalSince1970] * 1000;
firstScreen_pagePerformanceModel.page = className;
self.firstScreen_pagePerformanceModel = firstScreen_pagePerformanceModel;
return [self performance_init];
}
渲染完成的时机:目前只考虑有列表的视图。UITableView,UICollectionView reloadData结束后 visibleCell的数量大于某个数值。认为渲染完成。 关于如何reloaddata后获取获取visibleCell的数量可以参考《iOS开发获取tableView,collectionView reloaddata 执行结束布局生效时机》 坑点:如果是嵌套的UITableView,UICollectionView 存在reloaddata的时候,对应的视图的window不存在,这个时候采取延迟满足的策略,为这些视图打上标记。等到这些视图调用didMoveToWindow 的时候,判断视图的window是否存在,如果存在,并且该视图是被标记过的,那么这个时机可以认为是首屏渲染完成的时机。示例代码如下:
- (void)performance_didMoveToWindow
{
[self performance_didMoveToWindow];
//视图离开页面的时候window为nil,此时过滤掉
if (!self.window) {
return;
}
id delegate = self.delegate;
//过滤掉异常
if (!delegate) {
return;
}
NSString *delegate_ClassName = NSStringFromClass([delegate class]);
// 过滤掉系统子类
if (![NSStringFromClass([self class]) isEqualToString:@"UITableView"]) {
return;
}
// 过滤掉系统视图
if ([delegate_ClassName hasPrefix:@"UI"]) {
return;
}
// 忽略掉黑名单
if ([JKPerformanceManager isInBlackList:delegate_ClassName]) {
return;
}
[self setNeedsLayout];
[self layoutIfNeeded];
if ([[JKPerformanceManager helper] respondsToSelector:@selector(track_tableViewDidMoveToWindow:)]) {
[[JKPerformanceManager helper] track_tableViewDidMoveToWindow:self];
}
[self performance_firtScreenTrack];
}
操作耗时
?? 操作耗时是指:用户的一次操作,比如点击 从开始执行,到执行结束的耗时。我这次处理的操作耗时主要有,按钮的点击,手势触发的时间。列表点击触发的事件。 UIControl的处理如下:
- (void)performance_sendAction:(SEL)action to:(id)target forEvent:(UIEvent *)event
{
JKOperationPerformanceModel *operatePerformanceModel = [VVOperationPerformanceModel new];
operatePerformanceModel.start_time = [[NSDate date] timeIntervalSince1970] * 1000;
if ([[JKPerformanceManager helper] respondsToSelector:@selector(track_control:sendAction:to:forEvent:)]) {
[[JKPerformanceManager helper] track_control:self sendAction:action to:target forEvent:event];
}
// 正常执行事件
[self performance_sendAction:action to:target forEvent:event];
// 忽略掉黑名单,避免干扰
NSString *target_ClassName = NSStringFromClass([target class]);
if ([JKPerformanceManager isInBlackList:target_ClassName]) {
return;
}
// 性能采集打点
operatePerformanceModel.end_time = [[NSDate date] timeIntervalSince1970] * 1000;
operatePerformanceModel.element_type = JKOperateTypeClick;
operatePerformanceModel.widget = [NSString stringWithFormat:@"%@+%@",target_ClassName,NSStringFromSelector(action)];
if (!self.track_containerVC) {
UIViewController *track_containerVC = [JKPerformanceManager topContainerViewControllerOfResponder:self];
self.track_containerVC = track_containerVC;
}
operatePerformanceModel.page = NSStringFromClass(self.track_containerVC.class)?:NSStringFromClass([UIViewController class]);
[JKPerformanceManager trackPerformance:operatePerformanceModel vc:self.track_containerVC];
}
UIGestureRecognizer的处理如下:
- (void)handleOfTarget:(id)target selector:(SEL)selector
{
// 过滤掉已经hook的
NSMutableArray *hookedArray = (NSMutableArray *)objc_getAssociatedObject(target, track_gesture_target_has_hookedKey);
if ([hookedArray containsObject:selectorStr]) {
return;
}
NSError *error1 = nil;
[(NSObject *)target aspect_hookSelector:selector withOptions:AspectPositionInstead usingBlock:^(id<AspectInfo> data){
if ([[JKPerformanceManager helper] respondsToSelector:@selector(track_gesture:target:selector:)]) {
[[JKPerformanceManager helper] track_gesture:self target:target selector:selector];
}
JKOperationPerformanceModel *operatePerformanceModel = [JKOperationPerformanceModel new];
operatePerformanceModel.start_time = [[NSDate date] timeIntervalSince1970] * 1000;
NSInvocation *invocation = [data originalInvocation];
[invocation invoke];
if ([self isKindOfClass:[UITapGestureRecognizer class]]
|| [self isKindOfClass:[UILongPressGestureRecognizer class]]) {
operatePerformanceModel.end_time = [[NSDate date] timeIntervalSince1970] * 1000;
if ([self isKindOfClass:[UITapGestureRecognizer class]]) {
operatePerformanceModel.element_type = JKOperateTypeClick;
} else if ([self isKindOfClass:[UILongPressGestureRecognizer class]]) {
operatePerformanceModel.element_type = JKOperateTypeLongPress;
}
operatePerformanceModel.widget = [NSString stringWithFormat:@"%@+%@",target_ClassName,selectorStr];
if (!self.view.track_containerVC) {
UIViewController *track_containerVC = [JKPerformanceManager topContainerViewControllerOfResponder:self.view];
self.view.track_containerVC = track_containerVC;
}
operatePerformanceModel.page = NSStringFromClass(self.view.track_containerVC.class)?:NSStringFromClass([UIViewController class]);
[JKPerformanceManager trackPerformance:operatePerformanceModel vc:self.self.view.track_containerVC];
}
} error:&error1];
if (error1) {
#if DEBUG
NSLog(@"UIGestureRecognizer+JKTrack error1:%@",error1);
NSAssert(NO, @"UIGestureRecognizer+JKTrack error1");
#endif
} else {
NSMutableArray *array = (NSMutableArray *)objc_getAssociatedObject(target, track_gesture_target_has_hookedKey);
if (!array) {
array = [NSMutableArray new];
}
[array addObject:selectorStr];
objc_setAssociatedObject(target, track_gesture_target_has_hookedKey, array, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
}
UITableView 的处理如下:
- (void)performance_reloadData
{
[self performance_reloadData];
//屏蔽掉系统子类
if (![NSStringFromClass([self class]) isEqualToString:@"UITableView"]) {
return;
}
NSString *delegate_ClassName = NSStringFromClass([self.delegate class]);
// 过滤掉系统视图
if ([delegate_ClassName hasPrefix:@"UI"]) {
return;
}
// 忽略掉黑名单
if ([JKPerformanceManager isInBlackList:delegate_ClassName]) {
return;
}
if (!self.window) {
if ([[JKPerformanceManager helper] respondsToSelector:@selector(track_markTableViewNOSuperView:)]) {
[[JKPerformanceManager helper] track_markTableViewNOSuperView:self];
}
return;
}
[self setNeedsLayout];
[self layoutIfNeeded];
if ([[JKPerformanceManager helper] respondsToSelector:@selector(track_reloadDataOfTableView:)]) {
[[JKPerformanceManager helper] track_reloadDataOfTableView:self];
}
[self performance_firtScreenTrack];
}
文中贴出来的代码,主要是方便大家了解一下思路。 完整代码下载地址:《JKPerformanceManager》
《iOS开发获取tableView,collectionView reloaddata 执行结束布局生效时机》 https://blog.csdn.net/hanhailong18/article/details/105243778?spm=1001.2014.3001.5501 《UIView /UIViewController的生命周期》 https://juejin.cn/post/6844904089348734983 《VC耗时时监控的参考资料》 https://blog.csdn.net/killer1989/article/details/108099921 《iOS App冷启动治理:来自美团外卖的实践》 https://segmentfault.com/a/1190000017298001
更多干货文章,欢迎扫描二维码关注功能
|