iOS 底层系列 - RunLoop

869 阅读8分钟

一、前言

此篇文章主要是对 runloop 基本所有相关知识点进行了整理

二、问题

RunLoop 的作用

  1. 保持程序的持续运行。
  2. 处理App中的各种事件(比如触摸事件、滑动事件、定时器事件、Selector事件)。
  3. 节省Cpu资源,提高程序性能,在于有事情做的时候使的当前NSRunLoop的线程工作,没有事情做让当前NSRunLoop的线程休眠。

Runloop 的获取

  • 第一次进入时,初始化全局Dic,并先为主线程创建一个 RunLoop。
  • 取不到时,创建一个。取到就直接返回。
  • 全局的Dictionary,key 是 pthread_t, value 是 CFRunLoopRef

利用 runloop 解释一下页面的渲染的过程?

  • 当我们调用 [UIView setNeedsDisplay] 时,这时会调用当前 View.layer 的 [view.layer setNeedsDisplay]方法。 这等于给当前的 layer 打上了一个脏标记,而此时并没有直接进行绘制工作。而是会到当前的 Runloop 即将休眠,也就是 beforeWaiting 时才会进行绘制工作。

  • 紧接着会调用 [CALayer display],进入到真正绘制的工作。CALayer 层会判断自己的 delegate 有没有实现异步绘制的代理方法 displayer:,这个代理方法是异步绘制的入口,如果没有实现这个方法,那么会继续进行系统绘制的流程,然后绘制结束。

三、Runloop

Runloop 的理解

  • Runloop 是通过内部维护 事件循环 来 对 消息 管理的一个对象。 类似于 while 循环,但是和普通的循环不一样,runloop 是内核层面的循环,并且在内部会对很多事件进行处理。

  • 一般来讲,一个线程一次只能执行一个任务,执行完成后线程就会退出。如果我们需要一个机制,让线程能随时处理事件但并不退出。这种模型通常被称作 Event Loop。 Event Loop 在很多系统和框架里都有实现,比如 Node.js 的事件处理,比如 Windows 程序的消息循环,再比如 OSX/iOS 里的 RunLoop。实现这种模型的关键点在于:如何管理事件/消息,如何让线程在没有处理消息时休眠以避免资源占用、在有消息到来时立刻被唤醒。

Main.m 默认会开启 Runloop

执行 UIApplicationMain 函数的时候会默认开启 Runloop

RunLoop 与 线程的关系

CFRunLoopMode - 模式

  1. RunLoopDefaultMode: App的默认 Mode,通常主线程是在这个 Mode 下运行的。
  2. UITrackingRunLoopMode: 界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响。
  3. RunLoopCommonModes: 包含以上两种模式。
  4. UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用。
  5. GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到。

CFRunLoopObserver 监听者

每个 Observer 都包含了一个回调(函数指针),当 RunLoop 的状态发生变化时,观察者就能通过回调接受到这个变化。可以观测的时间点有以下几个:

Observer 监听实现

可以通过函数进行监听 runloop 的目前状态,然后做一些事情(例如卡顿率检测,性能优化)

Runloop 运行逻辑

四、Runloop 相关源码

CFRunLoopRun

void CFRunLoopRun(void) {    /* DOES CALLOUT */
    int32_t result;
    do {
        result = CFRunLoopRunSpecific (CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10false);
        CHECK_FOR_FORK();
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}

__CFRunLoopRun

/* rl, rlm are locked on entrance and exit */
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    int32_t retVal = 0;
    do {
        // 通知Observers:即将处理Timers
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);

        // 通知Observers:即将处理Sources
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);

        // 处理Blocks
        __CFRunLoopDoBlocks(rl, rlm);

        // 处理Sources0
        if (__CFRunLoopDoSources0(rl, rlm, stopAfterHandle)) {
            // 处理Blocks
            __CFRunLoopDoBlocks(rl, rlm);
        }

        // 如果有Sources1,就跳转到handle_msg标记处
        if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
            goto handle_msg;
        }

        // 通知Observers:即将休眠
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);

        // 进入休眠,等待其他消息唤醒
        __CFRunLoopSetSleeping(rl);
        __CFPortSetInsert(dispatchPort, waitSet);
        do {
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
        } while (1);

        // 醒来
        __CFPortSetRemove(dispatchPort, waitSet);
        __CFRunLoopUnsetSleeping(rl);

        // 通知Observers:已经唤醒
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);

    handle_msg: // 看看是谁唤醒了RunLoop,进行相应的处理
        if (被Timer唤醒的) {
            // 处理Timer
            __CFRunLoopDoTimers(rl, rlm, mach_absolute_time());
        }
        else if (被GCD唤醒的) {
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
        } else { // 被Sources1唤醒的
            __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply);
        }

        // 执行    Blocks
        __CFRunLoopDoBlocks(rl, rlm);

        // 根据之前的执行结果,来决定怎么做?
        if (sourceHandledThisLoop && stopAfterHandle) {
            retVal = kCFRunLoopRunHandledSource;
        } else if (timeout_context->termTSR < mach_absolute_time()) {
            retVal = kCFRunLoopRunTimedOut;
        } else if (__CFRunLoopIsStopped(rl)) {
            __CFRunLoopUnsetStopped(rl);
            retVal = kCFRunLoopRunStopped;
        } else if (rlm->_stopped) {
            rlm->_stopped = false;
            retVal = kCFRunLoopRunStopped;
        } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
            retVal = kCFRunLoopRunFinished;
        }

    } while (0 == retVal);
    return retVal;
}

CFRunLoopRunSpecific

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {
    // 通知Observers:进入Loop
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);

    // 核心的Loop逻辑
    __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);

    // 通知Observers:退出Loop
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
    return result;
}

CFRunLoopGetCurrent

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {
    // 通知Observers:进入Loop
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);

    // 核心的Loop逻辑
    __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);

    // 通知Observers:退出Loop
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
    return result;
}

五、通过 runloop 实现线程保活

@interface ViewController ()
@property (strongnonatomic) MJThread *thread;
@property (assignnonatomicgetter=isStoped) BOOL stopped;
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];

    __weak typeof(self) weakSelf = self;

    self.stopped = NO;
    self.thread = [[MJThread alloc] initWithBlock:^{
        NSLog(@"%@----begin----", [NSThread currentThread]);

        // 往RunLoop里面添加Source\Timer\Observer
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];

        while (weakSelf && !weakSelf.isStoped) {
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        }

        NSLog(@"%@----end----", [NSThread currentThread]);
    }];
    [self.thread start];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    if (!self.thread) return;

    [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}

// 子线程需要执行的任务
- (void)test
{
    NSLog(@"%s %@", __func__, [NSThread currentThread]);
}

- (IBAction)stop {
    if (!self.thread) return;

    // 在子线程调用stop(waitUntilDone设置为YES,代表子线程的代码执行完毕后,这个方法才会往下走)
    [self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:YES];
}

// 用于停止子线程的RunLoop
- (void)stopThread
{
    // 设置标记为YES
    self.stopped = YES;

    // 停止RunLoop
    CFRunLoopStop(CFRunLoopGetCurrent());
    NSLog(@"%s %@", __func__, [NSThread currentThread]);

    // 清空线程
    self.thread = nil;
}