【iOS性能监控】- FPS

2022-03-16  本文已影响0人  Shawn_Y

本篇文章主要包含以下方面
1、如何获取FPS数据
2、如何处理数据
3、上传策略

如何获取FPS数据

创建CADisplayLink,设置CADisplayLink的target的时候,用中间代理类做一次转发,代理类内部弱引用当前self,防止产生循环强引用。设置每秒的刷新帧率为60次,由于高刷屏的出现,这里可能需要做些微调。

- (CADisplayLink *)displayLink
{
    // Lazily create the display link.
    if (_displayLink == nil)
    {
        _displayLink = [CADisplayLink displayLinkWithTarget:[MMAPMWeakProxy proxyWithTarget:self] selector:@selector(updateFPSAction:)];
        _displayLink.preferredFramesPerSecond = 60;
    }
    return _displayLink;
}

添加到子线程RunLoop中,需要注意的是子线程要做保活,这里不再赘述具体怎么做

- (void)start
{
    if (_displayLink != nil)
    {
        return;
    }
    [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
}

displayLink的回调,更新计数count,超过1秒采样一次,计算间隔时间内的刷新频率,fps就是我们需要的数据

- (void)updateFPSAction:(CADisplayLink *)displayLink
{
    if (self.lastUpdateTime == 0)
    {
        self.lastUpdateTime = displayLink.timestamp;
    }
    ++self.count;
    NSTimeInterval interval = displayLink.timestamp - self.lastUpdateTime;
    if (interval < self.updateFPSInterval)
    {
        return;
    }
    self.lastUpdateTime = displayLink.timestamp;
    self.fps = self.count/interval;
    self.count = 0;
}

如何处理数据

首先思考一个问题,做FPS监控的目的是什么?

  1. 获得app的FPS指标
  2. 优化低刷新率的页面,获得更好的用户体验

所以FPS需要按用户进入的页面进行分组,在进入页面的时候调用

- (void)setVCPageName:(NSString *)vcName pageType:(NSString *)pageType
{
    @synchronized (self) {
        self.vcName = [MMAPMUtil shouldRecordForVCPageName:vcName] == YES ? vcName : nil;
    }
}

创建GCDTimer,每秒获取一次fps数据。

- (void)addTimer
{
    [self removeTimer];
    self.GCDTimer = [[AndyGCDTimer alloc] initInQueue:[AndyGCDQueue globalQueue]];
    __weak typeof(self) weakSelf = self;
    [self.GCDTimer timerExecute:^{
        __strong typeof(weakSelf) strongSelf = weakSelf;
        if (strongSelf.vcName.length == 0) return;
        
        NSUInteger fps = [strongSelf.fpsMonitor getFPS];
        // 如果 当前fps 小于 最小设定值,则立刻触发上报
        if (fps <= [MMAPMConfig sharedConfig].FPSPerfMinValue)
        {
            // 触发上报就拿当前的 fps记录到 log v2
            @synchronized (strongSelf) {
                // 首先从缓存中移除记录
                if ( !MM_IS_STR_NIL(strongSelf.vcName) ) {
                    [strongSelf.pageSysDictM removeObjectForKey:strongSelf.vcName];
                }
            }
            // 立刻上报当前页面的sys信息
            [strongSelf recordVCName:strongSelf.vcName fps:fps];
        }
        else
        {
            // 不断组合页面sys信息
            [strongSelf combineSysFPS:fps];
            
            // 如果没有小于最小fps值,则每隔 sysLogTriggerInterval 时间取平均数上报
            NSTimeInterval now = [[NSDate date] timeIntervalSince1970];
            if (strongSelf->_lastLogTimestamp + 60 < now)
            {
                // 组合fps信息写入log v2 日志系统
                [strongSelf recordLog];
            }
        }
        
    } timeIntervalWithSecs:1];
    
    [self.GCDTimer resume];
}

按页面分组,把fps数据添加到数组中

- (void)combineSysFPS:(NSUInteger)fps
{
    @synchronized (self) {
        if (self.vcName.length == 0) return;
        
        NSMutableDictionary *recordDictM = self.pageSysDictM[self.vcName];
        if (recordDictM == nil)
        {
            recordDictM = [NSMutableDictionary dictionary];
        }
        
        NSMutableArray *fpsArrM = recordDictM[FPS_KEY];
        if (fpsArrM == nil)
        {
            fpsArrM = [NSMutableArray array];
            recordDictM[FPS_KEY] = fpsArrM;
        }
        [fpsArrM addObject:@(fps)];
      
        [self.pageSysDictM setValue:recordDictM forKey:self.vcName];
    }
}

上传策略

计算每个页面的fps平均值

- (void)recordLog
{
    // 如果当前触发日志记录,则及时记录更新当前log的时间
    NSTimeInterval now = [[NSDate date] timeIntervalSince1970];
    _lastLogTimestamp = now;
    
    @synchronized (self) {
        [self.pageSysDictM enumerateKeysAndObjectsUsingBlock:^(NSString * _Nonnull vcName_Key, NSMutableDictionary * _Nonnull recordDict, BOOL * _Nonnull stop) {
            __block NSUInteger fps_avg;
            [recordDict enumerateKeysAndObjectsUsingBlock:^(NSString *  _Nonnull key, NSArray *  _Nonnull arr, BOOL * _Nonnull stop) {
                if ([key isEqualToString:FPS_KEY])
                {
                    fps_avg = @(round(1.0 * [[arr valueForKeyPath:@"@sum.unsignedIntegerValue"] unsignedIntegerValue] / arr.count)).unsignedIntegerValue;
                }
            }];
            
            [self recordVCName:vcName_Key fps:fps_avg ];
        }];
        
        [self.pageSysDictM removeAllObjects];
    }
}

上传到服务器

- (void)recordVCName:(NSString *)vcName fps:(NSUInteger)fps
{
    NSString *pageType = nil;
    @synchronized (self) {
        pageType = self.vc2PageTypeDictM[vcName];
    }
    if (vcName.length == 0 || pageType.length == 0 || [vcName hasPrefix:@"/"]) return;
    NSString *fpsStr = @(fps).stringValue;
 
 
    //记录到log v2 日志库
    NSDictionary *dict = @{@"fps" : fpsStr};
    [MMAPMLogger trackEvent:Track_Event_FPS_Monitor eventParams:@{@"page_name" : mm_safes(vcName), @"page_type":mm_safes(pageType), @"sys_data" : dict}];
}
上一篇下一篇

猜你喜欢

热点阅读