RunLoop series of articles

RunLoop (2) : Data structure RunLoop (3) : event loop mechanism RunLoop (4) : RunLoop and thread RunLoop (5) : RunLoop and NSTimer iOS – AutoRelease and @Autoreleasepool: RunLoop and @Autoreleasepool

preface

We have introduced the basic concepts of RunLoop and related data structures. In this article, we will explain how RunLoop works.

Start the RunLoop for the main thread

First let’s look at the main thread RunLoop startup process. As we mentioned earlier, the reason our iOS application keeps running is because we call UIApplicationMain inside main(), which starts the main thread RunLoop inside. The point of interruption is to view the function call stack with the LLDB instruction bt as follows:

UIApplicationMain
CFRunLoopRunSpecific

CFRunLoopRunSpecific function implementation: RunLoop entry

Check the source code for the implementation of this function, as follows:

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
    __CFRunLoopLock(rl);
    // Find the mode for this run according to modeName
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
    / / if you don't find | | mode not registered any event, in the end, not into the loop
    if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
	Boolean did = false;
	if (currentMode) __CFRunLoopModeUnlock(currentMode);
	__CFRunLoopUnlock(rl);
	return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
    }
    volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
    CFRunLoopModeRef previousMode = rl->_currentMode;
    rl->_currentMode = currentMode;
    int32_t result = kCFRunLoopRunFinished;

    // Notify Observers that they are about to enter RunLoop
	if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    // What RunLoop does
	result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    // Notify Observers that they are about to exit RunLoop
	if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

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

Cut out unimportant details:

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

    // Notify Observers that they are about to enter RunLoop
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    // What RunLoop does
	result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    // Notify Observers that they are about to exit RunLoop
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

    return result;
}
Copy the code

As you can see from the function call stack and the implementation of the CFRunLoopRunSpecific function, the implementation mechanism of the RunLoop event loop is embodied in the __CFRunLoopRun function.

Implementation of the __CFRunLoopRun function: an implementation mechanism for the event loop

Because the implementation of this function is complicated, the following is a simplified version with the details removed. If you want to explore the details, check out the Core Foundation source code.

/** * __CFRunLoopRun ** @param rl run RunLoop object * @param RLM run mode * @param seconds loop timeout * @param StopAfterHandle True: RunLoop exits after handling the event false: RunLoop runs until timeout or is manually terminated * @param previousMode Last run mode * * @return Returns 4 states */
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) 
{
    int32_t retVal = 0;
    do {
        // Notify Observers that Timers are about to be processed
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        // Notify Observers that Sources is about to be handled
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
        / / processing Blocks
    	__CFRunLoopDoBlocks(rl, rlm);
        / / Sources0 processing
        if (__CFRunLoopDoSources0(rl, rlm, stopAfterHandle)) {
            / / processing Blocks
            __CFRunLoopDoBlocks(rl, rlm);
	    }
        // Check whether Source1 exists
        if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
            // If there is Source1, jump to handle_msg
            goto handle_msg;
        }
        // Notify Observers that they are about to enter hibernation
	    __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
    	__CFRunLoopSetSleeping(rl);
        // ⚠️ sleeps, waiting for a message to wake up the thread
        __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
    	__CFRunLoopUnsetSleeping(rl);
        // Notify Observers: they have just woken up from hibernation
	    __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);

handle_msg:
        if(Awakened by Timer) {/ / handle the Timer
            __CFRunLoopDoTimers(rl, rlm, mach_absolute_time())
        } else if(awakened by GCD) {/ / handle the GCD
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
        } else {  // Wake up by Source1
            / / processing Source1
            __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;  
        }

        / / processing Blocks
        __CFRunLoopDoBlocks(rl, rlm);
        
        /* Sets the return value */
        // Enter loop with an argument that returns after processing the event
        if (sourceHandledThisLoop && stopAfterHandle) {  
            retVal = kCFRunLoopRunHandledSource;
        // The timeout of the passed parameter marker is exceeded
        } else if (timeout_context->termTSR < mach_absolute_time()) {  
            retVal = kCFRunLoopRunTimedOut;
        // Forced to stop by an external caller
        } else if (__CFRunLoopIsStopped(rl)) {  
            __CFRunLoopUnsetStopped(rl);
            retVal = kCFRunLoopRunStopped;
        // Automatically stop
        } else if (rlm->_stopped) {  
            rlm->_stopped = false;
            retVal = kCFRunLoopRunStopped;
        / / mode without any Source0 / Source1 / Timer/Observer
        } else if(__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) { retVal = kCFRunLoopRunFinished; }}while (0 == retVal);

    return retVal;
}
Copy the code

From this function implementation, you can see that RunLoop does the following things:

  • __CFRunLoopDoObservers: noticeObserversWhat’s next
  • __CFRunLoopDoBlocks: processingBlocks
  • __CFRunLoopDoSources0: processingSources0
  • __CFRunLoopDoSources1: processingSources1
  • __CFRunLoopDoTimers: processingTimers
  • Dealing with GCD related:dispatch_async(dispatch_get_main_queue(), ^{ });
  • __CFRunLoopSetSleeping/__CFRunLoopUnsetSleeping: sleep waiting/sleep ending
  • __CFRunLoopServiceMachPort -> Mach-msg () : Transfers control of the current thread

Implementation of the __CFRunLoopServiceMachPort function: Implementation principle of RunLoop hibernation

In the __CFRunLoopRun function, the __CFRunLoopServiceMachPort function is called, which calls the mach_msg() function to transfer control of the current thread to the kernel/user state.

  • Hibernate threads to avoid resource hogging when there are no messages to process. callmach_msg()Switch from user mode to kernel mode and wait for messages.
  • Wake up the thread immediately when there is a message to be processedmach_msg()Return to user mode to process messages.

This is how RunLoop sleep works, and how RunLoop and simple do… While loop differences:

  • RunLoopWhen hibernating, the current thread does nothing and the CPU does not allocate resources.
  • simpledo... whileLoop: The current thread is not resting, it is occupying CPU resources.
static Boolean __CFRunLoopServiceMachPort(mach_port_name_t port, mach_msg_header_t **buffer, size_t buffer_size, mach_port_t *livePort, mach_msg_timeout_t timeout, voucher_mach_msg_state_t *voucherState, voucher_t *voucherCopy) {
    Boolean originalBuffer = true;
    kern_return_t ret = KERN_SUCCESS;
    for (;;) {		/* In that sleep of death what nightmares may come ... * /
        mach_msg_header_t *msg = (mach_msg_header_t *)*buffer;
        msg->msgh_bits = 0;
        msg->msgh_local_port = port;
        msg->msgh_remote_port = MACH_PORT_NULL;
        msg->msgh_size = buffer_size;
        msg->msgh_id = 0;
        if (TIMEOUT_INFINITY == timeout) { CFRUNLOOP_SLEEP(a); }else { CFRUNLOOP_POLL(a); }/ / ⚠ ️ ⚠ ️ ⚠ ️
        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