注册
iOS

iOS -性能优化 _RunLoop原理去监控

ios 利用RunLoop的原理去监控卡顿

一、卡顿问题的几种原因

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

二、监测卡顿的思路

  1. 监测FPS:FPS 是一秒显示的帧数,也就是一秒内画面变化数量。如果按照动画片来说,动画片的 FPS 就是 24,是达不到 60 满帧的。也就是说,对于动画片来说,24 帧时虽然没有 60 帧时流畅,但也已经是连贯的了,所以并不能说 24 帧时就算是卡住了。 由此可见,简单地通过监视 FPS 是很难确定是否会出现卡顿问题了,所以我就果断弃了通过监视 FPS 来监控卡顿的方案。
  2. 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 出堆栈的信息,从而进一步分析出具体是哪个方法的执行时间过长;

上代码:


//
// SMLagMonitor.h
//
// Created by DaiMing on 16/3/28.
//

#import <Foundation/Foundation.h>

@interface SMLagMonitor : NSObject

+ (instancetype)shareInstance;

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

@end


//
// SMLagMonitor.m
//
// Created by DaiMing on 16/3/28.
//

#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];

 

搞定!

链接:https://www.cnblogs.com/qiyiyifan/p/11089735.html

0 个评论

要回复文章请先登录注册