美文网首页iOS学习笔记iOS学习开发good
如何利用 RunLoop 监控 app 卡顿

如何利用 RunLoop 监控 app 卡顿

作者: 王技术 | 来源:发表于2019-12-08 17:32 被阅读0次

本文意义在分析如何利用runloop监控卡顿。代码可以看戴铭大佬的代码

卡顿问题的几种原因

  • 复杂 UI 、图文混排的绘制量过大
  • 在主线程上做网络同步请求
  • 在主线程做大量的 IO 操作
  • 运算量过大,CPU 持续高占用
  • 死锁和主子线程抢锁

Runloop 监控卡顿

runloop 工作流程


runloop 工作流程

首先明确了 Runloop的状态有六个

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {

    kCFRunLoopEntry , // 进入 loop

    kCFRunLoopBeforeTimers , // 触发 Timer 回调

    kCFRunLoopBeforeSources , // 触发 Source0 回调

    kCFRunLoopBeforeWaiting , // 等待 mach_port 消息

    kCFRunLoopAfterWaiting ), // 接收 mach_port 消息

    kCFRunLoopExit , // 退出 loop

    kCFRunLoopAllActivities  // loop 所有状态改变

}

上图可以看出
Runloop 真正处理事务的状态区间是
KCFRunloopBeforeSources->KCFRunLoopBeforeWaiting
kCFRunLoopAfterWaiting-> kCFRunLoopBeforeTimers

所以我们监听 RunLoop 在进入睡眠之前和唤醒之后的两个状态,分别是 kCFRunLoopBeforeSourceskCFRunLoopAfterWaiting
也就是要触发 Source0 回调和接收 mach_port 消息两个状态。

大致思路
  • 创建一个 CFRunLoopObserverContext 观察者
  • 将创建好的观察者 runLoopObserver 添加到主线程 RunLoop 的 common 模式下观察
  • 每当监听到 Observer 通知,使信号量的值 +1
  • 创建一个持续的子线程使用信号量专门用来监控主线程的 RunLoop 状态,设置信号量的等待时间
  • 如过等待时间内子线程还没有被唤醒,则认为发生了卡顿

上代码 :

#import <Foundation/Foundation.h>

@interface SMLagMonitor : NSObject

+ (instancetype)shareInstance;

- (void)beginMonitor; //开始监视卡顿
- (void)endMonitor;   //停止监视卡顿

@end
#import "SMLagMonitor.h"

@interface SMLagMonitor() {
    int timeoutCount;
    CFRunLoopObserverRef runLoopObserver;
    @public
    dispatch_semaphore_t dispatchSemaphore;
    CFRunLoopActivity runLoopActivity;
}
@property (nonatomic, strong) NSTimer *cpuMonitorTimer;
@end

@implementation SMLagMonitor

#pragma mark - Interface
+ (instancetype)shareInstance {
    static id instance = nil;
    static dispatch_once_t dispatchOnce;
    dispatch_once(&dispatchOnce, ^{
        instance = [[self alloc] init];
    });
    return instance;
}

- (void)beginMonitor {
    //监测卡顿
    if (runLoopObserver) {
        return;
    }
    dispatchSemaphore = dispatch_semaphore_create(0); //Dispatch Semaphore保证同步
    //创建一个观察者
    CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL};
    runLoopObserver = CFRunLoopObserverCreate(kCFAllocatorDefault,
                                              kCFRunLoopAllActivities,
                                              YES,
                                              0,
                                              &runLoopObserverCallBack,
                                              &context);
    //将观察者添加到主线程runloop的common模式下的观察中
    CFRunLoopAddObserver(CFRunLoopGetMain(), runLoopObserver, kCFRunLoopCommonModes);
    
    //创建子线程监控
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        //子线程开启一个持续的loop用来进行监控
        while (YES) {
            long semaphoreWait = dispatch_semaphore_wait(dispatchSemaphore, dispatch_time(DISPATCH_TIME_NOW, 50*NSEC_PER_MSEC));
            //  semaphoreWait 的值不为 0, 说明线程被堵塞
            if (semaphoreWait != 0) {
                if (!runLoopObserver) {
                    timeoutCount = 0;
                    dispatchSemaphore = 0;
                    runLoopActivity = 0;
                    return;
                }
                // BeforeSources和 AfterWaiting 这两个 runloop 状态的区间时间能够检测到是否卡顿
                if (runLoopActivity == kCFRunLoopBeforeSources || runLoopActivity == kCFRunLoopAfterWaiting) {
                    // 将堆栈信息上报服务器的代码放到这里
                    if (++ timeoutCount < 5) { //连续5次就是250毫秒
                        continue;
                    } else {
                        NSLog(@"卡顿了");
                    }
                } //end activity
            }// end semaphore wait
            timeoutCount = 0;
        }// end while
    });
    
}

- (void)endMonitor {
    [self.cpuMonitorTimer invalidate];
    if (!runLoopObserver) {
        return;
    }
    CFRunLoopRemoveObserver(CFRunLoopGetMain(), runLoopObserver, kCFRunLoopCommonModes);
    CFRelease(runLoopObserver);
    runLoopObserver = NULL;
}

#pragma mark - Private

static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){
    SMLagMonitor *lagMonitor = (__bridge SMLagMonitor*)info;
    lagMonitor->runLoopActivity = activity;
    
    dispatch_semaphore_t semaphore = lagMonitor->dispatchSemaphore;
    dispatch_semaphore_signal(semaphore);
}

@end

相关文章

  • 如何利用 RunLoop 监控 app 卡顿

    本文意义在分析如何利用runloop监控卡顿。代码可以看戴铭大佬的代码 卡顿问题的几种原因 复杂 UI 、图文混排...

  • 戴铭(iOS开发课)读书笔记:13章节-卡顿监控

    原文链接:如何利用 RunLoop 原理去监控卡顿? 前言 一个App想要提升用户体验最重要的就是 降低程序崩溃 ...

  • RunLoop监控app卡顿

    本文意义在分析如何利用runloop监控卡顿。代码可以看戴铭大佬的代码 思路 首先思路就是利用CFRunloopO...

  • iOS通过runloop监控卡顿

    质量监控-卡顿检测iOS实时卡顿监控基于Runloop简单监测iOS卡顿的demo微信iOS卡顿监控系统iOS-R...

  • 利用 RunLoop 监控卡顿

    导致卡顿问题的几种原因: 复杂 UI 、图文混排的绘制量过大; 在主线程上做网络同步请求; 在主线程做大量的 IO...

  • 常规优化技巧

    卡顿优化 添加Observer到主线程RunLoop中,通过监听RunLoop状态切换的耗时,以达到监控卡顿的目的...

  • 卡顿监控

    App层面监控卡顿需要准确分析卡顿发生在什么函数,资源占用情况如何,目前业界两种主流有效的app监控方式如下:1、...

  • Runloop 之 监控app应用卡顿

    卡顿问题的原因: 复杂 UI 、图文混排的绘制量过大 在主线程上做网络同步请求 在主线程做大量的 IO 操作 运算...

  • iOS常规的优化技巧

    原文地址 卡顿优化 添加Observer到主线程RunLoop中,通过监听RunLoop状态切换的耗时,以达到监控...

  • 13 | 如何利用 RunLoop 原理去监控卡顿?

    卡顿问题,就是在主线程上无法响应用户交互的问题。现在,我们先来看一下导致卡顿问题的几种原因:1.复杂 UI 、图文...

网友评论

    本文标题:如何利用 RunLoop 监控 app 卡顿

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