This part is mainly runloop source code parsing

The following diagram is probably the most classic illustration of runloops, from a deeper understanding of runloops:

Next, I will look at some of the details of the source code in detail, some parts of the code will be longer, pasted here to keep it intact.

Entry: CFRunLoopRun

void CFRunLoopRun(void) {	/* DOES CALLOUT */
    int32_t result;
    do {
        // Start with DefaultMode.
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0 e10.false);
        CHECK_FOR_FORK();
    } while(kCFRunLoopRunStopped ! = result && kCFRunLoopRunFinished ! = result); }// Start with the specified mode, allowing you to set the timeout
SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    CHECK_FOR_FORK();
    return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
Copy the code

The CFRunLoopRunSpecific function uses the second parameter CFStringRef modeName to specify the Mode in which the runloop will run. Here, the modeName argument is a string, not a wrapped RunLoopMode object. The seconds parameter determines how long the runloop can run when the external call is passed in. The external call is implemented using GCD timer or MK Timer internally.

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
    __CFRunLoopLock(rl);
    // select a mode based on modeName
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
    /// If mode is empty, that is, there is no mode Item (source, timer, observer).
    /// So for runloop to survive, it must have a mode item or block to execute.
    if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
	    Boolean did = false;
	    if (currentMode) __CFRunLoopModeUnlock(currentMode);
	    __CFRunLoopUnlock(rl);
	    return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
    }

    // _per_run_data is the data associated with each run of the runloop. This has been described in detail before.
    volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
    CFRunLoopModeRef previousMode = rl->_currentMode;
    rl->_currentMode = currentMode;
    int32_t result = kCFRunLoopRunFinished;

    // Notify the observer of the kCFRunLoopEntry status
	if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    // Enter the runloop
	result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    /// runloop exits, notifying the Observer of the status of kCFRunLoopExit
	if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

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

__CFRunLoopFindMode

The __CFRunLoopFindMode function is long, but its purpose is clear: find runloopMode objects based on runloop objects and modeName, and create a new one if none is found.

/* call with rl locked, returns mode locked */
static CFRunLoopModeRef __CFRunLoopFindMode(CFRunLoopRef rl, CFStringRef modeName, Boolean create) {
    CHECK_FOR_FORK();
    CFRunLoopModeRef rlm;
    struct __CFRunLoopMode srlm;
    // Set the specific length of the memory region to which the SRLM pointer points to 0
    memset(&srlm, 0.sizeof(srlm));
    _CFRuntimeSetInstanceTypeIDAndIsa(&srlm, __kCFRunLoopModeTypeID);
    srlm._name = modeName;
    // Query mode objects from the _modes container of the runloop object
    rlm = (CFRunLoopModeRef)CFSetGetValue(rl->_modes, &srlm);
    if (NULL! = rlm) { __CFRunLoopModeLock(rlm);return rlm;
    }
    if(! create) {return NULL;
    }
    // If there is no mode object, create a new one.
    rlm = (CFRunLoopModeRef)_CFRuntimeCreateInstance(kCFAllocatorSystemDefault, __kCFRunLoopModeTypeID, sizeof(struct __CFRunLoopMode) - sizeof(CFRuntimeBase), NULL);
    if (NULL == rlm) {
	    return NULL;
    }
    __CFRunLoopLockInit(&rlm->_lock);
    rlm->_name = CFStringCreateCopy(kCFAllocatorSystemDefault, modeName);
    rlm->_stopped = false;
    rlm->_portToV1SourceMap = NULL;
    rlm->_sources0 = NULL;
    rlm->_sources1 = NULL;
    rlm->_observers = NULL;
    rlm->_timers = NULL;
    rlm->_observerMask = 0;
    rlm->_portSet = __CFPortSetAllocate();
    rlm->_timerSoftDeadline = UINT64_MAX;
    rlm->_timerHardDeadline = UINT64_MAX;

    kern_return_t ret = KERN_SUCCESS;
#if USE_DISPATCH_SOURCE_FOR_TIMERS
    rlm->_timerFired = false;
    // This queue will be useful later?
    rlm->_queue = _dispatch_runloop_root_queue_create_4CF("Run Loop Mode Queue".0);
    mach_port_t queuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue);
    if (queuePort == MACH_PORT_NULL) CRASH("*** Unable to create run loop mode queue port. (%d) ***".- 1);
    rlm->_timerSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0.0, rlm->_queue);

    __block Boolean *timerFiredPointer = &(rlm->_timerFired);
    dispatch_source_set_event_handler(rlm->_timerSource, ^{
        *timerFiredPointer = true;
    });

    // Set timer to far out there. The unique leeway makes this timer easy to spot in debug output.
    _dispatch_source_set_runloop_timer_4CF(rlm->_timerSource, DISPATCH_TIME_FOREVER, DISPATCH_TIME_FOREVER, 321);
    dispatch_resume(rlm->_timerSource);

    ret = __CFPortSetInsert(queuePort, rlm->_portSet);
    if(KERN_SUCCESS ! = ret) CRASH("*** Unable to insert timer port into port set. (%d) ***", ret);

#endif
#if USE_MK_TIMER_TOO
    // What does _timerPort do?
    rlm->_timerPort = mk_timer_create();
    ret = __CFPortSetInsert(rlm->_timerPort, rlm->_portSet);
    if(KERN_SUCCESS ! = ret) CRASH("*** Unable to insert timer port into port set. (%d) ***", ret);
#endif
    Where is the _wakeUpPort of the runloop object used?
    ret = __CFPortSetInsert(rl->_wakeUpPort, rlm->_portSet);
    if(KERN_SUCCESS ! = ret) CRASH("*** Unable to insert wake up port into port set. (%d) ***", ret);

#if DEPLOYMENT_TARGET_WINDOWS
    rlm->_msgQMask = 0;
    rlm->_msgPump = NULL;
#endif
    CFSetAddValue(rl->_modes, rlm);
    CFRelease(rlm);
    __CFRunLoopModeLock(rlm);	/* return mode locked */
    return rlm;
}
Copy the code

__CFRunLoopModeIsEmpty

The __CFRunLoopModeIsEmpty function receives a runloop object and a runloopMode parameter to determine whether the runloopMode has a Source (also known as a runloopMode Item or block). If the runloop has a task to execute, there is no reason for the runloop to run without a task.

// expects rl and rlm locked
static Boolean __CFRunLoopModeIsEmpty(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopModeRef previousMode) {
    CHECK_FOR_FORK();
    if (NULL == rlm) return true;
#if DEPLOYMENT_TARGET_WINDOWS
    if (0! = rlm->_msgQMask)return false;
#endif

    /// The main thread will be special, and its mode item will definitely not be empty
    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)) return false; // represents the libdispatch main queue

    // Determine source0, source1, and timer
    if (NULL! = rlm->_sources0 &&0 < CFSetGetCount(rlm->_sources0)) return false;
    if (NULL! = rlm->_sources1 &&0 < CFSetGetCount(rlm->_sources1)) return false;
    if (NULL! = rlm->_timers &&0 < CFArrayGetCount(rlm->_timers)) return false;

    // execute the block tasks in runloop.
    struct _block_item *item = rl- > _blocks_head;
    while (item) {
        struct _block_item *curr = item;
        item = item->_next;
        Boolean doit = false;
        if (CFStringGetTypeID() == CFGetTypeID(curr->_mode)) {
            doit = CFEqual(curr->_mode, rlm->_name) || (CFEqual(curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(rl->_commonModes, rlm->_name));
        } else {
            doit = CFSetContainsValue((CFSetRef)curr->_mode, rlm->_name) || (CFSetContainsValue((CFSetRef)curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(rl->_commonModes, rlm->_name));
        }
        if (doit) return false;
    }
    return true;
}
Copy the code

This function determines if there is a block task.

doit = CFEqual(curr->_mode, rlm->_name) || (CFEqual(curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(rl->_commonModes, rlm->_name));
Copy the code

If condition 1 (the name of the current runloopMode is the same as that stored in the block) and condition 2 (the block task is specified to be executed in CommonModes and the name of the current runloopMode is in CommonModes) are met, the block task needs to be executed.

Runloop supports nesting

Notice this simplified process:

volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
CFRunLoopModeRef previousMode = rl->_currentMode;
rl->_currentMode = currentMode;

// run runloop for all processes from Entry to Exit

__CFRunLoopPopPerRunData(rl, previousPerRun);
rl->_currentMode = previousMode;
Copy the code

This code illustrates the following three points:

  1. Before runloop runs, previous data (previousrun) and mode (previousMode) are saved
  2. After the runloop is complete, previous data (previousPerRun) and mode (previousMode) will be recovered
  3. Runloops support nesting: When a runloop is run again (such as the callout function that is processing a task), the data and state of the current runloop is saved and then restored.

On the third point, apple’s official documentation is pretty clear:

Run loops can be run recursively. You can call CFRunLoopRun() or CFRunLoopRunInMode(::_:) from within any run loop Callout and create run loop activations on the current thread’s call stack. You are not restricted in which modes you can run from within a callout. You can create another run loop activation running in any available run loop mode, including any modes already running higher in the call stack.

PerRunData and runloopMode can only be restored by runloop.

summary

To summarize what the CFRunLoopRunSpecific() function does:

  1. __CFRunLoopFindMode finds the corresponding runloopMode
  2. __CFRunLoopModeIsEmpty checks if source is empty
  3. Save PerRunData and runloopMode for runloop
  4. __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry)
  5. __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
  6. __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
  7. Restore PerRunData and runloopMode to runloop

The __CFRunLoopDoObservers function synchronizes the kCFRunLoopEntry state to each Observer to execute its own callback function.

if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
Copy the code

Let’s look at the actual runloop entry: __CFRunLoopRun.

The real entry: __CFRunLoopRun

__CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode); The function takes a lot of arguments.

Its function prototype is as follows:

static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) __attribute__((noinline));
Copy the code

This function is very, very long… But that’s the core of how Runloop works. The entire code is posted here to keep it intact, and source code interpretation is added to the comments.

/* rl, rlm are locked on entrance and exit */
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    uint64_t startTSR = mach_absolute_time();

    if (__CFRunLoopIsStopped(rl)) {
        __CFRunLoopUnsetStopped(rl);
	    return kCFRunLoopRunStopped;
    } else if (rlm->_stopped) {
	    rlm->_stopped = false;
	    return kCFRunLoopRunStopped;
    }

    mach_port_name_t dispatchPort = MACH_PORT_NULL;
    // check whether the queue is main_queue.
    Boolean libdispatchQSafe = pthread_main_np() && ((HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL== previousMode) || (! HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY &&0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ)));
    /// what is dispatchPort?
    if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name)) dispatchPort = _dispatch_get_main_queue_port_4CF();

#if USE_DISPATCH_SOURCE_FOR_TIMERS
    // Do you use GCD timer?
    mach_port_name_t modeQueuePort = MACH_PORT_NULL;
    if (rlm->_queue) {
        modeQueuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue);
        if(! modeQueuePort) { CRASH("Unable to get port for run loop mode queue (%d)".- 1); }}#endif

    // This code indicates that the runloop interval is also implemented using the GCD timer. Is dispatch_source_t associated with runloop source??
    /// GCD timer is kernel dependent, so it is very accurate. Not affected by runloop.
    dispatch_source_t timeout_timer = NULL;
    struct __timeout_context *timeout_context = (struct __timeout_context *)malloc(sizeof(*timeout_context));
    if (seconds <= 0.0) { // instant timeout
        seconds = 0.0;
        timeout_context->termTSR = 0ULL;
    } else if (seconds <= TIMER_INTERVAL_LIMIT) {
	    dispatch_queue_t queue = pthread_main_np() ? __CFDispatchQueueGetGenericMatchingMain() : __CFDispatchQueueGetGenericBackground();
	    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);
        /// associate timeout_context with timer
	    dispatch_set_context(timeout_timer, timeout_context); // source gets ownership of context
        // __CFRunLoopTimeout is a pointer to a function that calls CFRunLoopWakeUp(context->rl); Used to wake up runloop
	    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 {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
        voucher_mach_msg_state_t voucherState = VOUCHER_MACH_MSG_STATE_UNCHANGED;
        voucher_t voucherCopy = NULL;
#endif
        uint8_t msg_buffer[3 * 1024];
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
        mach_msg_header_t *msg = NULL;
        mach_port_t livePort = MACH_PORT_NULL;
#elif DEPLOYMENT_TARGET_WINDOWS
        HANDLE livePort = NULL;
        Boolean windowsMessageReceived = false;
#endif
	    __CFPortSet waitSet = rlm->_portSet;

        __CFRunLoopUnsetIgnoreWakeUps(rl);

        /// Note: Runloop is about to start working, handling timer, source, block, etc.
        // notify the Observer of the kCFRunLoopBeforeTimers status
        if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        // notify the Observer of the status of kCFRunLoopBeforeSources
        /// the source0 callback is about to trigger, not port
        if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);

        // execute the block added to the runloop
	    __CFRunLoopDoBlocks(rl, rlm);

        // Handle the Sources0 event, where is the Timer event handled?? Why put timer after runloop wake up?
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        if (sourceHandledThisLoop) {
            /// there is a __CFRunLoopDoBlocks
            __CFRunLoopDoBlocks(rl, rlm);
	    }

        /* Each time the loop processes the source0 task, the poll value will be true. The immediate effect is not to doobservers-beforewaiting and doobservers-afterwaiting. That is, runloop will go straight to sleep and will not tell BeforeWaiting and AfterWaiting activities. So you see, there may be cases where the runloop passes through several loops, but the Observer you registered will not receive a callback. * /
        Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);

        if(MACH_PORT_NULL ! = dispatchPort && ! didDispatchPortLastTime) {#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
            msg = (mach_msg_header_t *)msg_buffer;
            // There is a Mach port which is the source1 event and corresponds to a Mach MSG.
            /// All user operations and network operations are performed through port.
            /// why is this dispatchPort?
            if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
                goto handle_msg;
            }
#elif DEPLOYMENT_TARGET_WINDOWS
            if (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0.0, &livePort, NULL)) {
                goto handle_msg;
            }
#endif
        }

        /// This is what runloop does, after which runloop will go to sleep.
        / / / and if you have to use the port, then through the handle_msg jump to __CFRunLoopSetIgnoreWakeUps, namely the runloop sensei

        didDispatchPortLastTime = false;

        /// Note: Runloop is done and is about to go to sleep
        /// hibernate, that is, do while(1) where mach_MSg_trap () is executed to switch to kernel state. After that, runloop can only be actively woken up by source1.
        /// The user touch event is wrapped as a source1 event, passed to the Mach layer via port, mach_msg, and then the TRAP () function is called to complete the OS state switch.
        /// notify the observer of the kCFRunLoopBeforeWaiting state
	    if(! poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);/// The runloop starts sleeping, i.e. the following do-while(1) loop.
	    __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);

        CFAbsoluteTime sleepStart = poll ? 0.0 : CFAbsoluteTimeGetCurrent();

#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
#if USE_DISPATCH_SOURCE_FOR_TIMERS
        /// do while(1) is only a loop, so how does runloop sleep??
        do {
            if (kCFUseCollectableAllocator) {
                // objc_clear_stack(0);
                // <rdar://problem/16393959>
                memset(msg_buffer, 0.sizeof(msg_buffer));
            }
            msg = (mach_msg_header_t *)msg_buffer;
/* The __CFRunLoopServiceMachPort function is crucial. Call mach_msg and wait for the MSG of the Mach port to be received. The thread is about to go to sleep until it is woken up by one of the following events: 1, a port-based source1 event 2, a timer's trigger time 3, runloop's own timeout 4, and manually woken up using the WakeUp function call. After this function is executed, the runloop will sleep and the thread will sleep. Until the runloop is woken up, the thread is woken up!! This is important. What does waitSet, the first argument to the function, tell us? Hibernate, but can receive Mach MSG for waitSet port. * /
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);

            /// While (1) the runloop has gone to sleep.
            if(modeQueuePort ! = MACH_PORT_NULL && livePort == modeQueuePort) {/// what does this while loop do? What is the homework for break inside, don't you understand?
                // Drain the internal queue. If one of the callout blocks sets the timerFired flag, break out and service the timer.
                while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));
                if (rlm->_timerFired) {
                    // Leave livePort as the queue port, and service timers below
                    rlm->_timerFired = false;
                    break;
                } else {
                    if(msg && msg ! = (mach_msg_header_t *)msg_buffer) free(msg); }}else {
                // Go ahead and leave the inner loop.
                break; }}while (1);
#else
        if (kCFUseCollectableAllocator) {
            // objc_clear_stack(0);
            // <rdar://problem/16393959>
            memset(msg_buffer, 0.sizeof(msg_buffer));
        }
        msg = (mach_msg_header_t *)msg_buffer;
        // call mach_msg and wait for the MSG of the Mach port to be received. Thread to sleep
        __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
#endif


#elif DEPLOYMENT_TARGET_WINDOWS
        // Here, use the app-supplied message queue mask. They will set this if they are interested in having this run loop receive windows messages.
        __CFRunLoopWaitForMultipleObjects(waitSet, NULL, poll ? 0 : TIMEOUT_INFINITY, rlm->_msgQMask, &livePort, &windowsMessageReceived);
#endif

        /// Once you exit the above do-while(1), runloop is awakened
        __CFRunLoopLock(rl);
        __CFRunLoopModeLock(rlm);
        /// The runloop sleep time can be used to do something.
        rl->_sleepTime += (poll ? 0.0 : (CFAbsoluteTimeGetCurrent() - sleepStart));

        // 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);

        // __CFRunLoopSetSleeping(rl); With __CFRunLoopUnsetSleeping (rl); One-to-one correspondence.
        // Hibernate and wake up runloop
        // user callouts now OK again
	    __CFRunLoopUnsetSleeping(rl);

        /// How the thread is woken up is irrelevant. The kernel handles it itself.
        // synchronize kCFRunLoopAfterWaiting to the Observer: it just woke up from sleep
	    if(! poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);// When the message is received, the message is processed. That is, to wake up the runloop through port, you can go directly to the goto statement.
        handle_msg:;
        __CFRunLoopSetIgnoreWakeUps(rl);

        if (MACH_PORT_NULL == livePort) {
            CFRUNLOOP_WAKEUP_FOR_NOTHING();
            // handle nothing
        } else if (livePort == rl->_wakeUpPort) {
            CFRUNLOOP_WAKEUP_FOR_WAKEUP();
            // do nothing on Mac OS
        }
#if USE_DISPATCH_SOURCE_FOR_TIMERS
        else if(modeQueuePort ! = MACH_PORT_NULL && livePort == modeQueuePort) {/// The runloop is woken up and continues to do things. Why not reuse the code above?
            /// The timer is woken up
            CFRUNLOOP_WAKEUP_FOR_TIMER();
            /// triggers the timer callback function
            if(! __CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {// Re-arm the next timer, because we apparently fired early__CFArmNextTimerInMode(rlm, rl); }}#endif
#if USE_MK_TIMER_TOO
        else if(rlm->_timerPort ! = MACH_PORT_NULL && livePort == rlm->_timerPort) { CFRUNLOOP_WAKEUP_FOR_TIMER();// On Windows, we have observed an issue where the timer port is set before the time which we requested it to be set. For example, we set the fire time to be TSR 167646765860, but it is actually observed firing at TSR 167646764145, which is 1715 ticks early. The result is that, when __CFRunLoopDoTimers checks to see if any of the run loop timers should be firing, it appears to be 'too early' for the next timer, and no timers are handled.
            // In this case, the timer port has been automatically reset (since it was returned from MsgWaitForMultipleObjectsEx), and if we do not re-arm it, then no timers will ever be serviced again unless something adjusts the timer list (e.g. adding or removing timers). The  fix for the issue is to reset the timer here if CFRunLoopDoTimers did not handle a timer itself. 9308754
            if(! __CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {// Re-arm the next timer__CFArmNextTimerInMode(rlm, rl); }}#endif
        else if (livePort == dispatchPort) {
            CFRUNLOOP_WAKEUP_FOR_DISPATCH();
            __CFRunLoopModeUnlock(rlm);
            __CFRunLoopUnlock(rl);
            _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6.NULL);
#if DEPLOYMENT_TARGET_WINDOWS
            void *msg = 0;
#endif
            /// dispatchPort is the main thread. Yes!
            /// Execute if there are blocks to the main queue.
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
            // register a callback
            _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0.NULL);
            __CFRunLoopLock(rl);
            __CFRunLoopModeLock(rlm);
            sourceHandledThisLoop = true;
            didDispatchPortLastTime = true;
        } else {
            /// where is source0 or source1? Source1, based on port
            /// can be active wake up
            CFRUNLOOP_WAKEUP_FOR_SOURCE();

            // If we received a voucher from this mach_msg, then put a copy of the new voucher into TSD. CFMachPortBoost will look in the TSD for the voucher. By using the value in  the TSD we tie the CFMachPortBoost to this received mach_msg explicitly without a chance for anything in between the two pieces of code to set the voucher again.
            voucher_t previousVoucher = _CFSetTSD(__CFTSDKeyMachMessageHasVoucher, (void *)voucherCopy, os_release);

            // Despite the name, this works for windows handles as well
            CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
            if (rls) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
		        mach_msg_header_t *reply = NULL;
                /// Handle the source1 event
		        sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
		        if (NULL! = reply) { (void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
		            CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);
		        }
#elif DEPLOYMENT_TARGET_WINDOWS
                sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls) || sourceHandledThisLoop;
#endif
	        }

            // Restore the previous voucher
            _CFSetTSD(__CFTSDKeyMachMessageHasVoucher, previousVoucher, os_release);

        }
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
        if(msg && msg ! = (mach_msg_header_t *)msg_buffer) free(msg);
#endif

        // execute the block added to the runloop.
	    __CFRunLoopDoBlocks(rl, rlm);


	    if (sourceHandledThisLoop && stopAfterHandle) {
            /// When entering runloop, the argument says to return as soon as the event is processed
	        retVal = kCFRunLoopRunHandledSource;
        } else if (timeout_context->termTSR < mach_absolute_time()) {
            /// The runloop timed out
            retVal = kCFRunLoopRunTimedOut;
	    } else if (__CFRunLoopIsStopped(rl)) {
            __CFRunLoopUnsetStopped(rl);
            /// stopped by an external caller
	        retVal = kCFRunLoopRunStopped;
	    } else if (rlm->_stopped) {
	        rlm->_stopped = false;
            /// stopped by an external caller
	        retVal = kCFRunLoopRunStopped;
	    } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
            /// all mode items are missing
	        retVal = kCFRunLoopRunFinished;
	    }

#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
        voucher_mach_msg_revert(voucherState);
        os_release(voucherCopy);
#endif
    // retVal is a runloop that has finished processing all the events and then continues the loop, do-while(1).
    } while (0 == retVal);

    if (timeout_timer) {
        dispatch_source_cancel(timeout_timer);
        dispatch_release(timeout_timer);
    } else {
        free(timeout_context);
    }

    return retVal;
}
Copy the code

This function does look tired, and it’s easy to tease out a few core points.

Outer loop do-while(0 == retVal)

The outer loop is the complete flow of a loop of runloop.

Once retVal is non-zero, the outer do-while loop exits and the __CFRunLoopRun function returns a non-zero value, indicating the end of the runloop. RetVal becomes non-zero only in the following ways:

/* Reasons for CFRunLoopRunInMode() to Return */
typedef CF_ENUM(SInt32, CFRunLoopRunResult) {
    kCFRunLoopRunFinished = 1,
    kCFRunLoopRunStopped = 2,
    kCFRunLoopRunTimedOut = 3,
    kCFRunLoopRunHandledSource = 4
};
Copy the code

Take a close look at the source code below

if (sourceHandledThisLoop && stopAfterHandle) {
    // When entering runloop, the argument says to return when the event is processed
    retVal = kCFRunLoopRunHandledSource;
} else if (timeout_context->termTSR < mach_absolute_time()) {
    // The runloop timed out
    retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(rl)) {
    __CFRunLoopUnsetStopped(rl);
    // Stopped by an external caller
    retVal = kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
    rlm->_stopped = false;
    // Stopped by an external caller
    // What's the difference?
    // __CFRunLoopIsStopped return (rl->_perRunData->stopped)? true : false;
    retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
      // No mode item is displayed
   retVal = kCFRunLoopRunFinished;
}
Copy the code

The conclusion is obvious:

  1. StopAfterHandle is the fourth argument to the __CFRunLoopRun function and the fourth argument to the CFRunLoopRunSpecific function returnAfterSourceHandled. This parameter is false when the CFRunLoopRun function is called, so the run: of NSRunLoop cannot be stopped once executed. When the CFRunLoopRunInMode function is called, this parameter is set when runUntilDate: or runMode:beforeDate: of NSRunLoop is called. So if the conditions are met, the loop task is finished (sourceHandledThisLoop) and then exits.
  2. Runloop timeouts are determined by timeout_context.
  3. __CFRunLoopIsStopped and RLM ->_stopped are __CFRunLoopIsStopped. Is it an external force stop? Temporarily not clear how to stop??
  4. Call __CFRunLoopModeIsEmpty again to determine whether the source of runloopMode is empty or terminate the runloop if it is not.

Inner loop do-while(1)

The inner do-while(1) loop, the runloop, goes to sleep. The thing to do is to call the __CFRunLoopServiceMachPort function.

The following code is executed to enter hibernation (kernel mode). Once the inner do-while(1) exits the loop, the runloop wakes up

do {
    if (kCFUseCollectableAllocator) {
        // objc_clear_stack(0);
        // <rdar://problem/16393959>
        memset(msg_buffer, 0.sizeof(msg_buffer));
    }
    msg = (mach_msg_header_t *)msg_buffer;

    __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);

    // While (1) the runloop has gone to sleep.
    if(modeQueuePort ! = MACH_PORT_NULL && livePort == modeQueuePort) {// What does this while loop do? What is the homework for break inside, don't you understand?
        // Drain the internal queue. If one of the callout blocks sets the timerFired flag, break out and service the timer.
        while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));
        if (rlm->_timerFired) {
            // Leave livePort as the queue port, and service timers below
            rlm->_timerFired = false;
            break;
        } else {
            if(msg && msg ! = (mach_msg_header_t *)msg_buffer) free(msg); }}else {
        // Go ahead and leave the inner loop.
        break; }}while (1);
Copy the code

The __CFRunLoopServiceMachPort function is key. Call mach_msg and wait for the MSG of the Mach port to be received. The thread is about to go to sleep until it is awakened by one of the following events:

  1. A port-based source1 event
  2. The trigger time of a timer
  3. Runloop’s own timeout is up
  4. Manual wake up.

Runloop goes to sleep

__CFRunLoopServiceMachPort is where Mach MSG comes in. The mach_msg function is executed to switch the system from user state to kernel state. And the mach_msg call code is pretty impressive…

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;
    // Another loop
    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 port direction is from _dispatch_get_main_queue_port_4CF to MACH_PORT_NULL.
        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;
}
Copy the code

This code has a for loop that keeps building mach_msg and executing the mach_msg() function. The mach_msg_trap() system call is actually called inside mach_msg(), so this function triggers a change in the operating system state from user mode to kernel mode, and runloop goes to sleep.

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);
Copy the code

If you have MSG to process, jump to handle_msg. This step corresponds to having Source1 to deal with.

DidDispatchPortLastTime is true for the first time and will not be executed
if(MACH_PORT_NULL ! = dispatchPort && ! didDispatchPortLastTime) { msg = (mach_msg_header_t *)msg_buffer;
    if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
        gotohandle_msg; }}Copy the code

One thing to note about hibernation is that when runloop goes to sleep, the thread goes to sleep, and the CPU doesn’t do anything. Once the mach_msg() function is used to send the sleep message to the Mach kernel and the system is in kernel state, all you can do is wait. Just wait for the runloop to wake up.

Runloop awakened

Exit the inner loop and the runloop is woken up. There are two situations??

ModeQueuePort and livePort judgments, or RLM ->_timerFired = false; Why??

// While (1) the runloop has gone to sleep.
if(modeQueuePort ! = MACH_PORT_NULL && livePort == modeQueuePort) {// What does this while loop do? What is the homework for break inside, don't you understand?
    // Drain the internal queue. If one of the callout blocks sets the timerFired flag, break out and service the timer.
    while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));
    if (rlm->_timerFired) {
        // Leave livePort as the queue port, and service timers below
        rlm->_timerFired = false;
        break;
    } else {
        if(msg && msg ! = (mach_msg_header_t *)msg_buffer) free(msg); }}else {
    // Go ahead and leave the inner loop.
    break;
}
Copy the code

Wake up condition:

  1. Source1 event to port
  2. Time is up
  3. Runloop timeout??
  4. Awakened by the caller?

summary

After knowing that the inner do-while(1) is the key point that the system triggers the state switch and enters the sleep state, the respective functions of the code before and after the inner loop can be easily understood according to the runloop flow chart.

  1. Inner loop do-while(1) : the Timer, Source, and Block tasks are processed, after which the runloop goes to sleep
  2. After the inner loop do-while(1) : Runloop wakes up to handle some task (in this case, the task for Source1)

Inner loop before do-while(1)

This is where the runloop performs its tasks, before the runloop goes to sleep, and the process is as follows:

  1. Set the state of runloop
  2. Notify the BeforeTimers and BeforeSources states to the Observer
  3. Executing a Block Task
  4. Execute the Sources0 event
  5. Notify the BeforeWaiting state to the Observer
  6. Set the state of runloop

There is a question about this phase: the BeforeTimers state is sent to the Observer, but the Timer event is not executed. Why?

In fact, the Timer event is executed after the runloop has gone to sleep and then woken up, after the inner loop do-while(1). This can be seen in the timing of the __CFRunLoopDoTimers call.

A more detailed explanation of what Runloop does will come in a later section in __CFRunLoopDoXXX, where things really do happen.

Inner loop after do-while(1)

This is the phase where the runloop is awakened to perform the task.

Rl ->_sleepTime += (poll? 0.0: (CFAbsoluteTimeGetCurrent() -sleepstart)); * * *. And then set wake up after some of the state (__CFRunLoopSetIgnoreWakeUps, __CFRunLoopUnsetSleeping). Notify the Observer of AfterWaiting status:

if(! poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);Copy the code

Next, it’s time for the runloop to process the task once it’s woken up.

  1. Determine the livePort and modeQueuePort
  2. Determine the _timerPort and perform the timer callback
  3. Judge dispatchPort
  4. If none of the above, then the Source1 event. Find the corresponding Source1 based on runloop, runloopMode, and livePort, and then call __CFRunLoopDoSource1 to perform the task. If there is a Mach MSG reply, the mach_msg function will send the reply message to the specified Mach port. This is how the Mach core communicates with threads.
  5. Finally, the block task is executed again. The outer do-while loop is then continued for the next loop.

__CFRunLoopDoXXX where things are actually done

The runloop source code has a set of functions starting with __CFRunLoopDo:

__CFRunLoopDoBlocks(rl, rlm);
__CFRunLoopDoTimers(rl, rlm, mach_absolute_time());
__CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
__CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply);
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
Copy the code

These functions are key to what Runloop does, as described in more detail below. Note that each function receives a runloop object and a runloopMode object as parameters, which confirms the basic fact that runloopModes are isolated from each other and handle their own tasks.

The order in which these functions are explained is independent of the order in which they are actually executed.

Process Block task __CFRunLoopDoBlocks

__CFRunLoopDoBlocks(RL, RLM) executes the block task added to the runloop, traversing the _block_item list structure, and firing CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK(block); To execute a block.

// Call with rl and rlm locked
static Boolean __CFRunLoopDoBlocks(CFRunLoopRef rl, CFRunLoopModeRef rlm) {
    if(! rl->_blocks_head)return false;
    if(! rlm || ! rlm->_name)return false;

    Boolean did = false;
    struct _block_item *head = rl- > _blocks_head;
    struct _block_item *tail = rl- > _blocks_tail;
    rl->_blocks_head = NULL;
    rl->_blocks_tail = NULL;
    CFSetRef commonModes = rl->_commonModes;
    CFStringRef curMode = rlm->_name;
    // select * from rL and RLM
    __CFRunLoopModeUnlock(rlm);
    __CFRunLoopUnlock(rl);

    struct _block_item *prev = NULL;
    struct _block_item *item = head;
    while (item) {
        struct _block_item *curr = item;
        item = item->_next;
	    Boolean doit = false;
	    if (CFStringGetTypeID() == CFGetTypeID(curr->_mode)) {
	        doit = CFEqual(curr->_mode, curMode) || (CFEqual(curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(commonModes, curMode));
        } else {
	        doit = CFSetContainsValue((CFSetRef)curr->_mode, curMode) || (CFSetContainsValue((CFSetRef)curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(commonModes, curMode));
	    }
	    if(! doit) prev = curr;if (doit) {
	        if (prev) prev->_next = item;
	        if (curr == head) head = item;
	        if (curr == tail) tail = prev;
            // _block_item->_block
	        void (^block)(void) = curr->_block;
            CFRelease(curr->_mode);
            free(curr);
	        if (doit) {
                /// what does runloop mean by block??
                __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
	            did = true;
	        }
            Block_release(block); // do this before relocking to prevent deadlocks where some yahoo wants to run the run loop reentrantly from their dealloc
	    }
    }
    __CFRunLoopLock(rl);
    __CFRunLoopModeLock(rlm);
    if (head) {
	tail->_next = rl->_blocks_head;
	rl->_blocks_head = head;
        if(! rl->_blocks_tail) rl->_blocks_tail = tail; }return did;
}
Copy the code

A prototype callout function that actually executes a block is as follows:

static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__() __attribute__((noinline));
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(void (^block)(void)) {
    if (block) {
        block();
    }
    asm __volatile__(""); // thwart tail-call optimization
}
Copy the code

_block_item

Each runloop object has two fields, _blocks_head and _blocks_tail, which are both _block_item structures. The _block_item structure in runloop clearly forms a linked list, with each node storing an _block object.

struct _block_item {
    struct _block_item* _next;
    CFTypeRef _mode;	// CFString or CFSet
    void (^_block)(void);
};
Copy the code

So, where are these two fields of the Runloop object populated?

How are blocks added to runloops

The CFRunLoopPerformBlock function wraps the external block as a _block_item structure and places it in the _blocks_head and _blocks_tail fields of the Runloop object.

void CFRunLoopPerformBlock(CFRunLoopRef rl, CFTypeRef mode, void (^block)(void)) { CHECK_FOR_FORK(); if (CFStringGetTypeID() == CFGetTypeID(mode)) { mode = CFStringCreateCopy(kCFAllocatorSystemDefault, (CFStringRef)mode);  __CFRunLoopLock(rl); // ensure mode exists CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, (CFStringRef)mode, true); if (currentMode) __CFRunLoopModeUnlock(currentMode); __CFRunLoopUnlock(rl); } else if (CFArrayGetTypeID() == CFGetTypeID(mode)) { CFIndex cnt = CFArrayGetCount((CFArrayRef)mode); const void **values = (const void **)malloc(sizeof(const void *) * cnt); CFArrayGetValues((CFArrayRef)mode, CFRangeMake(0, cnt), values); mode = CFSetCreate(kCFAllocatorSystemDefault, values, cnt, &kCFTypeSetCallBacks); __CFRunLoopLock(rl); // ensure modes exist for (CFIndex idx = 0; idx < cnt; idx++) { CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, (CFStringRef)values[idx], true); if (currentMode) __CFRunLoopModeUnlock(currentMode); } __CFRunLoopUnlock(rl); free(values); } else if (CFSetGetTypeID() == CFGetTypeID(mode)) { CFIndex cnt = CFSetGetCount((CFSetRef)mode); const void **values = (const void **)malloc(sizeof(const void *) * cnt); CFSetGetValues((CFSetRef)mode, values); mode = CFSetCreate(kCFAllocatorSystemDefault, values, cnt, &kCFTypeSetCallBacks); __CFRunLoopLock(rl); // ensure modes exist for (CFIndex idx = 0; idx < cnt; idx++) { CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, (CFStringRef)values[idx], true); if (currentMode) __CFRunLoopModeUnlock(currentMode); } __CFRunLoopUnlock(rl); free(values); } else { mode = NULL; } /// Copy the block. block = Block_copy(block); if (! mode || ! block) { if (mode) CFRelease(mode); if (block) Block_release(block); return; } __CFRunLoopLock(rl); struct _block_item *new_item = (struct _block_item *)malloc(sizeof(struct _block_item)); new_item->_next = NULL; new_item->_mode = mode; new_item->_block = block; _block_item in runloop forms the list structure, and new blocks are added to the end of the list. if (! rl->_blocks_tail) { rl->_blocks_head = new_item; } else { rl->_blocks_tail->_next = new_item; } rl->_blocks_tail = new_item; __CFRunLoopUnlock(rl); }Copy the code

Looking at the last part of the code, _block_item in runloop forms the list structure. The list starts with _blocks_head and ends with _blocks_tail. New blocks are added to the end of the list.

When is the CFRunLoopPerformBlock() function called? The answer is CFRunLoopPerformBlock, the external interface:

CFRunLoopPerformBlock -> Add block to runloop -> runloop wake up task -> __CFRunLoopDoBlocks -> __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block) -> block()Copy the code

Then everything was clear. Runloop can be used directly to execute blocks, but it is rarely used.

/// CFRunLoopPerformBlock copies blocks. void CFRunLoopPerformBlock(CFRunLoopRef rl, CFTypeRef mode, void (^block)(void));Copy the code
Enqueues a block object on a given runloop to be executed as the runloop cycles in specified modes.

When the runloop runs in the specified mode, the block object is executed. You can use thisFunction as a means to offload work to another thread similar to Cocoa's performSelector:onThread:withObject:waitUntilDone:and related methods. You can also use it as an alternative to mechanisms such as putting a CFRunLoopTimer in the other thread's run loop, or using CFMessagePort to pass information between threads.

This method enqueues the block only and does not automatically wake up the specified run loop. Therefore, execution of the block occurs the next time the run loop wakes up to handle another input source. If you want the work performed right away, you must explicitly wake up that thread using the CFRunLoopWakeUp function.
Copy the code

CFRunLoopPerformBlock simply drops the block to the specified queue, but does not actively wake up the runloop. If the runloop is sleeping, the block will wait until the runloop is woken up by another source. You can of course wake up the runloop using the CFRunLoopWakeUp function.

CFRunLoopPerformBlock() is rarely used in common scenarios. It can be used to drop a block into the specified Mode of the runloop.

CFRunLoopPerformBlock(CFRunLoopGetCurrent(), kCFRunLoopCommonModes, ^{
    NSLog(@"CFRunLoopPerformBlock kCFRunLoopCommonModes");
});
Copy the code

Process the Timer task __CFRunLoopDoTimers

The __CFRunLoopDoTimers function iterates through all timers added to the runloopMode, filters out the timers whose execution time has expired, and calls the __CFRunLoopDoTimer(RL, RLM, RLT) function to execute the timer callback function.

// rl and rlm are locked on entry and exit
static Boolean __CFRunLoopDoTimers(CFRunLoopRef rl, CFRunLoopModeRef rlm, uint64_t limitTSR) {	/* DOES CALLOUT */
    Boolean timerHandled = false;
    CFMutableArrayRef timers = NULL;
    for (CFIndex idx = 0, cnt = rlm->_timers ? CFArrayGetCount(rlm->_timers) : 0; idx < cnt; idx++) {
        CFRunLoopTimerRef rlt = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(rlm->_timers, idx);

        if(__CFIsValid(rlt) && ! __CFRunLoopTimerIsFiring(rlt)) {if (rlt->_fireTSR <= limitTSR) {
                if(! timers) timers = CFArrayCreateMutable(kCFAllocatorSystemDefault,0, &kCFTypeArrayCallBacks); CFArrayAppendValue(timers, rlt); }}}for (CFIndex idx = 0, cnt = timers ? CFArrayGetCount(timers) : 0; idx < cnt; idx++) {
        CFRunLoopTimerRef rlt = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(timers, idx);
        Boolean did = __CFRunLoopDoTimer(rl, rlm, rlt);
        timerHandled = timerHandled || did;
    }
    if (timers) CFRelease(timers);
    return timerHandled;
}
Copy the code

__CFRunLoopDoTimer

The __CFRunLoopDoTimer function has more code.

// mode and rl are locked on entry and exit
static Boolean __CFRunLoopDoTimer(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopTimerRef rlt) {	/* DOES CALLOUT */
    Boolean timerHandled = false;
    uint64_t oldFireTSR = 0;

    /* Fire a timer */
    CFRetain(rlt);
    __CFRunLoopTimerLock(rlt);

    if(__CFIsValid(rlt) && rlt->_fireTSR <= mach_absolute_time() && ! __CFRunLoopTimerIsFiring(rlt) && rlt->_runLoop == rl) {void *context_info = NULL;
        void (*context_release)(const void *) = NULL;
        if (rlt->_context.retain) {
            context_info = (void *)rlt->_context.retain(rlt->_context.info);
            context_release = rlt->_context.release;
        } else {
            context_info = rlt->_context.info;
        }
        Boolean doInvalidate = (0.0 == rlt->_interval);
	__CFRunLoopTimerSetFiring(rlt);
        // Just in case the next timer has exactly the same deadlines as this one, we reset these values so that the arm next timer code can correctly find the next timer in the list and arm the underlying timer.
        rlm->_timerSoftDeadline = UINT64_MAX;
        rlm->_timerHardDeadline = UINT64_MAX;
        __CFRunLoopTimerUnlock(rlt);
	__CFRunLoopTimerFireTSRLock();
	oldFireTSR = rlt->_fireTSR;
	__CFRunLoopTimerFireTSRUnlock();

        __CFArmNextTimerInMode(rlm, rl);

	__CFRunLoopModeUnlock(rlm);
	__CFRunLoopUnlock(rl);
	__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(rlt->_callout, rlt, context_info);
	CHECK_FOR_FORK();
        if (doInvalidate) {
            CFRunLoopTimerInvalidate(rlt);      /* DOES CALLOUT */
        }
        if (context_release) {
            context_release(context_info);
        }
	__CFRunLoopLock(rl);
	__CFRunLoopModeLock(rlm);
        __CFRunLoopTimerLock(rlt);
	timerHandled = true;
	__CFRunLoopTimerUnsetFiring(rlt);
    }
    if (__CFIsValid(rlt) && timerHandled) {
        /* This is just a little bit tricky: we want to support calling * CFRunLoopTimerSetNextFireDate() from within the callout and * honor that new time here if it is a later date, otherwise * it is completely ignored. */
        if (oldFireTSR < rlt->_fireTSR) {
            /* Next fire TSR was set, and set to a date after the previous * fire date, so we honor it. */
            __CFRunLoopTimerUnlock(rlt);
            // The timer was adjusted and repositioned, during the
            // callout, but if it was still the min timer, it was
            // skipped because it was firing. Need to redo the
            // min timer calculation in case rlt should now be that
            // timer instead of whatever was chosen.
            __CFArmNextTimerInMode(rlm, rl);
        } else {
	    uint64_t nextFireTSR = 0LL;
            uint64_t intervalTSR = 0LL;
            if (rlt->_interval <= 0.0) {}else if (TIMER_INTERVAL_LIMIT < rlt->_interval) {
        	intervalTSR = __CFTimeIntervalToTSR(TIMER_INTERVAL_LIMIT);
            } else {
        	intervalTSR = __CFTimeIntervalToTSR(rlt->_interval);
            }
            if (LLONG_MAX - intervalTSR <= oldFireTSR) {
                nextFireTSR = LLONG_MAX;
            } else {
                if (intervalTSR == 0) {
                    // 15304159: Make sure we don't accidentally loop forever here
                    CRSetCrashLogMessage("A CFRunLoopTimer with an interval of 0 is set to repeat");
                    HALT;
                }
                uint64_t currentTSR = mach_absolute_time();
                nextFireTSR = oldFireTSR;
                while (nextFireTSR <= currentTSR) {
                    nextFireTSR += intervalTSR;
                }
            }
            CFRunLoopRef rlt_rl = rlt->_runLoop;
            if (rlt_rl) {
                CFRetain(rlt_rl);
		CFIndex cnt = CFSetGetCount(rlt->_rlModes);
		STACK_BUFFER_DECL(CFTypeRef, modes, cnt);
		CFSetGetValues(rlt->_rlModes, (const void **)modes);
		// To avoid A->B, B->A lock ordering issues when coming up
		// towards the run loop from a source, the timer has to be
		// unlocked, which means we have to protect from object
		// invalidation, although that's somewhat expensive.
		for (CFIndex idx = 0; idx < cnt; idx++) {
		    CFRetain(modes[idx]);
		}
		__CFRunLoopTimerUnlock(rlt);
		for (CFIndex idx = 0; idx < cnt; idx++) {
		    CFStringRef name = (CFStringRef)modes[idx];
		    modes[idx] = (CFTypeRef)__CFRunLoopFindMode(rlt_rl, name, false);
		    CFRelease(name);
		}
		__CFRunLoopTimerFireTSRLock();
		rlt->_fireTSR = nextFireTSR;
                rlt->_nextFireDate = CFAbsoluteTimeGetCurrent() + __CFTimeIntervalUntilTSR(nextFireTSR);
		for (CFIndex idx = 0; idx < cnt; idx++) {
		    CFRunLoopModeRef rlm = (CFRunLoopModeRef)modes[idx];
		    if (rlm) {
                        __CFRepositionTimerInMode(rlm, rlt, true);
		    }
		}
		__CFRunLoopTimerFireTSRUnlock();
		for (CFIndex idx = 0; idx < cnt; idx++) {
		    __CFRunLoopModeUnlock((CFRunLoopModeRef)modes[idx]);
		}
		CFRelease(rlt_rl);
	    } else{ __CFRunLoopTimerUnlock(rlt); __CFRunLoopTimerFireTSRLock(); rlt->_fireTSR = nextFireTSR; rlt->_nextFireDate = CFAbsoluteTimeGetCurrent() + __CFTimeIntervalUntilTSR(nextFireTSR); __CFRunLoopTimerFireTSRUnlock(); }}}else {
        __CFRunLoopTimerUnlock(rlt);
    }
    CFRelease(rlt);
    return timerHandled;
}
Copy the code

Here, similar to how Runloop handles blocks, There is also a compelling function call to CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION(RLT ->_callout, RLT, context_info); , its function prototype is:

static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__() __attribute__((noinline));
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(CFRunLoopTimerCallBack func, CFRunLoopTimerRef timer, void *info) {
    if (func) {
        func(timer, info);
    }
    asm __volatile__(""); // thwart tail-call optimization
}
Copy the code

The first parameter, CFRunLoopTimerCallBack func, is the callback function specified for the timer. The third argument, info, can be used to pass arguments.

How is timer added to runloop

The CFRunLoopAddTimer function is used to add a timer to the runloop, that is, to the mode item container of the specified runloopMode.

void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) {
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return;
    if(! __CFIsValid(rlt) || (NULL! = rlt->_runLoop && rlt->_runLoop ! = rl))return;
    __CFRunLoopLock(rl);
    / / if commonMode
    if (modeName == kCFRunLoopCommonModes) {
        // First extract runloop commonModes, strings
	    CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
	    if (NULL == rl->_commonModeItems) {
            // Set runloop commonModeItems to lazy loading
	        rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
	    }
        // Add timer to commonModeItems collection.
	    CFSetAddValue(rl->_commonModeItems, rlt);
	    if (NULL! =set) {
	        CFTypeRef context[2] = {rl, rlt};
	        /* add new item to all common-modes */
            / / to each element in the commonModes __CFRunLoopAddItemToCommonModes all the function
            // Add the same timer to multiple modes.
            // That is, use commonMode to synchronize timer to multiple modes.
            // The commonMode flags are DefaultMode and UITrackingMode.
	        CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
	        CFRelease(set); }}else {
        // Not commonMode, but an actual mode marked with commonMode
	    CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true);
	    if (NULL! = rlm) {if (NULL == rlm->_timers) {
                CFArrayCallBacks cb = kCFTypeArrayCallBacks;
                cb.equal = NULL;
                // Add the timer to the timers array corresponding to the mode of the final runloop.
                rlm->_timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &cb); }}if (NULL! = rlm && ! CFSetContainsValue(rlt->_rlModes, rlm->_name)) { __CFRunLoopTimerLock(rlt);if (NULL == rlt->_runLoop) {
		        rlt->_runLoop = rl;
  	        } else if(rl ! = rlt->_runLoop) { __CFRunLoopTimerUnlock(rlt); __CFRunLoopModeUnlock(rlm); __CFRunLoopUnlock(rl);return;
	        }
  	        CFSetAddValue(rlt->_rlModes, rlm->_name);
            __CFRunLoopTimerUnlock(rlt);
            __CFRunLoopTimerFireTSRLock();
            __CFRepositionTimerInMode(rlm, rlt, false);
            __CFRunLoopTimerFireTSRUnlock();
            if(! _CFExecutableLinkedOnOrAfter(CFSystemVersionLion)) {// Normally we don't do this on behalf of clients, but for
                // backwards compatibility due to the change in timer handling...
                if (rl != CFRunLoopGetCurrent()) CFRunLoopWakeUp(rl);
            }
	    }
        if (NULL! = rlm) { __CFRunLoopModeUnlock(rlm); } } __CFRunLoopUnlock(rl); }Copy the code

Using CFSetAddValue (RLT – > _rlModes RLM – > _name); And __CFRepositionTimerInMode (RLM, RLT, false); The timer function adds the timer to the specified runloopMode. __CFRepositionTimerInMode function executes CFArrayInsertValueAtIndex (timerArray newIdx, RLT); Add timer to the _timers container of runloopMode and call __CFArmNextTimerInMode(RLM, RLT ->_runLoop); The function loads the timer.

So the call chain of a Runloop timer is straightforward:

add timer to runloop forMode -> runloop is awakened to execute the task -> __CFRunLoopDoTimer -> __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(RLT ->_callout, RLT, context_info) -> Execute timer callback func(timer, info)Copy the code

This function involves quite a bit of CommonModes code and is quite helpful in understanding CommonModes. At the same time, it can be seen that the timer does not care about the activity of runloop, but only triggers the callback after the runloop is woken up. After the callback is executed, there is an operation to set when the next trigger will occur.

Process the Sources0 task __CFRunLoopDoSources0

__CFRunLoopDoSources0(rl, rlm, stopAfterHandle); Used to process Source0 tasks.

Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
if (sourceHandledThisLoop) {
    // Why is there a DoBlocks??
    __CFRunLoopDoBlocks(rl, rlm);
}
Copy the code

Its function source code is:

/* rl is locked, rlm is locked on entrance and exit */
static Boolean __CFRunLoopDoSources0(CFRunLoopRef rl, CFRunLoopModeRef rlm, Boolean stopAfterHandle) __attribute__((noinline));
static Boolean __CFRunLoopDoSources0(CFRunLoopRef rl, CFRunLoopModeRef rlm, Boolean stopAfterHandle) {	/* DOES CALLOUT */
    CHECK_FOR_FORK();
    CFTypeRef sources = NULL;
    Boolean sourceHandled = false;

    /* Fire the version 0 sources */
    if (NULL! = rlm->_sources0 &&0 < CFSetGetCount(rlm->_sources0)) {
        // Execute __CFRunLoopCollectSources0 for sources0 in the mode of the runloop.
	    CFSetApplyFunction(rlm->_sources0, (__CFRunLoopCollectSources0), &sources);
    }
    if (NULL! = sources) { __CFRunLoopModeUnlock(rlm); __CFRunLoopUnlock(rl);// sources is either a single (retained) CFRunLoopSourceRef or an array of (retained) CFRunLoopSourceRef
	    if (CFGetTypeID(sources) == CFRunLoopSourceGetTypeID()) {
	        CFRunLoopSourceRef rls = (CFRunLoopSourceRef)sources;
	        __CFRunLoopSourceLock(rls);
            if (__CFRunLoopSourceIsSignaled(rls)) {
	            __CFRunLoopSourceUnsetSignaled(rls);
	            if (__CFIsValid(rls)) {
	                __CFRunLoopSourceUnlock(rls);
                    // Call the perform callback set in source0__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(rls->_context.version0.perform, rls->_context.version0.info);  CHECK_FOR_FORK(); sourceHandled =true;
	            } else{ __CFRunLoopSourceUnlock(rls); }}else{ __CFRunLoopSourceUnlock(rls); }}else {
	        CFIndex cnt = CFArrayGetCount((CFArrayRef)sources);
	        CFArraySortValues((CFMutableArrayRef)sources, CFRangeMake(0, cnt), (__CFRunLoopSourceComparator), NULL);
	        for (CFIndex idx = 0; idx < cnt; idx++) {
		        CFRunLoopSourceRef rls = (CFRunLoopSourceRef)CFArrayGetValueAtIndex((CFArrayRef)sources, idx);
		        __CFRunLoopSourceLock(rls);
                if (__CFRunLoopSourceIsSignaled(rls)) {
		            __CFRunLoopSourceUnsetSignaled(rls);
		            if(__CFIsValid(rls)) { __CFRunLoopSourceUnlock(rls); __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(rls->_context.version0.perform, rls->_context.version0.info);  CHECK_FOR_FORK(); sourceHandled =true;
		            } else{ __CFRunLoopSourceUnlock(rls); }}else {
                    __CFRunLoopSourceUnlock(rls);
                }
		        if (stopAfterHandle && sourceHandled) {
		            break;
		        }
	        }
	    }
	    CFRelease(sources);
	    __CFRunLoopLock(rl);
	    __CFRunLoopModeLock(rlm);
    }
    return sourceHandled;
}
Copy the code

CFRunLoopSourceContext stores the source callback, which can receive the parameter info.

union {
	CFRunLoopSourceContext version0;	/* immutable, except invalidation */
    CFRunLoopSourceContext1 version1;	/* immutable, except invalidation */
} _context;
Copy the code

Which invokes the CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION (RLS – > _context. Version0. Perform, rls->_context.version0.info); To carry out the mission. The function’s parameter Perform is the callback function, and the parameter info is the parameter accepted by the callback function.

static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__() __attribute__((noinline));
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(void (*perform)(void *), void *info) {
    if (perform) {
        perform(info);
    }
    asm __volatile__(""); // thwart tail-call optimization
}
Copy the code

How does Source0 wake up Runloop

To execute a task for source0, source0 must be set to Signal and then manually call wakeUp to wakeUp runloop in order to qualify for its callout function.

void CFRunLoopSourceSignal(CFRunLoopSourceRef rls) {
    CHECK_FOR_FORK();
    __CFRunLoopSourceLock(rls);
    if (__CFIsValid(rls)) {
	__CFRunLoopSourceSetSignaled(rls);
    }
    __CFRunLoopSourceUnlock(rls);
}
Copy the code

The source0 task can be executed by calling CFRunLoopSourceSignal, setting the source0 to Signal, and calling wakeUp to wakeUp runloop. In the cfsocket.c file there is code like this:

if (shared->_source) {
    CFRunLoopSourceSignal(shared->_source);
    _CFRunLoopSourceWakeUpRunLoops(shared->_source);
}
Copy the code

And _CFRunLoopSourceWakeUpRunLoops is runloop the contents of the source code, including call __CFRunLoopSourceWakeUpLoop function, which in turn calls CFRunLoopWakeUp function.

CF_PRIVATE void _CFRunLoopSourceWakeUpRunLoops(CFRunLoopSourceRef rls) {
    CFBagRef loops = NULL;
    __CFRunLoopSourceLock(rls);
    if (__CFIsValid(rls) && NULL! = rls->_runLoops) { loops = CFBagCreateCopy(kCFAllocatorSystemDefault, rls->_runLoops); } __CFRunLoopSourceUnlock(rls);if (loops) {
	    CFBagApplyFunction(loops, __CFRunLoopSourceWakeUpLoop, NULL); CFRelease(loops); }}static void __CFRunLoopSourceWakeUpLoop(const void *value, void *context) {
    CFRunLoopWakeUp((CFRunLoopRef)value);
}
Copy the code

As stated in the CFRunLoopWakeUp function, the wakeup operation is placed in a queue of length 1. Therefore, the wakeup operation is not repeated. This is done by calling __CFSendTrivialMachMessage(rL ->_wakeUpPort, 0, MACH_SEND_TIMEOUT, 0) to send a message to the _wakeUpPort of the runloop. Inside the __CFSendTrivialMachMessage function, mach_msg() is actually called.

void CFRunLoopWakeUp(CFRunLoopRef rl) {
    CHECK_FOR_FORK();
    // This lock is crucial to ignorable wakeups, do not remove it.
    __CFRunLoopLock(rl);
    if (__CFRunLoopIsIgnoringWakeUps(rl)) {
        __CFRunLoopUnlock(rl);
        return;
    }
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
    kern_return_t ret;
    /* We unconditionally try to send the message, since we don't want * to lose a wakeup, but the send may fail if there is already a * wakeup pending, since the queue length is 1. */
    ret = __CFSendTrivialMachMessage(rl->_wakeUpPort, 0, MACH_SEND_TIMEOUT, 0);
    if(ret ! = MACH_MSG_SUCCESS && ret ! = MACH_SEND_TIMED_OUT) CRASH("*** Unable to send message to wake up port. (%d) ***", ret);
#elif DEPLOYMENT_TARGET_WINDOWS
    SetEvent(rl->_wakeUpPort);
#endif
    __CFRunLoopUnlock(rl);
}

static uint32_t __CFSendTrivialMachMessage(mach_port_t port, uint32_t msg_id, CFOptionFlags options, uint32_t timeout) {
    kern_return_t result;
    mach_msg_header_t header;
    header.msgh_bits = MACH_MSGH_BITS(MACH_MSG_TYPE_COPY_SEND, 0);
    header.msgh_size = sizeof(mach_msg_header_t);
    header.msgh_remote_port = port;
    header.msgh_local_port = MACH_PORT_NULL;
    header.msgh_id = msg_id;
    result = mach_msg(&header, MACH_SEND_MSG|options, header.msgh_size, 0, MACH_PORT_NULL, timeout, MACH_PORT_NULL);
    if (result == MACH_SEND_TIMED_OUT) mach_msg_destroy(&header);
    return result;
}
Copy the code

Source0 is also used to wake up the runloop using Mach MSG, the Mach port being the _wakeUpPort of the Runloop object. Set Source0 to Singal and call CFRunLoopWakeUp with mach_msg(). This is consistent with the way runloop is finally woken up in __CFRunLoopServiceMachPort, and it does lead to the same thing. This is obviously reasonable, because the operation of waking up runloop is actually a state switch of the Mach kernel, i.e. user => kernel state, where communication is only possible through Mach MSG.

How is Source0 added to runloop

The CFRunLoopAddSource function adds the Source of the runloop to the specified runloopMode.

Where is the perform function pointer specified? Info is the parameter.

void CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef rls, CFStringRef modeName) {	/* DOES CALLOUT */
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return;
    if(! __CFIsValid(rls))return;
    Boolean doVer0Callout = false;
    __CFRunLoopLock(rl);
    if (modeName == kCFRunLoopCommonModes) {
	    CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
	    if (NULL == rl->_commonModeItems) {
	        rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
	    }
	    CFSetAddValue(rl->_commonModeItems, rls);

	    if (NULL! =set) {
	        CFTypeRef context[2] = {rl, rls};
	        /* add new item to all common-modes */
	        CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
	        CFRelease(set); }}else {
	    CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true);
	    if (NULL! = rlm &&NULL == rlm->_sources0) {
	        rlm->_sources0 = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
	        rlm->_sources1 = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
	        rlm->_portToV1SourceMap = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0.NULL.NULL);
	    }
	    if (NULL! = rlm && ! CFSetContainsValue(rlm->_sources0, rls) && ! CFSetContainsValue(rlm->_sources1, rls)) {if (0 == rls->_context.version0.version) {
	            CFSetAddValue(rlm->_sources0, rls);
	        } else if (1 == rls->_context.version0.version) {
	            CFSetAddValue(rlm->_sources1, rls);
		        __CFPort src_port = rls->_context.version1.getPort(rls->_context.version1.info);
		        if(CFPORT_NULL ! = src_port) { CFDictionarySetValue(rlm->_portToV1SourceMap, (const void(*)uintptr_t)src_port, rls);
		            __CFPortSetInsert(src_port, rlm->_portSet);
	            }
	        }
	        __CFRunLoopSourceLock(rls);
	        if (NULL == rls->_runLoops) {
	            rls->_runLoops = CFBagCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeBagCallBacks); // sources retain run loops!
	        }
	        CFBagAddValue(rls->_runLoops, rl);
	        __CFRunLoopSourceUnlock(rls);
	        if (0 == rls->_context.version0.version) {
	            if (NULL! = rls->_context.version0.schedule) { doVer0Callout =true; }}}if (NULL! = rlm) { __CFRunLoopModeUnlock(rlm); } } __CFRunLoopUnlock(rl);if (doVer0Callout) {
        // although it looses some protection for the source, we have no choice but
        // to do this after unlocking the run loop and mode locks, to avoid deadlocks
        // where the source wants to take a lock which is already held in another
        // thread which is itself waiting for a run loop/mode lock
	    rls->_context.version0.schedule(rls->_context.version0.info, rl, modeName);	/* CALLOUT */}}Copy the code

The CFRunLoopAddSource function adds this source to the Mode items of runloopMode. For common mode, call CFSetAddValue(rl->_commonModeItems, RLS); ; If it is source0, CFSetAddValue(RLM ->_sources0, RLS) is called; ; If it is source1, call ***CFSetAddValue(RLM ->_sources1, RLS); ***, as well as port Settings.

For source0, there is a Schedule callback that is triggered when source is added to runloop, Use the RLS – > _context. Version0. The schedule (RLS – > _context. Version0. Info, rl, modeName); / CALLOUT /.

Process the Sources1 task __CFRunLoopDoSource1

sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop; Is called after runloop has been woken up.

// msg, size and reply are unused on Windows
static Boolean __CFRunLoopDoSource1() __attribute__((noinline));
static Boolean __CFRunLoopDoSource1(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopSourceRef rls
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
                                    , mach_msg_header_t *msg, CFIndex size, mach_msg_header_t **reply
#endif
                                    ) {	/* DOES CALLOUT */
    CHECK_FOR_FORK();
    Boolean sourceHandled = false;

    /* Fire a version 1 source */
    CFRetain(rls);
    __CFRunLoopModeUnlock(rlm);
    __CFRunLoopUnlock(rl);
    __CFRunLoopSourceLock(rls);
    if (__CFIsValid(rls)) {
	__CFRunLoopSourceUnsetSignaled(rls);
	__CFRunLoopSourceUnlock(rls);
        __CFRunLoopDebugInfoForRunLoopSource(rls);
        Perform the perform callback set in source1.
        __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(rls->_context.version1.perform,
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
            msg, size, reply,
#endif
            rls->_context.version1.info);
	CHECK_FOR_FORK();
	sourceHandled = true;
    } else {
        if (_LogCFRunLoop) { CFLog(kCFLogLevelDebug, CFSTR("%p (%s) __CFRunLoopDoSource1 rls %p is invalid"), CFRunLoopGetCurrent(), *_CFGetProgname(), rls); }
	__CFRunLoopSourceUnlock(rls);
    }
    CFRelease(rls);
    __CFRunLoopLock(rl);
    __CFRunLoopModeLock(rlm);
    return sourceHandled;
}
Copy the code

The code to execute the task is:

__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(rls->_context.version1.perform,
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
            msg, size, reply,
#endif
            rls->_context.version1.info);
Copy the code

The __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ function prototype is as follows:

// Runloop is woken up to handle the event, handling the source1 event.
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__() __attribute__((noinline));
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
        void *(*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info),
        mach_msg_header_t *msg, CFIndex size, mach_msg_header_t **reply,
#else
        void (*perform)(void *),
#endif
        void *info) {
    if (perform) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
        *reply = perform(msg, size, kCFAllocatorSystemDefault, info);
#else
        perform(info);
#endif
    }
    asm __volatile__(""); // thwart tail-call optimization
}
Copy the code

The function has many arguments, MSG is a Mach MSG object and reply is the reply message after the Mach MSG message communication is executed. Perform is a callback pointer that contains four parameters, and info is also used to pass parameters to the callback function.

How does Source1 wake up Runloop

Call __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(MSG_buffer), &livePort, poll? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy, rl, rlm); Internally, the mach_msg() function is called to complete the Mach kernel’s state change, even if runloop is asleep.

How is Source1 added to runloop

Source1 adds the same code to runloop as source0, except for the port setting.

__CFPort src_port = rls->_context.version1.getPort(rls->_context.version1.info);
if(CFPORT_NULL ! = src_port) { CFDictionarySetValue(rlm->_portToV1SourceMap, (const void(*)uintptr_t)src_port, rls);
    __CFPortSetInsert(src_port, rlm->_portSet);
}
Copy the code

Handles the Observer task __CFRunLoopDoObservers

The __CFRunLoopDoObservers function notifies the Observer of six states of runloop.

  1. __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry); About to enter runloop
  2. __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers); About to process the timer
  3. __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources); Source0 is about to be processed
  4. __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting); About to go to sleep
  5. __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting); To awaken from hibernation
  6. __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit); Exit runloop

If Source1 wakes up runloop directly, the goTO statement is used to jump to handLE_msg execution without notification of AfterWaiting status! So AfterWaiting notifications are only for non-source1 wake up scenarios, where the runloop is woken up manually via UITouch events, timers, etc.

/* rl is locked, rlm is locked on entrance and exit */
static void __CFRunLoopDoObservers() __attribute__((noinline));
static void __CFRunLoopDoObservers(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopActivity activity) {	/* DOES CALLOUT */CHECK_FOR_FORK(); Get the maximum number of observers for runloop mode1024A? CFIndex cnt = rlm->_observers ? CFArrayGetCount(rlm->_observers) :0;
    if (cnt < 1) return;

    /* Fire the observers */
    STACK_BUFFER_DECL(CFRunLoopObserverRef, buffer, (cnt <= 1024)? cnt :1);
    CFRunLoopObserverRef *collectedObservers = (cnt <= 1024)? buffer : (CFRunLoopObserverRef *)malloc(cnt * sizeof(CFRunLoopObserverRef));
    // Get the CFRunLoopObserverRef object by iterating through all observers of runloop mode. Collect valid observers.
    CFIndex obs_cnt = 0;
    for (CFIndex idx = 0; idx < cnt; idx++) {
        CFRunLoopObserverRef rlo = (CFRunLoopObserverRef)CFArrayGetValueAtIndex(rlm->_observers, idx);
        if (0! = (rlo->_activities & activity) && __CFIsValid(rlo) && ! __CFRunLoopObserverIsFiring(rlo)) { collectedObservers[obs_cnt++] = (CFRunLoopObserverRef)CFRetain(rlo); } } __CFRunLoopModeUnlock(rlm); __CFRunLoopUnlock(rl);// Perform a callback to all observers
    for (CFIndex idx = 0; idx < obs_cnt; idx++) {
        CFRunLoopObserverRef rlo = collectedObservers[idx];
        __CFRunLoopObserverLock(rlo);
        if(__CFIsValid(rlo)) { Boolean doInvalidate = ! __CFRunLoopObserverRepeats(rlo); __CFRunLoopObserverSetFiring(rlo); __CFRunLoopObserverUnlock(rlo);// Execute the observer callback rlo->_callout to wake up the runloop and set it to the __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ state.
            __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(rlo->_callout, rlo, activity, rlo->_context.info);
            if (doInvalidate) {
                CFRunLoopObserverInvalidate(rlo);
            }
            __CFRunLoopObserverUnsetFiring(rlo);
        } else {
            __CFRunLoopObserverUnlock(rlo);
        }
        CFRelease(rlo);
    }
    __CFRunLoopLock(rl);
    __CFRunLoopModeLock(rlm);

    if(collectedObservers ! = buffer)free(collectedObservers);
}
Copy the code

For each valid Observer in this function, CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION(rLO ->_callout, rLO, activity, rLO ->_context.info); . This function takes four parameters: func, the observer callback, the Observer itself, the runloop state, the activity object, and the info object, which can be used to pass parameters.

static void __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__() __attribute__((noinline));
static void __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(CFRunLoopObserverCallBack func, CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
    if (func) {
        func(observer, activity, info);
    }
    asm __volatile__(""); // thwart tail-call optimization
}
Copy the code

How is an Observer added to runloop

void CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef rlo, CFStringRef modeName) {
    CHECK_FOR_FORK();
    CFRunLoopModeRef rlm;
    if (__CFRunLoopIsDeallocating(rl)) return;
    if(! __CFIsValid(rlo) || (NULL! = rlo->_runLoop && rlo->_runLoop ! = rl))return;
    __CFRunLoopLock(rl);
    if (modeName == kCFRunLoopCommonModes) {
	    CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
	    if (NULL == rl->_commonModeItems) {
	        rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
	    }
	    CFSetAddValue(rl->_commonModeItems, rlo);
	    if (NULL! =set) {
	        CFTypeRef context[2] = {rl, rlo};
	        /* add new item to all common-modes */
	        CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
	        CFRelease(set); }}else {
	    rlm = __CFRunLoopFindMode(rl, modeName, true);
	    if (NULL! = rlm &&NULL == rlm->_observers) {
	        rlm->_observers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks);
	    }
	    if (NULL! = rlm && ! CFArrayContainsValue(rlm->_observers, CFRangeMake(0, CFArrayGetCount(rlm->_observers)), rlo)) {
            Boolean inserted = false;
            for (CFIndex idx = CFArrayGetCount(rlm->_observers); idx--; ) {
                CFRunLoopObserverRef obs = (CFRunLoopObserverRef)CFArrayGetValueAtIndex(rlm->_observers, idx);
                if (obs->_order <= rlo->_order) {
                    CFArrayInsertValueAtIndex(rlm->_observers, idx + 1, rlo);
                    inserted = true;
                    break; }}if(! inserted) { CFArrayInsertValueAtIndex(rlm->_observers,0, rlo);
            }
	        rlm->_observerMask |= rlo->_activities;
	        __CFRunLoopObserverSchedule(rlo, rl, rlm);
	    }
        if (NULL! = rlm) { __CFRunLoopModeUnlock(rlm); } } __CFRunLoopUnlock(rl); }Copy the code

Adding an Observer to a runloop is similar to adding a timer or source to a runloop, with commonModes considered (CFSetAddValue(rl->_commonModeItems, rlo);). . Not under the commonMode, direct call CFArrayInsertValueAtIndex (RLM – > _observers, independence idx + 1, rlo); Add them to _observers of runloopMode.

Obsever and Timer can only be added to one runloop, while sources can be added to multiple runloops. CFRunLoopTimerRef and CFRunLoopObserverRef have only one CFRunLoopRef _runLoop; CFRunLoopSourceRef has a CFMutableBagRef _runLoops; The container.

Something about the main thread

The __CFRunLoopRun function has a main queue:

mach_port_name_t dispatchPort = MACH_PORT_NULL;
// check whether the queue is main_queue.
Boolean libdispatchQSafe = pthread_main_np() && ((HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL== previousMode) || (! HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY &&0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ)));
// dispatchPort is the Mach port required by the main thread task dispatch
// If it is a runloop of the main thread and is CommonModes
if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name)) {
    dispatchPort = _dispatch_get_main_queue_port_4CF();
}
Copy the code

Otherwise, dispatchPort is MACH_PORT_NULL. Only in the main thread runloop does dispatchPort have a value, meaning that dispatchPort is used exclusively in the main thread.

DispatchPort, modeQueuePort

The dispatchPort is the host thread’s port for sending and receiving Mach MSG. The other part of the port should be libDispatch. Dispatch_async (dispatch_get_main_queue(), ^{XXX}).

Dispatch_async (dispatch_get_main_queue(), ^{XXX})) Is dispatchPort? When dispatch_async(dispatch_get_main_queue(), block) is called, the main queue will put the block in the corresponding thread (which happens to be the main thread), and the main thread’s RunLoop will wake up. Get the block from the message and call CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE() to execute the block:

The _dispatch_get_main_queue_port_4CF() function is as follows:

dispatch_runloop_handle_t
_dispatch_get_main_queue_port_4CF(void)
{
	return _dispatch_get_main_queue_handle_4CF();
}

dispatch_runloop_handle_t
_dispatch_get_main_queue_handle_4CF(void)
{
	dispatch_queue_main_t dq = &_dispatch_main_q;
	dispatch_once_f(&_dispatch_main_q_handle_pred, dq,
			_dispatch_runloop_queue_handle_init);
	return _dispatch_runloop_queue_get_handle(dq->_as_dl);
}

DISPATCH_ALWAYS_INLINE
static inline dispatch_runloop_handle_t
_dispatch_runloop_queue_get_handle(dispatch_lane_t dq)
{
#if TARGET_OS_MAC
	return ((dispatch_runloop_handle_t) (uintptr_t)dq->do_ctxt);
#elif defined(__linux__)
	// decode: 0 is a valid fd, so offset by 1 to distinguish from NULL
	return ((dispatch_runloop_handle_t) (uintptr_t)dq->do_ctxt) - 1;
#else
#error "runloop support not implemented on this platform"
#endif
}
Copy the code

On the MAC platform, dispatch_RUNloop_HANDLE_t is actually mach_port_t.

#if TARGET_OS_MAC
typedef mach_port_t dispatch_runloop_handle_t;
#elif defined(__linux__)
typedef int dispatch_runloop_handle_t;
#else
#error "runloop support not implemented on this platform"
#endif
Copy the code

In addition, there is a modeQueuePort only available on MACOSX, which is the mach_port_name_t object of the queue where the current runloop resides.

#if TARGET_OS_MAC
dispatch_runloop_handle_t
_dispatch_runloop_root_queue_get_port_4CF(dispatch_queue_t dq)
{
	if(unlikely(dx_type(dq) ! = DISPATCH_QUEUE_RUNLOOP_TYPE)) { DISPATCH_CLIENT_CRASH(dx_type(dq),"Not a runloop queue");
	}
	return _dispatch_runloop_queue_get_handle(upcast(dq)._dl);
}
#endif
Copy the code

__CFRUNLOOP_IS_CALLING and __CFRUNLOOP_IS_SERVICING, which are called callout functions in runloop. The actual code is pretty simple, but it makes sense that the function names get so much attention because they’re where the real work is.

The main thread – dependent callout function

After the runloop is awakened, if the livePort of the awakened runloop is the dispatchPort of the main thread, the callout function CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE is executed.

if (livePort == dispatchPort) {
    CFRUNLOOP_WAKEUP_FOR_DISPATCH();
    __CFRunLoopModeUnlock(rlm);
    __CFRunLoopUnlock(rl);
    _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6.NULL);
    /// dispatchPort is the main thread. Yes!
    /// Execute if there are blocks to the main queue.
    __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
    _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0.NULL);
    __CFRunLoopLock(rl);
    __CFRunLoopModeLock(rlm);
    sourceHandledThisLoop = true;
    didDispatchPortLastTime = true;
}
Copy the code

CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE is based on Mach MSG and its function prototype is:

static void __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__() __attribute__((noinline));
static void __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(void *msg) {
    _dispatch_main_queue_callback_4CF(msg);
    asm __volatile__(""); // thwart tail-call optimization
}
Copy the code

Mach MSG is required to execute the GCD event of the main thread. The MSG in this case is the MSG used when runloop sleep is awakened.

For the _dispatch_main_queue_callback_4CF() function, see the libdispatch source file in queue.c. The internal implementation of this function is obviously a topic for another day. All you know is that it schedules and executes tasks in the Main Queue.

void
_dispatch_main_queue_callback_4CF(
		void *ignored DISPATCH_UNUSED)
{
	// the main queue cannot be suspended and no-one looks at this bit
	// so abuse it to avoid dirtying more memory

	if (_dispatch_main_q.dq_side_suspend_cnt) {
		return;
	}
	_dispatch_main_q.dq_side_suspend_cnt = true;
	_dispatch_main_queue_drain(&_dispatch_main_q);
	_dispatch_main_q.dq_side_suspend_cnt = false;
}
Copy the code

However, the Mach MSG is passed to the GCD without using…

Mach MSG before runloop sleep

We notice that the __CFRunLoopServiceMachPort function is called once before entering the inner loop do-while(1), when runloop is about to sleep.

if(MACH_PORT_NULL ! = dispatchPort && ! didDispatchPortLastTime) {#if TARGET_OS_MAC
    msg = (mach_msg_header_t *)msg_buffer;
    if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL, rl, rlm)) {
        gotohandle_msg; }}Copy the code

This step calls __CFRunLoopServiceMachPort to ensure that blocks from GCD to main Queue are always executed as quickly as possible. However, it will not be called on the first outer loop run by runloop, or the last runloop wake up that was not due to the GCD main thread.

Something about GCD

// Usually use GCD, {NSLog(@"dispatch_get_global_queue"); ^{"dispatch_get_global_queue"); });Copy the code

The child thread uses GCD and breakpoints do not appear in runloop callout. Since this is handled by GCD itself, it has nothing to do with Runloop.

The main thread is different:

dispatch_async(dispatch_get_main_queue(), ^{
    NSLog(@"dispatch_get_main_queue");
});
Copy the code

Dispatch_async (dispatch_get_global_queue(0, 0), ^{dispatch_async(dispatch_get_main_queue()), ^{ Runloop callout will appear, why? // With GCD asynchrony, we need to rely on RunLoop to return to the main thread after a child thread has finished processing something. The __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ function is called internally. NSLog(@"dispatch_get_main_queue"); }); });Copy the code

The main thread is executed by runloop.

How does GCD wake up Runloop

Other things you might need to pay attention to

In addition to the main flow of runloop above, there are a few other things you might want to notice.

Runloop-related state Settings

Some of the state of the runloop itself is stored by _per_run_data, such as RL ->_perRunData-> Stopped and RL ->_perRunData->ignoreWakeUps, as described in the previous data structures section.

In addition, runloop’s _cfinfo[CF_INFO_BITS] is used to query and set some states of runloop, such as isSleeping and isDeallocating.

CF_INLINE Boolean __CFRunLoopIsSleeping(CFRunLoopRef rl) {
    return (Boolean)__CFBitfieldGetValue(((const CFRuntimeBase *)rl)->_cfinfo[CF_INFO_BITS], 1.1);
}

CF_INLINE void __CFRunLoopSetSleeping(CFRunLoopRef rl) {
    __CFBitfieldSetValue(((CFRuntimeBase *)rl)->_cfinfo[CF_INFO_BITS], 1.1.1);
}

CF_INLINE void __CFRunLoopUnsetSleeping(CFRunLoopRef rl) {
    __CFBitfieldSetValue(((CFRuntimeBase *)rl)->_cfinfo[CF_INFO_BITS], 1.1.0);
}

CF_INLINE Boolean __CFRunLoopIsDeallocating(CFRunLoopRef rl) {
    return (Boolean)__CFBitfieldGetValue(((const CFRuntimeBase *)rl)->_cfinfo[CF_INFO_BITS], 2.2);
}

CF_INLINE void __CFRunLoopSetDeallocating(CFRunLoopRef rl) {
    __CFBitfieldSetValue(((CFRuntimeBase *)rl)->_cfinfo[CF_INFO_BITS], 2.2.1);
}
Copy the code

Runloop’s Source0 contains only a callback, and isSignaled is used to indicate whether Source0 can be processed.

/* Bit 1 of the base reserved bits is used for signalled state */

CF_INLINE Boolean __CFRunLoopSourceIsSignaled(CFRunLoopSourceRef rls) {
    return (Boolean)__CFBitfieldGetValue(rls->_bits, 1.1);
}

CF_INLINE void __CFRunLoopSourceSetSignaled(CFRunLoopSourceRef rls) {
    __CFBitfieldSetValue(rls->_bits, 1.1.1);
}

CF_INLINE void __CFRunLoopSourceUnsetSignaled(CFRunLoopSourceRef rls) {
    __CFBitfieldSetValue(rls->_bits, 1.1.0);
}
Copy the code

In addition, there are isFiring of observer and isFiring of timer, etc., which are not discussed in detail here.

Runloop How to switch mode

There are many lock and unlock operations in the source code.

__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
Copy the code

In __CFRunLoopRun, there are three unlock mode operations:

  1. Runloop is about to hibernate
  2. When runloop is woken up, it processes the task of GCD Main
  3. After runloop exits

The Runloop may switch the runloopMode at this time.

Manual wake up of runloop

By manual wakeup, we mean calling the CFRunLoopWakeUp function.

The CFRunLoopWakeUp function was explained earlier. The wakeup operation is placed in a queue of length 1. Therefore, the wakeup operation is not repeated. This is done by calling __CFSendTrivialMachMessage(rL ->_wakeUpPort, 0, MACH_SEND_TIMEOUT, 0) to send a message to the _wakeUpPort of the runloop. Inside the __CFSendTrivialMachMessage function, mach_msg() is actually called.

The conclusion is that runloop wakeup is essentially mach_msg, which means that switching state of the Mach kernel is really only possible with Mach MSG.

Runloop timeout

The runloop’s running time is passed in externally, and the correct running time is ensured internally by a GCD timer or mk timer. The runloop timeout callback is __CFRunLoopTimeout. Runloop cancels the __CFRunLoopTimeoutCancel callback function.

#if __HAS_DISPATCH__
    The __CFRunLoopTimeout function is called when the runloop timer expires
    dispatch_source_t timeout_timer = NULL;
#endif
    struct __timeout_context *timeout_context = (struct __timeout_context *)malloc(sizeof(*timeout_context));
    if (seconds <= 0.0) { // instant timeout
        seconds = 0.0;
        timeout_context->termTSR = 0ULL;
    } else if (seconds <= TIMER_INTERVAL_LIMIT) {
#if __HAS_DISPATCH__
	dispatch_queue_t queue = pthread_main_np() ? __CFDispatchQueueGetGenericMatchingMain() : __CFDispatchQueueGetGenericBackground();
	timeout_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0.0.queue);
        dispatch_retain(timeout_timer);
	timeout_context->ds = timeout_timer;
#endif
	timeout_context->rl = (CFRunLoopRef)CFRetain(rl);
	timeout_context->termTSR = startTSR + __CFTimeIntervalToTSR(seconds);
#if __HAS_DISPATCH__
	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);
#endif
    } else { // infinite timeout
        seconds = 9999999999.0;
        timeout_context->termTSR = UINT64_MAX;
    }
Copy the code

__timeout_context will hold a runloop object and possibly a GCD timer object dispatch_source_t ds.

struct __timeout_context {
#if __HAS_DISPATCH__
    dispatch_source_t ds;
#endif
    CFRunLoopRef rl;
    _Atomic(uint64_t) termTSR;
};
Copy the code

How are runloop objects destroyed

Earlier, we mentioned that when the Thread object is destroyed, the TSD runloop object is also destroyed, using the __CFFinalizeRunLoop() function.

// Called for each thread as it exits
CF_PRIVATE void __CFFinalizeRunLoop(uintptr_t data) {
    CFRunLoopRef rl = NULL;
    if (data <= 1) {
	    __CFLock(&loopsLock);
	    if (__CFRunLoops) {
	        rl = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(pthread_self()));
	        if (rl) CFRetain(rl);
            // Remove the corresponding runloop object from __CFRunLoops
	        CFDictionaryRemoveValue(__CFRunLoops, pthreadPointer(pthread_self()));
	    }
	    __CFUnlock(&loopsLock);
    } else {
        // There is a recursive call
        _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(data - 1), (void(*) (void *))__CFFinalizeRunLoop);
    }
    if(rl && CFRunLoopGetMain() ! = rl) {// protect against cooperative threads
        if (NULL! = rl->_counterpart) { CFRelease(rl->_counterpart); rl->_counterpart =NULL;
        }
	    // purge all sources before deallocation
        CFArrayRef array = CFRunLoopCopyAllModes(rl);
        for (CFIndex idx = CFArrayGetCount(array); idx--;) {
            CFStringRef modeName = (CFStringRef)CFArrayGetValueAtIndex(array, idx);
            // Remove sources from runloopMode
            __CFRunLoopRemoveAllSources(rl, modeName);
        }
        __CFRunLoopRemoveAllSources(rl, kCFRunLoopCommonModes);
        CFRelease(array);
    }
    // When a thread is destroyed, its runloop is also destroyed.
    if (rl) CFRelease(rl);
}
Copy the code

Before destroying a Runloop object, remove it from the __CFRunLoops global dictionary, traverse all its modes, remove all source objects in each mode, and finally destroy the Runloop object.

What is the data argument to the __CFFinalizeRunLoop() function? This is simply the reference count of the runloop object, corresponding to the slot __CFTSDKeyRunLoopCntr. If data is greater than 1, only the value of __CFTSDKeyRunLoopCntr is reduced by 1. When data <= 1, the runloop destruction code is actually executed.

There are three calls to the __CFTSDKeyRunLoopCntr slot:

  1. _CFRunLoopGet0() : ***_CFSetTSD(__CFTSDKeyRunLoopCntr, (void))(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void ()(void ))__CFFinalizeRunLoop);**
  2. __CFFinalizeRunLoop() : ***_CFSetTSD(__CFTSDKeyRunLoopCntr, (void))(data – 1), (void ()(void ))__CFFinalizeRunLoop);**
  3. _CFRunLoopSetCurrent() : **_CFSetTSD(__CFTSDKeyRunLoopCntr, 0, (void ()(void ))__CFFinalizeRunLoop);**

When the runloopMode object is destroyed, its mode item and other objects are released accordingly:

static void __CFRunLoopModeDeallocate(CFTypeRef cf) {
    CFRunLoopModeRef rlm = (CFRunLoopModeRef)cf;
    if (NULL! = rlm->_sources0) CFRelease(rlm->_sources0);if (NULL! = rlm->_sources1) CFRelease(rlm->_sources1);if (NULL! = rlm->_observers) CFRelease(rlm->_observers);if (NULL! = rlm->_timers) CFRelease(rlm->_timers);if (NULL! = rlm->_portToV1SourceMap) CFRelease(rlm->_portToV1SourceMap); CFRelease(rlm->_name); __CFPortSetFree(rlm->_portSet);#if USE_DISPATCH_SOURCE_FOR_TIMERS
    if (rlm->_timerSource) {
        dispatch_source_cancel(rlm->_timerSource);
        dispatch_release(rlm->_timerSource);
    }
    if (rlm->_queue) {
        dispatch_release(rlm->_queue);
    }
#endif
#if USE_MK_TIMER_TOO
    if(MACH_PORT_NULL ! = rlm->_timerPort) mk_timer_destroy(rlm->_timerPort);#endif
    pthread_mutex_destroy(&rlm->_lock);
    memset((char *)cf + sizeof(CFRuntimeBase), 0x7C.sizeof(struct __CFRunLoopMode) - sizeof(CFRuntimeBase));
}
Copy the code

A special variable poll

This variable poll is given after __CFRunLoopDoSources0 is executed at the start of a run of each runloop.

Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
Copy the code

There is something strange about this poll judgment and I don’t know why it is done.

Each time the loop processes the source0 task, the poll value is true and the immediate effect is that doobservers-beforewaiting and doobservers-afterwaiting are not enabled. That is, runloop will go straight to sleep and will not tell BeforeWaiting and AfterWaiting activities. So you see, there may be cases where the runloop passes through several loops, but the Observer you registered will not receive a callback.

The resources

  1. Event loop
  2. Run Loops
  3. NSRunLoop
  4. CFRunLoop
  5. CFRunLoopPerformBlock
  6. CFRunLoopPerformBlock vs dispatch_async
  7. RunLoop.subproj
  8. Kernel Programming Guide: Mach Overview
  9. mach_msg
  10. CFPlatform.c
  11. AsyncDisplayKit has been renamed to Texture
  12. Understand RunLoop in depth
  13. Decryption Runloop
  14. Get to the bottom of iOS – Understand RunLoop in depth
  15. Optimizing the UITableViewCell height calculation stuff
  16. Revisit the RunLoop principle
  17. IOS RunLoop,