理解runloop

175 阅读5分钟

1.定义

RunLoop就是控制线程生命周期并接收事件进行处理的机制,RunLoop是iOS事件响应与任务处理最核心的机制,它贯穿iOS整个系统.

2.机制

通过RunLoop机制实现省电,流畅,响应速度快,用户体验好.

3.与线程的关系

  • 苹果不允许直接创建 RunLoop,它只提供了两个自动获取的函数:CFRunLoopGetMain() 和 CFRunLoopGetCurrent()。
  • 线程和 RunLoop 是一一对应的,其关系是保存在一个全局的 Dictionary 里。
  • 只能在当前线程中操作当前线程的RunLoop,而不能去操作其他线程的RunLoop。
  • RunLoop对象在第一次获取RunLoop时创建,销毁则是在线程结束的时候。
  • 主线程的RunLoop对象系统自动帮助我们创建好了,而子线程的RunLoop对象需要我们主动获取,因为子线程刚创建时并没有 RunLoop,如果你不主动获取,那它一直都不会有。
  • 4.涉及到的相关类
    • CFRunLoopRef:RunLoop对象
    • CFRunLoopModeRef:运行模式
    • CFRunLoopSourceRef:输入源/事件源
    • CFRunLoopTimerRef:定时源
    • CFRunLoopObserverRef:观察者

    CFRunLoopSourceRef 是事件产生的地方。Source有两个版本:Source0 和 Source1。

    • Source0 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。

    • Source1 包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程,其原理在下面会讲到。

    CFRunLoopTimerRef 是基于时间的触发器,它和 NSTimer 是toll-free bridged 的,可以混用。其包含一个时间长度和一个回调(函数指针)。当其加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调。

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

    typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0), // 即将进入Loop
    kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理 Timer
    kCFRunLoopBeforeSources = (1UL << 2), // 即将处理 Source
    kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠
    kCFRunLoopAfterWaiting = (1UL << 6), // 刚从休眠中唤醒
    kCFRunLoopExit = (1UL << 7), // 即将退出Loop
    };

    上面的 Source/Timer/Observer 被统称为 mode item,一个 item 可以被同时加入多个 mode。但一个 item 被重复加入同一个 mode 时是不会有效果的。如果一个 mode 中一个 item 都没有,则 RunLoop 会直接退出,不进入循环。

    5.内部逻辑

      blob.png

    内部代码整理如下:

    /// 用DefaultMode启动
    void CFRunLoopRun(void) {
    CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10,false);
    }
    /// 用指定的Mode启动,允许设置RunLoop超时时间
    int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) {
    return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
    }
    /// RunLoop的实现
    int CFRunLoopRunSpecific(runloop, modeName, seconds, stopAfterHandle) {
    /// 首先根据modeName找到对应mode
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false);
    /// 如果mode里没有source/timer/observer, 直接返回。
    if (__CFRunLoopModeIsEmpty(currentMode)) return;
    /// 1. 通知 Observers: RunLoop 即将进入 loop。
    __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry);
    /// 内部函数,进入loop
    __CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled) {
    Boolean sourceHandledThisLoop = NO;
    int retVal = 0;
    do {
    /// 2. 通知 Observers: RunLoop 即将触发 Timer 回调。
    __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers);
    /// 3. 通知 Observers: RunLoop 即将触发 Source0 (非port) 回调。
    __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources);
    /// 执行被加入的block__CFRunLoopDoBlocks(runloop, currentMode);
    /// 4. RunLoop 触发 Source0 (非port) 回调。
    sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle);
    /// 执行被加入的block__CFRunLoopDoBlocks(runloop, currentMode);
    /// 5. 如果有 Source1 (基于port) 处于 ready 状态,直接处理这个 Source1 然后跳转去处理消息。
    if (__Source0DidDispatchPortLastTime) {
    Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg)
    if (hasMsg) goto handle_msg;
    }
    /// 通知 Observers: RunLoop 的线程即将进入休眠(sleep)。
    if (!sourceHandledThisLoop) {
    __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);
    }
    /// 7. 调用 mach_msg 等待接受 mach_port 的消息。线程将进入休眠, 直到被下面某一个事件唤醒。
    /// ? 一个基于 port 的Source 的事件。
    /// ? 一个 Timer 到时间了
    /// ? RunLoop 自身的超时时间到了
    /// ? 被其他什么调用者手动唤醒
    __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) {
    mach_msg(msg, MACH_RCV_MSG, port); // thread wait for receive msg
    }
    /// 8. 通知 Observers: RunLoop 的线程刚刚被唤醒了。
    __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting);
    /// 收到消息,处理消息。handle_msg:
    /// 9.1 如果一个 Timer 到时间了,触发这个Timer的回调。
    if (msg_is_timer) {
    __CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time())
    }
    /// 9.2 如果有dispatch到main_queue的block,执行block。
    else if (msg_is_dispatch) {
    __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
    }
    /// 9.3 如果一个 Source1 (基于port) 发出事件了,处理这个事件
    else {
    CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort);
    sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg);
    if (sourceHandledThisLoop) {
    mach_msg(reply, MACH_SEND_MSG, reply);
    }
    }
    /// 执行加入到Loop的block
    __CFRunLoopDoBlocks(runloop, currentMode);
    if (sourceHandledThisLoop && stopAfterHandle) {
    /// 进入loop时参数说处理完事件就返回。
    retVal = kCFRunLoopRunHandledSource;
    } else if (timeout) {
    /// 超出传入参数标记的超时时间了
    retVal = kCFRunLoopRunTimedOut;
    } else if (__CFRunLoopIsStopped(runloop)) {
    /// 被外部调用者强制停止了retVal = kCFRunLoopRunStopped;
    } else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) {
    /// source/timer/observer一个都没有了
    retVal = kCFRunLoopRunFinished;
    }
    /// 如果没超时,mode里没空,loop也没被停止,那继续loop。
    } while (retVal == 0);}
    /// 10. 通知 Observers: RunLoop 即将退出。
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);}

    具体执行逻辑:

  • 1、通知观察者 RunLoop 已经启动。
  • 2、通知观察者即将要开始定时器。
  • 3、通知观察者任何即将启动的非基于端口的源。
  • 4、启动任何准备好的非基于端口的源(Source0)。
  • 5、如果基于端口的源(Source1)准备好并处于等待状态,进入步骤9。
  • 6、通知观察者线程进入休眠状态。
  • 7、将线程置于休眠状态,知道下面的任一事件发生才唤醒线程。
    . 某一事件到达基于端口的源
    . 定时器启动。
    . RunLoop 设置的时间已经超时。
    . RunLoop 被唤醒。
  • 8、通知观察者线程将被唤醒。
  • 9、处理未处理的事件。
    .如果用户定义的定时器启动,处理定时器事件并重启RunLoop。进入步骤2。
    .如果输入源启动,传递相应的消息。
    .如果RunLoop被显示唤醒而且时间还没超时,重启RunLoop。进入步骤2
  • 10、通知观察者RunLoop结束。

  • 6.理解runloop models

    Default:NSDefaultRunLoopMode,默认模式,在Run Loop没有指定Mode的时候,默认就跑在Default Mode下
    Connection:NSConnectionReplyMode,用来监听处理网络请求NSConnection的事件
    Modal:NSModalPanelRunLoopMode,OS X的Modal面板事件
    Event tracking:UITrackingRunLoopMode,拖动事件
    Common mode:NSRunLoopCommonModes,是一个模式集合,当绑定一个事件源到这个模式集合的时候就相当于绑定到了集合内的每一个模式