iOS中的Runloop观察

标签: 编程学习 iOS学习
什么是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个:

  1. CFRunLoopRef
  2. CFRunLoopModeRef
  3. CFRunLoopSourceRef
  4. CFRunLoopTimerRef
  5. 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 有两种

  1. kCFRunLoopDefaultMode:默认 mode,通常主线程是在这个 mode 下运⾏;
  2. 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与线程的关系
  1. 每条线程都有唯⼀的⼀个与之对应的 Runloop 对象,也就是说线程是 Runloop 是一一对应的;
  2. Runloop 保存在⼀个全局的 Dictionary ⾥,线程作为 key, Runloop 作为 value;
  3. 线程刚创建时并没有 Runloop 对象, Runloop 会在第⼀次获取它时创建;
  4. Runloop 对象会在线程结束时销毁;
  5. 主线程的 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);
}