美文网首页
Runloop--处理逻辑

Runloop--处理逻辑

作者: 人生看淡不服就干 | 来源:发表于2017-05-11 14:54 被阅读124次

学习Runloop

Threading Programming Guide
Core Foundation开源代码(包含Runloop)

Runloop处理逻辑

每次运行runloop,你的线程的runloop会自动处理之前未处理的消息,并通知相关的观察者。具体的顺序如下:

  1. 通知观察者runloop已经启动

  2. 通知观察者任何即将要开始的定时器

  3. 通知观察者任何即将启动的非基于端口的源

  4. 处理任何准备好的非基于端口的源

  5. 如果基于端口的源准备好并处于等待状态,则立即处理,跳至步骤9

  6. 通知观察者线程进入休眠

  7. 将线程置于休眠直到任一下面的事件发生:

    某一事件达到基于端口的源
    定时器启动
    runloop设置的时间已经超时
    runloop被显式唤醒

  8. 通知观察者线程将被唤醒

  9. 判断唤醒的事件类型并处理:

    如果是用户定义的定时器事件,则处理并重新开始Runloop,跳至步骤2
    如果是一个事件源,则传递事件并重新开始Runloop,跳至步骤2
    如果被显式唤醒,并且还没超时,则重新开始Runloop,跳至步骤2

  10. 通知观察者runloop结束。

Runloop处理逻辑图

Runloop事件类型

RunLoop主要处理以下6类事件回调:

static void __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__();
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__();
static void __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__();
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__();
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__();
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__();
  1. Observer事件:runloop中状态变化时进行通知。比如,CAAnimation是由RunloopObserver触发回调来布局和重绘。

  2. Block事件:非延迟的[NSObject performSelector:]CFRunLoopPerformBlock()

  3. Main_Dispatch_Queue事件:GCD中dispatch到main queue的block会被dispatch到main loop执行。比如dispatch_async,dispatch_after。

  4. Timer事件:延迟的[NSObject performSelector:],timer事件。

  5. Source0事件:处理如UIEvent,CFSocket这类事件。触摸事件其实是Source1接收系统事件后在回调 __IOHIDEventSystemClientQueueCallback()内触发的 Source0,Source0 再触发的_UIApplicationHandleEventQueue()。source0一定是要唤醒runloop及时响应并执行的,如果runloop此时在休眠等待系统的 mach_msg事件,那么就会通过source1来唤醒runloop执行。自定义的Source0事件,需要手动触发:

    CFRunLoopSourceSignal(source); //触发事件
    CFRunLoopWakeUp(runloop);//唤醒runloop处理事件
  1. Source1事件:系统内核的mach_msg事件。比如,CADisplayLink。

所有的事件可以分为两类:

  1. 非基于port的事件:Observer事件、Block事件、Source0事件
  2. 基于port的事件: Timer事件、Dispatch事件、Source1事件

Runloop核心源代码:

__CFRunLoopMode

 struct __CFRunLoopMode {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;  /* must have the run loop locked before locking this */
//mode名
    CFStringRef _name;
    Boolean _stopped;
    char _padding[3];
//source0 源
    CFMutableSetRef _sources0;
//source1 源
    CFMutableSetRef _sources1;
//observer 源
    CFMutableArrayRef _observers;
//timer 源
    CFMutableArrayRef _timers;

//mach port 到 source1 的映射,为了在runloop主逻辑中过滤runloop自己的port消息。
    CFMutableDictionaryRef _portToV1SourceMap;

//记录了所有当前mode中需要监听的port,作为调用监听消息函数的参数。
    __CFPortSet _portSet;
    CFIndex _observerMask;
#if USE_DISPATCH_SOURCE_FOR_TIMERS
    dispatch_source_t _timerSource;
    dispatch_queue_t _queue;
    Boolean _timerFired; // set to true by the source when a timer has fired
    Boolean _dispatchTimerArmed;
#endif
#if USE_MK_TIMER_TOO
//使用 mk timer, 用到的mach port,和source1类似,都依赖于mach port
    mach_port_t _timerPort;
    Boolean _mkTimerArmed;
#endif
//timer触发的理想时间
    uint64_t _timerSoftDeadline; /* TSR */
//timer触发的实际时间,理想时间加上tolerance(偏差)
    uint64_t _timerHardDeadline; /* TSR */
};

__CFRunLoop

struct __CFRunLoop {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;          /* locked for accessing mode list */

//用于手动将当前runloop线程唤醒,通过调用CFRunLoopWakeUp完成,CFRunLoopWakeUp会向_wakeUpPort发送一条消息
    __CFPort _wakeUpPort;           // used for CFRunLoopWakeUp 
    Boolean _unused;
    volatile _per_run_data *_perRunData;              // reset for runs of the run loop
    pthread_t _pthread;
    uint32_t _winthread;
//记录了当前runloop中所有的mode名
    CFMutableSetRef _commonModes;
//记录了当前runloop中所有注册到commonMode中的源
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode;
    CFMutableSetRef _modes;
//记录了添加到runloop中的block,它也可以像其他源一样被runloop处理,通过CFRunLoopPerformBlock可向runloop中添加block任务。
    struct _block_item *_blocks_head;
    struct _block_item *_blocks_tail;
    CFTypeRef _counterpart;
};

__CFRunLoopRun()

static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    //获取系统启动之后cpu嘀嗒数
    uint64_t startTSR = mach_absolute_time();
    if (__CFRunLoopIsStopped(rl)) {
        __CFRunLoopUnsetStopped(rl);
    return kCFRunLoopRunStopped;
    } else if (rlm->_stopped) {
    rlm->_stopped = false;
    return kCFRunLoopRunStopped;
    }

    //mach 端口, 线程之间通信的对象
    mach_port_name_t dispatchPort = MACH_PORT_NULL;
    //这里主要是为了判断当前线程是否为主线程        
    Boolean libdispatchQSafe = pthread_main_np() && ((HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL == previousMode) || (!HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && 0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ)));
    if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name)) dispatchPort = _dispatch_get_main_queue_port_4CF();

    //使用GCD实现runloop超时功能
    dispatch_source_t timeout_timer = NULL;
    struct __timeout_context *timeout_context = (struct __timeout_context *)malloc(sizeof(*timeout_context));
    //seconds是设置的runloop超时时间
    if (seconds <= 0.0) { // instant timeout
        seconds = 0.0;
        timeout_context->termTSR = 0ULL;
    } else if (seconds <= TIMER_INTERVAL_LIMIT) {
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, DISPATCH_QUEUE_OVERCOMMIT);
    timeout_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
        dispatch_retain(timeout_timer);
    timeout_context->ds = timeout_timer;
    timeout_context->rl = (CFRunLoopRef)CFRetain(rl);
    timeout_context->termTSR = startTSR + __CFTimeIntervalToTSR(seconds);
    dispatch_set_context(timeout_timer, timeout_context); // source gets ownership of context
    dispatch_source_set_event_handler_f(timeout_timer, __CFRunLoopTimeout);
        dispatch_source_set_cancel_handler_f(timeout_timer, __CFRunLoopTimeoutCancel);
        uint64_t ns_at = (uint64_t)((__CFTSRToTimeInterval(startTSR) + seconds) * 1000000000ULL);
        dispatch_source_set_timer(timeout_timer, dispatch_time(1, ns_at), DISPATCH_TIME_FOREVER, 1000ULL);
        dispatch_resume(timeout_timer);
    } else { // infinite timeout
        seconds = 9999999999.0;
        timeout_context->termTSR = UINT64_MAX;
    }
    Boolean didDispatchPortLastTime = true;
    int32_t retVal = 0;
    do {
        uint8_t msg_buffer[3 * 1024];
        mach_msg_header_t *msg = NULL;
        mach_port_t livePort = MACH_PORT_NULL;
    __CFPortSet waitSet = rlm->_portSet;
        __CFRunLoopUnsetIgnoreWakeUps(rl);
        //rl->_perRunData->ignoreWakeUps = 0x0;
        if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
//Perform blocks queued by CFRunLoopPerformBlock;
    __CFRunLoopDoBlocks(rl, rlm);
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        //如果rl中有source0消息
        if (sourceHandledThisLoop) {
            //处理block  Perform blocks newly queued by CFRunLoopPerformBlock;
            __CFRunLoopDoBlocks(rl, rlm);
       }
//poll标志着有没有处理source0的消息,如果没有则为false,反之为true
        Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
        //第一次进来不走这个逻辑,didDispatchPortLastTime是true
        if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
            msg = (mach_msg_header_t *)msg_buffer;
            //__CFRunLoopServiceMachPort用于接受指定端口(一个也可以是多个)的消息,最后一个参数代表当端口无消息的时候是否休眠,0是立刻返回不休眠,
            //TIMEOUT_INFINITY代表休眠
            //处理通过GCD派发到主线程的任务,这些任务优先级最高会被最先处理
            if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0)) {
                goto handle_msg;
            }
        }
        didDispatchPortLastTime = false;
//根据之前有没有处理过source0消息,来判断如果也没有source1消息的时候是否让线程进入睡眠,这里处理observer源,如果睡眠则通知Observer进入睡眠。
    if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
    __CFRunLoopSetSleeping(rl);
    // do not do any user callouts after this point (after notifying of sleeping)
        // Must push the local-to-this-activation ports in on every loop
        // iteration, as this mode could be run re-entrantly and we don't
        // want these ports to get serviced.
        __CFPortSetInsert(dispatchPort, waitSet);

    __CFRunLoopModeUnlock(rlm);
    __CFRunLoopUnlock(rl);
    if (kCFUseCollectableAllocator) {
        objc_clear_stack(0);
        memset(msg_buffer, 0, sizeof(msg_buffer));
    }
    msg = (mach_msg_header_t *)msg_buffer;
    //如果poll为null,且waitset中无port有消息,线程进入休眠
    __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);

    __CFRunLoopLock(rl);
    __CFRunLoopModeLock(rlm);
    // Must remove the local-to-this-activation ports in on every loop
    // iteration, as this mode could be run re-entrantly and we don't
    // want these ports to get serviced. Also, we don't want them left
    // in there if this function returns.
    __CFPortSetRemove(dispatchPort, waitSet);

    __CFRunLoopSetIgnoreWakeUps(rl);
        // user callouts now OK again
    __CFRunLoopUnsetSleeping(rl);
    //处理observer源,线程醒来
    if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
        handle_msg:;
        __CFRunLoopSetIgnoreWakeUps(rl);
        if (MACH_PORT_NULL == livePort) {
            CFRUNLOOP_WAKEUP_FOR_NOTHING();
            // handle nothing
//通过CFRunloopWake将当前线程唤醒
        } else if (livePort == rl->_wakeUpPort) {
            CFRUNLOOP_WAKEUP_FOR_WAKEUP();
            // do nothing on Mac OS
        }
//处理timer源
        else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
            CFRUNLOOP_WAKEUP_FOR_TIMER();
            if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                // Re-arm the next timer
                __CFArmNextTimerInMode(rlm, rl);
            }
        }
//通过GCD派发给主线程的任务
        else if (livePort == dispatchPort) {
            CFRUNLOOP_WAKEUP_FOR_DISPATCH();
            __CFRunLoopModeUnlock(rlm);
            __CFRunLoopUnlock(rl);
            _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
            _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
            __CFRunLoopLock(rl);
            __CFRunLoopModeLock(rlm);
            sourceHandledThisLoop = true;
            didDispatchPortLastTime = true;
        } else {
//通过macPort给当前线程派发消息,处理source1
            CFRUNLOOP_WAKEUP_FOR_SOURCE();
            // Despite the name, this works for windows handles as well
            //过滤macPort消息,有一些消息不一定是runloop中注册的,这里只处理runloop中注册的消息,在rlm->_portToV1SourceMap通过macPort找有没有对应的runloopMode
            CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
            if (rls) {
                mach_msg_header_t *reply = NULL;
                sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
                if (NULL != reply) {
                    //当前线程处理完source1,给发消息的线程反馈消息, MACH_SEND_MSG表示给replay端口发送消息
                    (void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
                    CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);
                }
           }
        }
    if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);

    __CFRunLoopDoBlocks(rl, rlm);
    if (sourceHandledThisLoop && stopAfterHandle) {
        retVal = kCFRunLoopRunHandledSource;
    } else if (timeout_context->termTSR < mach_absolute_time()) {
        //runloop超时时间到
            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);
    if (timeout_timer) {
        dispatch_source_cancel(timeout_timer);
        dispatch_release(timeout_timer);
    } else {
        free(timeout_context);
    }
    return retVal;
}

根据源码补充处理逻辑

补充:
第4步:非基于端口的源包括Block事件,Source0事件
第5步:主要处理dispatchPort的事件。即处理通过GCD派发到主线程的任务,这些任务优先级最高会被最先处理。
第6步:runloop睡眠是有条件的

//poll标志着有没有处理source0的消息,如果没有则为false,反之为true
//如果poll为false,且waitset中无port有消息,线程进入休眠
//__CFRunLoopServiceMachPort用于接受指定端口(一个也可以是多个)的消息,最后一个参数代表当端口无消息的时候是否休眠,0是立刻返回不休眠,TIMEOUT_INFINITY代表休眠
 __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);

第7步:定时器唤醒通过Mode里的timerPort。超时唤醒和显式唤醒,都是通过Runloop里的wakeUpPort。
每次Runloop处理完定时器回调后都会设置下一次的Timer触发时间,即通过mk_timer_arm系统调用,注册了一个系统时钟的回调:

 mk_timer_arm(rlm->_timerPort, __CFUInt64ToAbsoluteTime(nextSoftDeadline));

疑问:timer只是在Runloop唤醒后才处理,而kCFRunLoopBeforeTimers通知是在睡眠前处理???

第9步:处理Source1事件,先要根据Port对象查找出已注册的Source1。

CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);

Source与Port的关系是在添加Source的时候建立的(CFRunLoopAddSource)

__CFPort src_port = rls->_context.version1.getPort(rls->_context.version1.info);
if (CFPORT_NULL != src_port) {
//rlm->_portToV1SourceMap是port到source1的映射,为了在runloop主逻辑中过滤runloop自己的port消息。
CFDictionarySetValue(rlm->_portToV1SourceMap, (const void *)(uintptr_t)src_port, rls);
//rml->_portSet记录了所有当前mode中需要监听的port,作为调用监听消息函数的参数。
__CFPortSetInsert(src_port, rlm->_portSet);
}

参考文章

Runloop源码分析

相关文章

  • Runloop--处理逻辑

    学习Runloop Threading Programming GuideCore Foundation开源代码(...

  • SpringSecurity开发基于表单的认证(二)

    自定义用户认证逻辑 处理用户信息获取逻辑 处理用户校验逻辑 处理密码加密解密 处理用户信息获取逻辑 用户信息的获取...

  • Runloop--底层实现

    从上面代码可以看到,RunLoop 的核心是基于 mach port 的,其进入休眠时调用的函数是 mach_ms...

  • 逻辑处理

    computed computed的属性函数,只有message变更的时候,才会重新执行。如果返回的是Date.n...

  • iOS 代码里逻辑分支的处理

    iOS 代码里逻辑分支的处理iOS 代码里逻辑分支的处理

  • runloop

    Runloop-- 运行循环(死循环) 目的:1、保证runloop所在线程不退出2、负责监听事件(触摸 时钟 ...

  • 实例告诉你如何把 if-else 重构成高质量代码!

    为什么我们写的代码都是 if-else? 异常逻辑处理型重构方法实例一 异常逻辑处理型重构方法实例二 异常逻辑处理...

  • NSRunLoop--线程

    NSRunLoop其实本质就是死循环;作用:Runloop--运行循环 1.保证程序不退出 2.负责监听事件、触摸...

  • 机器学习day7-逻辑回归问题

    逻辑回归 逻辑回归,是最常见最基础的模型。 逻辑回归与线性回归 逻辑回归处理的是分类问题,线性回归处理回归问题。两...

  • @SentinelResource配置(中)

    客户自定义限流处理逻辑 创建CustomerBlockHandler类用于自定义限流处理逻辑 自定义限流处理类: ...

网友评论

      本文标题:Runloop--处理逻辑

      本文链接:https://www.haomeiwen.com/subject/auhotxtx.html