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

 更新时间:2022年09月08日 16:58:08   作者:奶茶大叔  
这篇文章主要为大家介绍了ios利用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去监控卡顿的资料请关注脚本之家其它相关文章!

相关文章

  • 关于iOS中的各种颜色设置总结大全(推荐)

    关于iOS中的各种颜色设置总结大全(推荐)

    这篇文章主要给大家介绍了关于iOS中颜色设置的相关资料,其中包括导航栏、状态栏、Tabbar、Button、TextField、AttributedString和通用部分的颜色设置方法示例,对大家具有一定的参考学习价值,需要的朋友们下面随着小编来一起看看吧。
    2017-09-09
  • iOS UIWebView 通过 cookie 完成自动登录实例

    iOS UIWebView 通过 cookie 完成自动登录实例

    本篇文章主要介绍了iOS UIWebView 通过 cookie 完成自动登录实例,具有一定的参考价值,感兴趣的小伙伴们可以参考一下。
    2017-04-04
  • iOS UITextField最大字符数和字节数的限制详解

    iOS UITextField最大字符数和字节数的限制详解

    在开发中我们经常遇到这样的需求:在UITextField或者UITextView中限制用户可以输入的最大字符数。但在UITextView , UITextfield 中有很多坑,网上的方法也很多。但是并不是很全面吧,这里全面进行了总结,有需要的朋友们可以参考借鉴,下面跟着小编一起来学习学习吧。
    2016-11-11
  • 关于iOS截图你应该知道的那些事儿

    关于iOS截图你应该知道的那些事儿

    这篇文章主要给大家介绍了关于iOS截图你应该知道的那些事儿,文中通过示例代码介绍的非常详细,对各位iOS开发者们的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2018-06-06
  • iOS仿擦玻璃效果的实现方法

    iOS仿擦玻璃效果的实现方法

    最近在网上看到一个博客分享的这个效果很不错,就拿下来看看,结果看了好几遍也没完全看懂,再结合自己之前学的东西感觉不用这么复杂也能实现同样的效果,于是就开始动手了。现在将实现的步骤和示例代码分享给大家,有需要的朋友们可以参考借鉴。
    2016-10-10
  • iOS开发中对文件目录的访问及管理的基本方法小结

    iOS开发中对文件目录的访问及管理的基本方法小结

    这篇文章主要介绍了iOS开发中对文件目录的访问及管理的基本方法小结,代码基于传统的Objective-C,需要的朋友可以参考下
    2015-10-10
  • iOS应用中UILabel文字显示效果的常用设置总结

    iOS应用中UILabel文字显示效果的常用设置总结

    UILabel组件可以用来设置文字内容的排版与字体效果等,功能非常多,下面就来为大家整理一下基本的iOS应用中UILabel文字显示效果的常用设置总结
    2016-05-05
  • IOS ObjectC与javascript交互详解及实现代码

    IOS ObjectC与javascript交互详解及实现代码

    这篇文章主要介绍了IOS OC与js交互详解及实现代码的相关资料,需要的朋友可以参考下
    2017-03-03
  • iOS开发中使用UIScrollView实现图片轮播和点击加载

    iOS开发中使用UIScrollView实现图片轮播和点击加载

    这篇文章主要介绍了iOS开发中使用UIScrollView实现图片轮播和点击加载的方法,代码基于传统的Objective-C,需要的朋友可以参考下
    2015-12-12
  • 详解iOS页面传值(顺传 逆传)

    详解iOS页面传值(顺传 逆传)

    本文主要介绍了iOS页面传值(顺传 逆传)的方法。具有一定的参考价值,下面跟着小编一起来看下吧
    2017-01-01

最新评论