先来聊聊RunLoop,Runloop是一个接收处理消息的对象,它通过入口函数来启动执行Event loop模型逻辑.RunloopiOS中只能在当前线程中获取,不能手动创建(主线程的RunLoop可以在子线程中获取).获取的时候系统会自动创建.
RunLoop的内部结构如下:
Observer主要用来得知RunLoop不同时期的状态,当RunLoop状态改变后会通知Observer,Observer回执行回调.我们获取或者知道这些状态什么时候触发,可以去做很多操作和优化.
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry , // 进入 loop
kCFRunLoopBeforeTimers , // 即将触发 Timer 回调
kCFRunLoopBeforeSources , // 即将触发 Source0 回调
kCFRunLoopBeforeWaiting , // 等待 mach_port 消息,进入休眠
kCFRunLoopAfterWaiting ), // 唤醒,接收 mach_port 消息
kCFRunLoopExit , // 退出 loop
kCFRunLoopAllActivities // loop 所有状态改变
}
Source是输入源,其实就是处理事件的回调.它有两个版本:一个是Source0,一个是Source1.其中Source0是非基于端口的输入源,主要用于处理内部事件,App自己管理的事件,比如:UIEvent、UITouch等(其实是先触发source1,source1的回调再触发source0,source0通过UIApplication队列分发事件).可以自定义,需手动唤醒,自定义输入源是先被标记为待处理,然后再唤醒Runloop处理. Source1是由XNU内核管理,Mach_Port来驱动使用当.触发trap内核会被唤醒,mach_msg()方法会从用户态切换到内核态,内核态中的mach_msg()会完成实际任务.
Timer就是定时相关的了,比如NSTimer就会把不同的时间点注册到RunLoop中,定时唤醒Port,处理消息.
Runloop内部逻辑:
- 1、启动runloop;
- //进入do-while循环.{
- 2、通知observer即将启动定时器;
- 3、通知observer即将启非基于端口的输入源;
- 4、启动非基于端口的输入源;
- 5、如果有基于端口的输入源准备就绪,就跳转到9处理消息;
- 6、通知observer即将进入休眠;
- 7、休眠等待唤醒;
- 8、通知observer即将被唤醒;
- 9、处理消息事件,跳回2;
- }
- 10、退出runloop;
下面是通过监控主线程RunLoop监控卡顿的代码,及详细注释:
@interface MCXLagMonitor(){
CFRunLoopObserverRef runLoopObserver;
int timeoutCounting;//超时计数
dispatch_semaphore_t dispatchSemaphore; //信号量
CFRunLoopActivity runLoopActivity; //RunLoop的状态
}
@end
@implementation MCXLagMonitor
+ (id)shareInstance {
static id instance = nil;
static dispatch_once_t dispatchOnce;
dispatch_once(&dispatchOnce, ^{
instance = [[self alloc]init];
});
return instance;
}
- (void)beginMonitor{
CFRunLoopObserverContext context = {0, (__bridge void *)(self), NULL, NULL}; //context是一个结构体,
第二个info参数会传到CFRunLoopObserverCreate的callout的info中.
dispatchSemaphore = dispatch_semaphore_create(0);//创建信号量
runLoopObserver = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, &runLoopObserverCallBack, &context);//参数分别是: 分配空间 状态枚举 是否循环调用observer 优先级 回调函数 结构体
CFRunLoopAddObserver(CFRunLoopGetMain(), runLoopObserver, kCFRunLoopCommonModes);//添加到主线程的RunLoop
dispatch_async(dispatch_get_global_queue(0, 0), ^{//开启监控子线程
while (YES) {//loop
long semphoreWait = dispatch_semaphore_wait(self->dispatchSemaphore, dispatch_time(DISPATCH_TIME_NOW, 1*NSEC_PER_SEC));//信号量为0的时候会等待1秒再执行后面的代码,即1秒为超时时间.如果信号量为0等待超时后该方法就返回非零,否则返回0,也就是说信号量在观察到RunLoop变化的时候会执行callout信号量+1,然后该方法-1返回0,继续执行下面方法.另外,1秒timeout的阻塞过程中,如果信号量因状态改变增量,就直接返回0执行后面代码.
if (semphoreWait == 0) {
self->timeoutCounting = 0;
}else{
if (!self->runLoopObserver) {
self->dispatchSemaphore = 0;
self->timeoutCounting = 0;
self->runLoopActivity = 0;
}
if (self->runLoopActivity == kCFRunLoopBeforeSources || self->runLoopActivity == kCFRunLoopAfterWaiting) {//RunLoop两个状态,如果触发即将进入source0状态后一直没有进入下一个BeforeWaiting状态,那说明方法执行时间过长. 然后是AfterWaiting也就是即将唤醒状态,如果这个状态持续时间过久,说明调用mach_msg 等待接受mach_port的消息时间过长而无法进入下一状态.他们的表现就是阻塞主线程,造成卡顿,通过监控它们来监控卡顿.
if (++self->timeoutCounting<3) {//超过3s上报堆栈信息 如果觉得长的话,可以把上面的时间改成纳秒、毫秒等
continue;
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{//再开启一个子线程来上报堆栈信息
NSLog(@"堆栈信息");
});
}
}
}
});
}
- (void)endMonitor{
if (!runLoopObserver) {
return;
}
CFRunLoopRemoveObserver(CFRunLoopGetMain(), runLoopObserver, kCFRunLoopCommonModes);
CFRelease(runLoopObserver);
runLoopObserver = NULL;
}
static void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){
MCXLagMonitor *monitor = (__bridge MCXLagMonitor *)(info);//桥接self
monitor->runLoopActivity = activity;
dispatch_semaphore_signal(monitor->dispatchSemaphore);//信号量+1
}