欢迎访问移动开发之家(rcyd.net),关注移动开发教程。移动开发之家  移动开发问答|  每日更新
页面位置 : > > > 内容正文

ios利用RunLoop原理实现去监控卡顿实例详解,

来源: 开发者 投稿于  被查看 21280 次 评论:55

ios利用RunLoop原理实现去监控卡顿实例详解,


目录
  • 一、卡顿问题的几种原因
  • 二、监测卡顿的思路
    • 监测FPS:
    • RunLoop:
  • 三、如何检查卡顿

    一、卡顿问题的几种原因

    复杂 UI 、图文混排的绘制量过大;

    在主线程上做网络同步请求;

    在主线程做大量的 IO 操作;

    运算量过大,CPU 持续高占用;

    死锁和主子线程抢锁。

    二、监测卡顿的思路

    监测FPS:

    FPS 是一秒显示的帧数,也就是一秒内画面变化数量。如果按照动画片来说,动画片的 FPS 就是 24,是达不到 60 满帧的。也就是说,对于动画片来说,24 帧时虽然没有 60 帧时流畅,但也已经是连贯的了,所以并不能说 24 帧时就算是卡住了。 由此可见,简单地通过监视 FPS 是很难确定是否会出现卡顿问题了,所以我就果断弃了通过监视 FPS 来监控卡顿的方案。

    RunLoop:

    通过监控 RunLoop 的状态来判断是否会出现卡顿。RunLoop原理这里就不再多说,主要说方法,首先明确loop的状态有六个

    typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
        kCFRunLoopEntry , // 进入 loop
        kCFRunLoopBeforeTimers , // 触发 Timer 回调
        kCFRunLoopBeforeSources , // 触发 Source0 回调
        kCFRunLoopBeforeWaiting , // 等待 mach_port 消息
        kCFRunLoopAfterWaiting ), // 接收 mach_port 消息
        kCFRunLoopExit , // 退出 loop
        kCFRunLoopAllActivities  // loop 所有状态改变
    }
    

    我们需要监测的状态有两个:RunLoop 在进入睡眠之前和唤醒后的两个 loop 状态定义的值,分别是 kCFRunLoopBeforeSources 和 kCFRunLoopAfterWaiting ,也就是要触发 Source0 回调和接收 mach_port 消息两个状态。

    三、如何检查卡顿

    说下步骤:

    创建一个 CFRunLoopObserverContext 观察者;

    将创建好的观察者 runLoopObserver 添加到主线程 RunLoop 的 common 模式下观察;

    创建一个持续的子线程专门用来监控主线程的 RunLoop 状态;

    一旦发现进入睡眠前的 kCFRunLoopBeforeSources 状态,或者唤醒后的状态 kCFRunLoopAfterWaiting,在设置的时间阈值内一直没有变化,即可判定为卡顿;

    dump 出堆栈的信息,从而进一步分析出具体是哪个方法的执行时间过长;

    上代码:

    #import <Foundation/Foundation.h>
    @interface SMLagMonitor : NSObject
    + (instancetype)shareInstance;
    - (void)beginMonitor; //开始监视卡顿
    - (void)endMonitor;   //停止监视卡顿
    @end
    
    #import "SMLagMonitor.h"
    #import "SMCallStack.h"
    #import "SMCPUMonitor.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 {
        //监测 CPU 消耗
        self.cpuMonitorTimer = [NSTimer scheduledTimerWithTimeInterval:3
                                                                 target:self
                                                               selector:@selector(updateCPUInfo)
                                                               userInfo:nil
                                                                repeats:YES];
        //监测卡顿
        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, 20*NSEC_PER_MSEC));
                if (semaphoreWait != 0) {
                    if (!runLoopObserver) {
                        timeoutCount = 0;
                        dispatchSemaphore = 0;
                        runLoopActivity = 0;
                        return;
                    }
                    //两个runloop的状态,BeforeSources和AfterWaiting这两个状态区间时间能够检测到是否卡顿
                    if (runLoopActivity == kCFRunLoopBeforeSources || runLoopActivity == kCFRunLoopAfterWaiting) {
                        // 将堆栈信息上报服务器的代码放到这里
                        //出现三次出结果
    //                    if (++timeoutCount < 3) {
    //                        continue;
    //                    }
                        NSLog(@"monitor trigger");
                        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
    //                        [SMCallStack callStackWithType:SMCallStackTypeAll];
                        });
                    } //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);
    }
    - (void)updateCPUInfo {
        thread_act_array_t threads;
        mach_msg_type_number_t threadCount = 0;
        const task_t thisTask = mach_task_self();
        kern_return_t kr = task_threads(thisTask, &threads, &threadCount);
        if (kr != KERN_SUCCESS) {
            return;
        }
        for (int i = 0; i < threadCount; i++) {
            thread_info_data_t threadInfo;
            thread_basic_info_t threadBaseInfo;
            mach_msg_type_number_t threadInfoCount = THREAD_INFO_MAX;
            if (thread_info((thread_act_t)threads[i], THREAD_BASIC_INFO, (thread_info_t)threadInfo, &threadInfoCount) == KERN_SUCCESS) {
                threadBaseInfo = (thread_basic_info_t)threadInfo;
                if (!(threadBaseInfo->flags & TH_FLAGS_IDLE)) {
                    integer_t cpuUsage = threadBaseInfo->cpu_usage / 10;
                    if (cpuUsage > 70) {
                        //cup 消耗大于 70 时打印和记录堆栈
                        NSString *reStr = smStackOfThread(threads[i]);
                        //记录数据库中
    //                    [[[SMLagDB shareInstance] increaseWithStackString:reStr] subscribeNext:^(id x) {}];
                        NSLog(@"CPU useage overload thread stack:\n%@",reStr);
                    }
                }
            }
        }
    }
    @end
    

    使用,直接在APP didFinishLaunchingWithOptions 方法里面这样写:

    [[SMLagMonitor shareInstance] beginMonitor];

    以上就是ios利用RunLoop原理实现去监控卡顿实例详解的详细内容,更多关于ios RunLoop去监控卡顿的资料请关注3672js教程其它相关文章!

    您可能感兴趣的文章:
    • iOS开发runloop运行循环机制学习
    • EvenLoop模型在iOS的RunLoop应用示例
    • 分析IOS RunLoop的事件循环机制
    • IOS开发之多线程NSThiread GCD NSOperation Runloop

    用户评论