首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >深入浅出 RunLoop(三):事件循环机制

深入浅出 RunLoop(三):事件循环机制

作者头像
师大小海腾
发布2020-04-16 11:52:19
8170
发布2020-04-16 11:52:19
举报
文章被收录于专栏:iOS 技术分享iOS 技术分享

主线程的 RunLoop 的启动过程

首先我们来看一下主线程的RunLoop的启动过程。 前面我们说过,我们的 iOS 程序能保持持续运行的原因就是在main()函数中调用了UIApplicationMain函数,这个函数内部会启动主线程的RunLoop。 打断点,通过 LLDB 指令bt查看函数调用栈如下:

可以看到,在UIApplicationMain函数中调用了 Core Foundation 框架下的CFRunLoopRunSpecific函数。

CFRunLoopRunSpecific 函数实现:RunLoop 的入口

查看源码中该函数的实现,如下:

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
    __CFRunLoopLock(rl);
    // 根据 modeName 找到本次运行的 mode
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
    // 如果没找到 || mode 中没有注册任何事件,则就此停止,不进入循环
    if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
    Boolean did = false;
    if (currentMode) __CFRunLoopModeUnlock(currentMode);
    __CFRunLoopUnlock(rl);
    return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
    }
    volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
    CFRunLoopModeRef previousMode = rl->_currentMode;
    rl->_currentMode = currentMode;
    int32_t result = kCFRunLoopRunFinished;

    // 通知 Observers:即将进入 RunLoop(此处有 Observer 会创建 AutoreleasePool)
    if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    // RunLoop 具体要做的事情
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    // 通知 Observers:即将退出 RunLoop(此处有 Observer 会释放 AutoreleasePool)
    if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

        __CFRunLoopModeUnlock(currentMode);
        __CFRunLoopPopPerRunData(rl, previousPerRun);
    rl->_currentMode = previousMode;
    __CFRunLoopUnlock(rl);
    return result;
}

删掉不重要的细节:

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */

    // 通知 Observers:即将进入 RunLoop
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    // RunLoop 具体要做的事情
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    // 通知 Observers:即将退出 RunLoop
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

    return result;
}

从函数调用栈,以及CFRunLoopRunSpecific函数的实现中可以得知,RunLoop事件循环的实现机制体现在__CFRunLoopRun函数中。

__CFRunLoopRun 函数实现:事件循环的实现机制

由于该函数实现较复杂,以下为删掉细节的精简版本,想探究具体的可以查看 Core Foundation 源码

/**
 *  __CFRunLoopRun
 *
 *  @param rl              运行的 RunLoop 对象
 *  @param rlm             运行的 mode
 *  @param seconds         loop 超时时间
 *  @param stopAfterHandle true: RunLoop 处理完事件就退出  false:一直运行直到超时或者被手动终止
 *  @param previousMode    上一次运行的 mode
 *
 *  @return 返回 4 种状态
 */
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);
        }
        // 判断有无 Source1
        if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
            // 如果有 Source1,就跳转到 handle_msg
            goto handle_msg;
        }
        // 通知 Observers:即将进入休眠
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        __CFRunLoopSetSleeping(rl);
        // ⚠️休眠,等待消息来唤醒线程
        __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
        __CFRunLoopUnsetSleeping(rl);
        // 通知 Observers:刚从休眠中唤醒
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);

handle_msg:
        if (被 Timer 唤醒) {
            // 处理 Timer
            __CFRunLoopDoTimers(rl, rlm, mach_absolute_time())
        } else if (被 GCD 唤醒) {
            // 处理 GCD 
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
        } else {  // 被 Source1 唤醒   
            // 处理 Source1
            __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;  
        }

        // 处理 Blocks
        __CFRunLoopDoBlocks(rl, rlm);
        
        /* 设置返回值 */
        // 进入 loop 时参数为处理完事件就返回
        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;
        // mode 中没有任何的 Source0/Source1/Timer/Observer
        } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {  
            retVal = kCFRunLoopRunFinished;
        }
    
    } while (0 == retVal);

    return retVal;
}

从该函数实现中可以得知RunLoop主要就做以下几件事情:

  • __CFRunLoopDoObservers:通知Observers接下来要做什么
  • __CFRunLoopDoBlocks:处理Blocks
  • __CFRunLoopDoSources0:处理Sources0
  • __CFRunLoopDoSources1:处理Sources1
  • __CFRunLoopDoTimers:处理Timers
  • 处理 GCD 相关:dispatch_async(dispatch_get_main_queue(), ^{ });
  • __CFRunLoopSetSleeping/__CFRunLoopUnsetSleeping:休眠等待/结束休眠
  • __CFRunLoopServiceMachPort -> mach-msg():转移当前线程的控制权

事件循环机制

__CFRunLoopServiceMachPort 函数实现:RunLoop 休眠的实现原理

__CFRunLoopRun函数中,会调用__CFRunLoopServiceMachPort函数,该函数中调用了mach_msg()函数来转移当前线程的控制权给内核态/用户态。

  • 没有消息需要处理时,休眠线程以避免资源占用。调用mach_msg()从用户态切换到内核态,等待消息;
  • 有消息需要处理时,立刻唤醒线程,调用mach_msg()回到用户态处理消息。

这就是RunLoop休眠的实现原理,也是RunLoop与简单的do...while循环区别:

  • RunLoop:休眠的时候,当前线程不会做任何事,CPU 不会再分配资源;
  • 简单的do...while循环:当前线程并没有休息,一直占用 CPU 资源。
static Boolean __CFRunLoopServiceMachPort(mach_port_name_t port, mach_msg_header_t **buffer, size_t buffer_size, mach_port_t *livePort, mach_msg_timeout_t timeout, voucher_mach_msg_state_t *voucherState, voucher_t *voucherCopy) {
    Boolean originalBuffer = true;
    kern_return_t ret = KERN_SUCCESS;
    for (;;) {      /* In that sleep of death what nightmares may come ... */
        mach_msg_header_t *msg = (mach_msg_header_t *)*buffer;
        msg->msgh_bits = 0;
        msg->msgh_local_port = port;
        msg->msgh_remote_port = MACH_PORT_NULL;
        msg->msgh_size = buffer_size;
        msg->msgh_id = 0;
        if (TIMEOUT_INFINITY == timeout) { CFRUNLOOP_SLEEP(); } else { CFRUNLOOP_POLL(); }

        // ⚠️⚠️⚠️
        ret = mach_msg(msg, MACH_RCV_MSG|(voucherState ? MACH_RCV_VOUCHER : 0)|MACH_RCV_LARGE|((TIMEOUT_INFINITY != timeout) ? MACH_RCV_TIMEOUT : 0)|MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0)|MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AV), 0, msg->msgh_size, port, timeout, MACH_PORT_NULL);

        // Take care of all voucher-related work right after mach_msg.
        // If we don't release the previous voucher we're going to leak it.
        voucher_mach_msg_revert(*voucherState);
        
        // Someone will be responsible for calling voucher_mach_msg_revert. This call makes the received voucher the current one.
        *voucherState = voucher_mach_msg_adopt(msg);
        
        if (voucherCopy) {
            if (*voucherState != VOUCHER_MACH_MSG_STATE_UNCHANGED) {
                // Caller requested a copy of the voucher at this point. By doing this right next to mach_msg we make sure that no voucher has been set in between the return of mach_msg and the use of the voucher copy.
                // CFMachPortBoost uses the voucher to drop importance explicitly. However, we want to make sure we only drop importance for a new voucher (not unchanged), so we only set the TSD when the voucher is not state_unchanged.
                *voucherCopy = voucher_copy();
            } else {
                *voucherCopy = NULL;
            }
        }

        CFRUNLOOP_WAKEUP(ret);
        if (MACH_MSG_SUCCESS == ret) {
            *livePort = msg ? msg->msgh_local_port : MACH_PORT_NULL;
            return true;
        }
        if (MACH_RCV_TIMED_OUT == ret) {
            if (!originalBuffer) free(msg);
            *buffer = NULL;
            *livePort = MACH_PORT_NULL;
            return false;
        }
        if (MACH_RCV_TOO_LARGE != ret) break;
        buffer_size = round_msg(msg->msgh_size + MAX_TRAILER_SIZE);
        if (originalBuffer) *buffer = NULL;
        originalBuffer = false;
        *buffer = realloc(*buffer, buffer_size);
    }
    HALT;
    return false;
}下一篇
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 主线程的 RunLoop 的启动过程
  • CFRunLoopRunSpecific 函数实现:RunLoop 的入口
  • __CFRunLoopRun 函数实现:事件循环的实现机制
  • __CFRunLoopServiceMachPort 函数实现:RunLoop 休眠的实现原理
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档