iOS中的Runloop观察
什么是RunLoop?
Runloop 顾名思义,运行循环,是通过内部维护的事件循环来对事件、消息进行管理的一个对象。App中的定时器、perforSelector、GCD Async Main Queue、事件响应、⼿势识别、界⾯刷新、⽹络请求等等都需要Runloop的支持,如果没有Runloop 我们的应用执行完一段代码就直接退出了,不可能再一直执行并且等着我们去操作。而且Runloop还能提高应用程序的性能,该做事的时候做事,该休息的时候休息。
RunLoop 对象
iOS 平台中有两套 API 可以获取 Runloop 对象: Foundation框架下的NSRunLoop 和 Core Foundation框架下的CFRunLoopRef,NSRunLoop和CFRunLoopRef都代表着RunLoop对象,NSRunLoop是基于CFRunLoopRef的⼀层OC包装,CFRunLoopRef是开源的
#Foundation
[NSRunLoop currentRunLoop]; # 获得当前线程的 Runloop 对象
[NSRunLoop mainRunLoop]; # 获得主线程的 Runloop 对象
#Core Foundation
CFRunLoopGetCurrent(); # 获得当前线程的 Runloop 对象
CFRunLoopGetMain(); # 获得主线程的 Runloop 对象
RunLoop 相关的类
Core Foundation 中与 Runloop 有关系的类有5个:
- CFRunLoopRef
- CFRunLoopModeRef
- CFRunLoopSourceRef
- CFRunLoopTimerRef
- CFRunLoopTimerRef
而 Runloop 对象的结构如下所示
typedef struct __CFRunLoop * CFRunLoopRef;
struct __CFRunLoop {
pthread_t _pthread;
CFMutableSetRef _commomModes;
CFMutableSetRef _commomModesItems;
CFMutableSetRef _modes;
CFRunLoopModeRef _currentMode;
};
typedef struct __CFRunLoopMode * CFRunLoopModeRef;
struct __CFRunLoopMode {
CFStringRef _name;
CFMutableSetRef _source0;
CFMutableSetRef _source1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
};
由以上结构可以看出,每个 Runloop 对象中存在多个 mode(CFRunLoopModeRef),代表 Runloop 的运⾏模式,每个 mode 对象中包含有 source0、source1、observer、timer,他们在对应的 mode 下运行,每个 mode 只处理对应的事情,mode 作用就是把不同模式下的 source0、source1、observer、timer 隔离开来,Runloop启动时只能选择其中⼀个Mode,作为currentMode,如果需要切换 mode,只能退出当前 loop,再重新选择⼀个 mode 进⼊, 如果 mode ⾥没有任何 source0、source1、timer、observer,Runloop会⽴⻢退出。
CFRunLoopModeRef 常见的 mode 有两种
- kCFRunLoopDefaultMode:默认 mode,通常主线程是在这个 mode 下运⾏;
- UITrackingRunLoopMode:界⾯跟踪 mode,⽤于 ScrollView 追踪触摸滑动,保证界⾯滑动时不受其他 mode 影响。
我们又是后会用到的 CommonMode 并不是实际的一种 mode,是同步source、timer、observer到多个mode中的一种技术方案,存在 Runloop 对象的 _commomModes 中。
Mode 中元素的作用
source0 : 处理触摸事件,performSelector:onTherad source1 : 基于port的线程间通信,系统事件的捕捉(比如触摸事件) timers : NSTimer、performSelector:withObject:afterDelay: (里面也有定时器) observers : 用于监听 Runloop 的状态,UI刷新(BeforeWaiting状态)
添加 Observer 监听 Runloop 的状态
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry: {
CFRunLoopMode model = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
NSLog(@"即将进入Loop ---> kCFRunLoopEntry -- %@", model);
CFRelease(model);
break;
}
case kCFRunLoopBeforeTimers: {
CFRunLoopMode model = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
NSLog(@"即将处理Timers ---> kCFRunLoopBeforeTimers -- %@", model);
CFRelease(model);
break;
}
case kCFRunLoopBeforeSources: {
CFRunLoopMode model = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
NSLog(@"即将处理Source ----> kCFRunLoopBeforeSources -- %@", model);
CFRelease(model);
break;
}
case kCFRunLoopBeforeWaiting: {
CFRunLoopMode model = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
NSLog(@"即将进入休眠 -----> kCFRunLoopBeforeWaiting -- %@", model);
CFRelease(model);
break;
}
case kCFRunLoopAfterWaiting: {
CFRunLoopMode model = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
NSLog(@"刚从休眠中唤醒 ----> kCFRunLoopAfterWaiting -- %@", model);
CFRelease(model);
break;
}
case kCFRunLoopExit: {
CFRunLoopMode model = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
NSLog(@"即将退出RunLoop ---> kCFRunLoopExit -- %@", model);
CFRelease(model);
break;
}
default:
break;
}
});
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
CFRelease(observer);
Runloop 主要的点就是 用户态 和 内核态的切换,这里使用的是不公开的系统函数 mach_msg 来变更的状态
RunLoop与线程的关系
- 每条线程都有唯⼀的⼀个与之对应的 Runloop 对象,也就是说线程是 Runloop 是一一对应的;
- Runloop 保存在⼀个全局的 Dictionary ⾥,线程作为 key, Runloop 作为 value;
- 线程刚创建时并没有 Runloop 对象, Runloop 会在第⼀次获取它时创建;
- Runloop 对象会在线程结束时销毁;
- 主线程的 Runloop 已经⾃动获取(创建),⼦线程默认没有开启 Runloop 。
RunLoop 的运行逻辑
01、通知Observers:进入Loop
02、通知Observers:即将处理Timers
03、通知Observers:即将处理Sources
04、处理Blocks
05、处理Source0(可能会再次处理Blocks)
06、如果存在Source1,就跳转到第8步 ----->06跳到08
07、通知Observers:开始休眠(等待消息唤醒)
08、通知Observers:结束休眠(被某个消息唤醒) <-----从06跳到08
---01> 处理Timer
---02> 处理GCD Async To Main Queue
---03> 处理Source1
09、处理Blocks
10、根据前面的执行结果,决定如何操作
---01> 回到第02步
---02> 退出Loop
11、通知Observers:退出Loop
以下是 Runloop 的运行逻辑
RunLoop 的简单应用
- 解决 NSTimer 在滑动时停止工作的问题
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"定时器开始了");
}];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
- 控制线程的生命周期(线程保活)
typedef void (^MyThreadBlock)(void);
@interface MyThread()
@property(nonatomic, strong) Thread *innerThread;
@property(nonatomic, assign, getter=isStopped) BOOL stopped;
@end
@implementation MyThread
#pragma mark - public methods
- (instancetype)init {
if(self = [super init]) {
self.stopped = NO;
__weak typeof(self) weakSelf = self;
self.innerThread = [[Thread alloc] initWithBlock:^{
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
while (weakSelf && !weakSelf.isStopped) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}
return self;
}
- (void)run {
[self.innerThread start];
}
- (void)stop {
if(!self.innerThread) return;
[self performSelector:@selector(__stopAction) onThread:self.innerThread withObject:nil waitUntilDone:YES];
}
- (void)executeTask:(MyThreadBlock)task {
if(!self.innerThread || !task) return;
[self performSelector:@selector(__executeTask:) onThread:self.innerThread withObject:nil waitUntilDone:NO];
}
- (void)dealloc {
NSLog(@"--------> %s", __func__);
[self stop]; //也可以销毁的时候直接关闭,这样外面使用的时候就不用手动关闭了
}
#pragma mark - private methods
- (void)__stopAction {
self.stopped = YES;
CFRunLoopStop(CFRunLoopGetCurrent());
NSLog(@"%s ------- %@", __func__, [NSThread currentThread]);
}
- (void)__executeTask:(MyThreadBlock)task {
task();
}
@end
- 监控性能卡顿
可以添加Observer到主线程RunLoop中,通过监听RunLoop状态切换的耗时,以达到监控卡顿的目的,大部分导致卡顿的的方法是在kCFRunLoopBeforeSources 和 kCFRunLoopAfterWaiting 之间,比如source0主要是处理App内部事件,App自己负责管理(出发),如UIEvent(Touch事件等,GS发起到RunLoop运行再到事件回调到UI)、CFSocketRef。开辟一个子线程,然后实时计算 kCFRunLoopBeforeSources 和 kCFRunLoopAfterWaiting 两个状态区域之间的耗时是否超过某个阀值,来断定主线程的卡顿情况。
@interface DetectTest(){
int timeoutCount;
CFRunLoopObserverRef observer;
@public
dispatch_semaphore_t semaphore;
CFRunLoopActivity activity;
}
@end
@implementation DetectTest
+ (instancetype)shareInstance {
static id instance = nil;
static dispatch_once_t dispatchOnce;
dispatch_once(&dispatchOnce, ^{
instance = [[self alloc] init];
});
return instance;
}
void runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
DetectTest *detect = (__bridge DetectTest*)info;
detect->activity = activity;
dispatch_semaphore_t semaphore = detect->semaphore;
/// 发送信号
dispatch_semaphore_signal(semaphore);
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"kCFRunLoopEntry");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"kCFRunLoopBeforeTimers");
break;
case kCFRunLoopBeforeSources:
NSLog(@"kCFRunLoopBeforeSources");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"kCFRunLoopBeforeWaiting");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"kCFRunLoopAfterWaiting");
break;
case kCFRunLoopExit:
NSLog(@"kCFRunLoopExit");
break;
default:
break;
}
}
- (void)detect {
if (observer) return;
// 信号
semaphore = dispatch_semaphore_create(0);
// 注册RunLoop状态观察
CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL};
//创建Run loop observer对象
//第一个参数用于分配observer对象的内存
//第二个参数用以设置observer所要关注的事件,详见回调函数myRunLoopObserver中注释
//第三个参数用于标识该observer是在第一次进入run loop时执行还是每次进入run loop处理时均执行
//第四个参数用于设置该observer的优先级
//第五个参数用于设置该observer的回调函数
//第六个参数用于设置该observer的运行环境
observer = CFRunLoopObserverCreate(kCFAllocatorDefault,
kCFRunLoopAllActivities,
YES,
0,
&runLoopObserverCallBack,
&context);
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
// 在子线程监控时长
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//子线程开启一个持续的 loop 用来进行监控
while (YES){
//semaphoreWait 信号等待的时间
long semaphoreWait = dispatch_semaphore_wait(self->semaphore, dispatch_time(DISPATCH_TIME_NOW, 50 * NSEC_PER_MSEC));
if (semaphoreWait != 0){
if (!self->observer){
self->timeoutCount = 0;
self->semaphore = 0;
self->activity = 0;
return;
}
//BeforeSources 和 AfterWaiting 这两个状态能够检测到是否卡顿
if (self->activity==kCFRunLoopBeforeSources || self->activity==kCFRunLoopAfterWaiting){
//出现五次出结果
if (++self->timeoutCount < 5) continue;
//将堆栈信息上报服务器的代码放到这里
NSLog(@"monitor trigger");
}
}
self->timeoutCount = 0;
}
});
}
@end
#include <libkern/OSAtomic.h>
#include <execinfo.h>
//获取函数堆栈信息
+ (NSArray *)backtrace {
void* callstack[128];
int frames = backtrace(callstack, 128);//用于获取当前线程的函数调用堆栈,返回实际获取的指针个数
char **strs = backtrace_symbols(callstack, frames);//从backtrace函数获取的信息转化为一个字符串数组
int i;
NSMutableArray *backtrace = [NSMutableArray arrayWithCapacity:frames];
for (i = 0;
i < backtrace.count;
i++) {
[backtrace addObject:[NSString stringWithUTF8String:strs[i]]];
}
free(strs);
return backtrace;
}
// 三方库PLCrashReporter获取堆栈信息
- (void)getBacktrace {
// 获取数据
NSData *lagData = [[[PLCrashReporter alloc] initWithConfiguration:[[PLCrashReporterConfig alloc] initWithSignalHandlerType:PLCrashReporterSignalHandlerTypeBSD symbolicationStrategy:PLCrashReporterSymbolicationStrategyAll]] generateLiveReport];
// 转换成 PLCrashReport 对象
PLCrashReport *lagReport = [[PLCrashReport alloc] initWithData:lagData error:NULL];
// 进行字符串格式化处理
NSString *lagReportString = [PLCrashReportTextFormatter stringValueForCrashReport:lagReport withTextFormat:PLCrashReportTextFormatiOS];
//将字符串上传服务器
NSLog(@"lag happen, detail below: \n %@",lagReportString);
}