当前位置: 代码网 > it编程>App开发>苹果IOS > ios利用RunLoop原理实现去监控卡顿实例详解

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

2024年05月19日 苹果IOS 我要评论
一、卡顿问题的几种原因复杂 ui 、图文混排的绘制量过大;在主线程上做网络同步请求;在主线程做大量的 io 操作;运算量过大,cpu 持续高占用;死锁和主子线程抢锁。二、监测卡顿的思路监测fps:fp

一、卡顿问题的几种原因

复杂 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去监控卡顿的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com