美文网首页iOSiOS-Developer-OC开发环境
[iOS]检测页面加载时间的一种方式

[iOS]检测页面加载时间的一种方式

作者: 未来行者 | 来源:发表于2018-08-31 00:29 被阅读608次

    背景

    前一段时间组内有开会确定一些技术性的研究工作,其中一个分配给我的是如何获得用户所感知的页面出现时间.第一时间去Google了前人们总结的经验,但并没有发现令人满意的方案.先介绍几种思路,然后对比一下.

    基本思路

    通常是利用swizllingviewDidLoad方法里保存一个初始时间,然后在viewDidAppear里得到页面出现的时间.

    @implementation UIViewController (LoadTime)
    
    + (void)load
    {
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
           swizzleMethod([UIViewController class], @selector(viewDidLoad), @selector(my_viewDidLoad));
        });
           swizzleMethod([UIViewController class], @selector(viewDidAppear), @selector(my_viewDidAppear:));
        });
    }
    
    - (void)my_viewDidLoad
    {
        NSDate *date = [NSDate date];
        // 保存开始时间
        _date = date;
        [self my_viewDidLoad];
    }
    - (void)my_viewDidAppear:(BOOL)animated{
        [self my_viewDidAppear:animated];
        // 得到加载时间
        NSTimeInterval duration = [[NSDate date] timeIntervalSinceDate:_date];
        NSLog(@"Page %@ cost %g to be appeared", [self class], duration); 
    }
    
    @end
    

    一种基于KVO的页面加载时间获取(作者五子棋)

    原文在这里
    这篇博客指出了上述方法的问题,你hook的其实是父类UIViewController的方法,子类其实是调用了[super xxxxxx]方法,这种处理方式没办法对每种页面都进行处理,需要各自建立对应分类.
    于是作者突发奇想,利用KVO拿到派生的子类进行IMP的替换,从而解决了这个问题.

    「无侵入页面加载完成检测」的一些思路(作者Limboy)

    原文在这里
    这个方法是我完全没有想到的一种处理方式.利用图像纯色占比来判断当前页面是否是加载完成.简单来说,就是开启一个CADisplayLink定时器,对当前页面进行截图,然后利用计算纯色占比的算法算出比例,当比例大于某一个阈值,就说明页面已经加载成功了.这种方法我觉得是最直观的方法,但作者也列举了一些问题:

    1.需要主动去截屏检测,而不能加载完成后告知。这其中的差别在于无法得知具体哪个时间加载完成了。
    2.有些页面被故意设计成有较多留白,这时就不容易判断了。
    3.「未加载完成」不同的页面会有不同的表现。
    4.当用户滑动时,有可能之前的页面已经加载了

    美团Hertz的思路

    原文在这里
    这篇文章介绍了美团关于性能监控的一些措施,也提到了iOS中页面加载时间检测的方式:在iOS中我们采取了不同的做法,Hertz在配置文件中指定最终渲染页面的某个元素的tag,并在网络请求成功后开启CADisplayLink检查该元素是否出现在根节点下面。

    总结下来的三个问题

    • 问题1:即使解决了无法直接hook子类的实现,但是也不能得到确切的加载时间如下面的例子:
    - (void)viewDidLoad {
        [super viewDidLoad];
        self.view.backgroundColor = [UIColor whiteColor];
        // 模拟了一个异步网络请求
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            sleep(3);
            dispatch_async(dispatch_get_main_queue(), ^{
                TempView *tView = [[TempView alloc] init];
                tView.frame = CGRectMake(10, 20, 300, 200);
                tView.backgroundColor = [UIColor orangeColor];
                [self.view addSubview:tView];
            });
        });
    }
    

    在这个例子中,用户所感受到的加载完成应该是tView在网络请求之后显示的时间.单纯的hook生命周期方法是无法获取网络延迟这段时间的.

    • 问题2:如果开启定时器进行页面检测截图,那么消耗内存也会影响页面渲染,造成性能问题.
    • 问题3:如果给页面打tag,需要在网络请求成功之后去进行检测tag是否出现,这里有个实际问题:对于网络请求的处理貌似比较复杂,直接想到的就是在最底层网络请求成功的回调里加一个钩子;但不同的页面请求不一样,怎么区分这个请求和页面的关系貌似也是一个问题,就算能处理也感觉不会太优雅.

    我的处理方式

    这里借鉴了一下美团的思路,但又有所不同.
    我们先弄清楚2个问题:

    1.我们检测页面加载时长的目的主要是为了检测某些页面网络请求加载时间的快慢,通常页面的出现就伴随着接口数据的刷新,从而发现哪些页面显示时间长比较耗时,进而针对性地进行优化.
    2.并不是所有的界面都需要进行检测,例如比较静态的页面,例如我的,设置等静态页面和其他用户并不是特别关注的页面.

    到这里我们就明确了那些页面需要被检测,那我们怎么判断页面真的显示呢?

    • 如果该页面主要子view是UITableView或者UICollectionView,那么可以指定找到这个页面的这两种子类,通过visibleCells是否大于0来判断.
    • 如果该页面主要子view的类型是其他view,那么根据这个view是否出现在屏幕上来判断.(注:这种方式不太通用,暂且这么判断吧).

    知道如何判断了,就可以说下思路了:

    1. 我们可以本地指定一个plist文件,或者说通过接口下发一个json,来指定哪些页面需要被检测,这个页面的主要子view是什么,是什么类型的view.像这样:
    <dict>
        <key>ViewController</key>
        <dict>
            <key>TargetSubview</key>
            <string>UITableView</string>
            <key>TargetSubviewType</key>
            <integer>0</integer>
        </dict>
        <key>TempViewController</key>
        <dict>
            <key>TargetSubview</key>
            <string>TempView</string>
            <key>TargetSubviewType</key>
            <integer>1</integer>
        </dict>
    </dict>
    </plist>
    
    1. hook UIViewControllerviewDidLoad方法,拿到初始时间,同时开启一个CADisplayLink定时器进行检测.
    2. 在定时器的方法里,我们就开始根据需要检测的页面,找到目标的子view,然后根据view类型进行相应的判断即可.如果符合判断条件,就可以进行上报了.

    这里是一个简单的demo地址.

    如有其它思路或者勘误,请提点和指正!

    相关文章

      网友评论

      本文标题:[iOS]检测页面加载时间的一种方式

      本文链接:https://www.haomeiwen.com/subject/efsjwftx.html