站着吹不动 發表於 2022-9-8 16:58:08

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

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li>一、卡顿问题的几种原因</li><li>二、监测卡顿的思路</li><ul class="second_class_ul"><li>监测FPS:</li><li>RunLoop:</li></ul><li>三、如何检查卡顿</li><ul class="second_class_ul"></ul></ul></div><p class="maodian"></p><h2>一、卡顿问题的几种原因</h2>
<p>复杂 UI 、图文混排的绘制量过大;</p>
<p>在主线程上做网络同步请求;</p>
<p>在主线程做大量的 IO 操作;</p>
<p>运算量过大,CPU 持续高占用;</p>
<p>死锁和主子线程抢锁。</p>
<p class="maodian"></p><h2>二、监测卡顿的思路</h2>
<p class="maodian"></p><h3>监测FPS:</h3>
<p>FPS 是一秒显示的帧数,也就是一秒内画面变化数量。如果按照动画片来说,动画片的 FPS 就是 24,是达不到 60 满帧的。也就是说,对于动画片来说,24 帧时虽然没有 60 帧时流畅,但也已经是连贯的了,所以并不能说 24 帧时就算是卡住了。 由此可见,简单地通过监视 FPS 是很难确定是否会出现卡顿问题了,所以我就果断弃了通过监视 FPS 来监控卡顿的方案。</p>
<p class="maodian"></p><h3>RunLoop:</h3>
<p>通过监控 RunLoop 的状态来判断是否会出现卡顿。RunLoop原理这里就不再多说,主要说方法,首先明确loop的状态有六个</p>
<div class="jb51code"><pre class="brush:cpp;">typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry , // 进入 loop
    kCFRunLoopBeforeTimers , // 触发 Timer 回调
    kCFRunLoopBeforeSources , // 触发 Source0 回调
    kCFRunLoopBeforeWaiting , // 等待 mach_port 消息
    kCFRunLoopAfterWaiting ), // 接收 mach_port 消息
    kCFRunLoopExit , // 退出 loop
    kCFRunLoopAllActivities// loop 所有状态改变
}
</pre></div>
<p>我们需要监测的状态有两个:RunLoop 在进入睡眠之前和唤醒后的两个 loop 状态定义的值,分别是 kCFRunLoopBeforeSources 和 kCFRunLoopAfterWaiting ,也就是要触发 Source0 回调和接收 mach_port 消息两个状态。</p>
<p class="maodian"></p><h2>三、如何检查卡顿</h2>
<p>说下步骤:</p>
<p>创建一个 CFRunLoopObserverContext 观察者;</p>
<p>将创建好的观察者 runLoopObserver 添加到主线程 RunLoop 的 common 模式下观察;</p>
<p>创建一个持续的子线程专门用来监控主线程的 RunLoop 状态;</p>
<p>一旦发现进入睡眠前的 kCFRunLoopBeforeSources 状态,或者唤醒后的状态 kCFRunLoopAfterWaiting,在设置的时间阈值内一直没有变化,即可判定为卡顿;</p>
<p>dump 出堆栈的信息,从而进一步分析出具体是哪个方法的执行时间过长;</p>
<p>上代码:</p>
<div class="jb51code"><pre class="brush:cpp;">#import &lt;Foundation/Foundation.h&gt;
@interface SMLagMonitor : NSObject
+ (instancetype)shareInstance;
- (void)beginMonitor; //开始监视卡顿
- (void)endMonitor;   //停止监视卡顿
@end
</pre></div>
<div class="jb51code"><pre class="brush:cpp;">#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(&amp;dispatchOnce, ^{
      instance = [ 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,
                                              &amp;runLoopObserverCallBack,
                                              &amp;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 &lt; 3) {
//                        continue;
//                  }
                  NSLog(@"monitor trigger");
                  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
//                        ;
                  });
                } //end activity
            }// end semaphore wait
            timeoutCount = 0;
      }// end while
    });
}
- (void)endMonitor {
    ;
    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-&gt;runLoopActivity = activity;
    dispatch_semaphore_t semaphore = lagMonitor-&gt;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, &amp;threads, &amp;threadCount);
    if (kr != KERN_SUCCESS) {
      return;
    }
    for (int i = 0; i &lt; 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, THREAD_BASIC_INFO, (thread_info_t)threadInfo, &amp;threadInfoCount) == KERN_SUCCESS) {
            threadBaseInfo = (thread_basic_info_t)threadInfo;
            if (!(threadBaseInfo-&gt;flags &amp; TH_FLAGS_IDLE)) {
                integer_t cpuUsage = threadBaseInfo-&gt;cpu_usage / 10;
                if (cpuUsage &gt; 70) {
                  //cup 消耗大于 70 时打印和记录堆栈
                  NSString *reStr = smStackOfThread(threads);
                  //记录数据库中
//                  [[ increaseWithStackString:reStr] subscribeNext:^(id x) {}];
                  NSLog(@"CPU useage overload thread stack:\n%@",reStr);
                }
            }
      }
    }
}
@end
</pre></div>
<p>使用,直接在APP didFinishLaunchingWithOptions 方法里面这样写:</p>
<div class="jb51code"><pre class="brush:cpp;">[ beginMonitor];</pre></div>
<p>以上就是ios利用RunLoop原理实现去监控卡顿实例详解的详细内容,更多关于ios RunLoop去监控卡顿的资料请关注琼殿技术社区其它相关文章!</p>
                           
                            <div class="art_xg">
                              <b>您可能感兴趣的文章:</b><ul><li>iOS开发runloop运行循环机制学习</li><li>EvenLoop模型在iOS的RunLoop应用示例</li><li>分析IOS RunLoop的事件循环机制</li><li>IOS开发之多线程NSThiread GCD NSOperation Runloop</li></ul>
                            </div>

                        </div>
                        <!--endmain-->
頁: [1]
查看完整版本: ios利用RunLoop原理实现去监控卡顿实例详解