In the previous chapters, you gained a basic understanding of RunLoop, as well as the underlying objects. How does RunLoop work? ★ Core Foundation ★ cfrounloop.c

__CFRunLoopRun      >>>CFRunLoopRunSpecific           >>>CFRunLoopRun

★ Source note read download address

For the corresponding links above, see the end of the article – reference.

First, the operation logic

The operation logic diagram on the left is from the official documentation.

As you can see, in RunLoop, input events are received from two different sources: input source and timer source.

1. Sources are classified by synchronous and asynchronous

The figure above depicts two sources: an input source that receives asynchronously, and a timing source that receives synchronously.

1.1 Input sources

Input sources transmit asynchronous events, usually from other threads or programs, depending on whether they are from the kernel or not:

  • Port-based Sources, an event Based on PortThe kernel automatically emits signals. For example,CFSocketRef, is basically not used at the application layer.
  • Custom Input Sources, non-port event-based, manually created Source, must be sent manually from other threads.
  • Cocoa Perform Selector Sources, provided by CocoaperformSelectorSerial methods are also a source of events. As with port-based sources, executeselectorRequests are serialized on the target thread, alleviating many of the synchronization problems that can be caused by allowing multiple methods on a thread. Unlike port-based sources, when a selector executes, it automatically executesRun LoopRemove the inside.

1.2 the Timer sources

The timing source delivers synchronous events that occur at a specific time or at repeated time intervals.

Timers can produce time-based notifications, but they are not a real-time mechanism. Like the input source, timers are dependent on the particular mode of your Run Loop. If the timer is in a mode that is not currently monitored by the Run Loop, the timer will not start until the Run Loop is in the corresponding mode.

It mainly consists of two parts:

  • NSTimer
  • performSelector:withObject:afterDelay:

2. Sources are classified by object

In the previous article, we categorized RunLoop receive events by object.

2.1 Source1

Corresponds to port-based Sources, that is, port-based, which communicates with other threads through the kernel.

Often used to receive and distribute system events, most screen interaction events are received by Source1, packaged as events, distributed, and finally processed by Source0.

Therefore, it includes:

  • Communication between threads based on Port;
  • System event capture;

2.2 Source0

A non-port event. In the application, the final handling of the touch event and perforSelector:onThread are wrapped as objects of this type, and the developer assigns a callback function to handle the event manually.

Note that perforSelector:onThread has delay (delay function, timing function, etc.).

  • perforSelector:onThreadnotdelayPhi is a function of phiSource0Events.
  • performSelector:withObject:afterDelayThere aredelay, belongs toTimersEvents.

Therefore, it includes:

  • Touch event handling
  • performSelector:onThread

2.3 Timers

Same as [Timer Sources](#Timer Sources).

Two, source details

Cfrunloop. c is the main source of RunLoop operation logic. The author makes a note of it and extracts RunloopCycle. c for easy viewing. See the code at the end of the reference – sample source code.

You can also skip this section and directly view the flow chart of RunLoop, which provides a clear and intuitive analysis of the running logic and detailed flow.

1. Entry function

void CFRunLoopRun(void) {
    int32_t result;
    do {
        // Call RunLoop to execute the function
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0 e10.false);
    } while(kCFRunLoopRunStopped ! = result && kCFRunLoopRunFinished ! = result); }Copy the code

2. RunLoop executes the function

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {
    // The RunLoop is releasing, and the return is complete
    if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
    __CFRunLoopLock(rl);
    // Retrieve the current running Mode according to modeName
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
    // If there is no source/timer/observer in mode, return it directly.
    int32_t result = kCFRunLoopRunFinished;
    
    if (currentMode->_observerMask & kCFRunLoopEntry )
        // 1. Notify Observers of RunLoop.
        __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
        // 2-- 11, the core code of RunLoop
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    
    if (currentMode->_observerMask & kCFRunLoopExit )
        // 12. Notify Observers: exit RunLoop
        __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
    
    rl->_currentMode = previousMode;
    return result;
}
Copy the code

3. RunLoop message handler function

static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    
    int32_t retVal = 0;
    do {
        if (rlm->_observerMask & kCFRunLoopBeforeTimers)
            // 2. Notify Observers: RunLoop is about to handle Timer callbacks.
            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        if (rlm->_observerMask & kCFRunLoopBeforeSources)
            // 3. Notify Observers: RunLoop is about to process Source
            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
        // 4. Process Blocks
        __CFRunLoopDoBlocks(rl, rlm);
        
        // 5. Handle the Source0 (non-port) callback (possibly again Blocks)
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        if (sourceHandledThisLoop) {
            / / processing Blocks
            __CFRunLoopDoBlocks(rl, rlm);
        }
        
        Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
        
        if(MACH_PORT_NULL ! = dispatchPort && ! didDispatchPortLastTime) {// 6. If there is a port based Source1 in ready state, process the Source1 directly and jump to processing the message.
            msg = (mach_msg_header_t *)msg_buffer;
            if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
                gotohandle_msg; }}if(! poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting))// 7. Notify Observers that threads of RunLoop are about to enter sleep.
            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        __CFRunLoopSetSleeping(rl);
        CFAbsoluteTime sleepStart = poll ? 0.0 : CFAbsoluteTimeGetCurrent();
        do {
            msg = (mach_msg_header_t *)msg_buffer;
            // 8. RunLoop starts sleeping: wait for message to wake up, call mach_msg and wait for message to receive mach_port.
            // a port-based Source event.
            // a Timer is running out
            // • RunLoop's own timeout is up
            • Manually awakened by what other caller
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
        } while (1);

        __CFRunLoopUnsetSleeping(rl);
        if(! poll && (rlm->_observerMask & kCFRunLoopAfterWaiting))// 9. Notify Observers: RunLoop about ending hibernation
            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
    handle_msg:;
        // The message is received and processed.
        __CFRunLoopSetIgnoreWakeUps(rl);
        
        if (MACH_PORT_NULL == livePort) {
            CFRUNLOOP_WAKEUP_FOR_NOTHING();
            // handle nothing
        } else if (livePort == rl->_wakeUpPort) {
            CFRUNLOOP_WAKEUP_FOR_WAKEUP();
        } else if(rlm->_timerPort ! = MACH_PORT_NULL && livePort == rlm->_timerPort) { CFRUNLOOP_WAKEUP_FOR_TIMER();If a Timer runs out of time, the Timer's callback is triggered.
            if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                __CFArmNextTimerInMode(rlm, rl);
            }
        } else if (livePort == dispatchPort) {
            CFRUNLOOP_WAKEUP_FOR_DISPATCH();
            _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6.NULL);
            // 9.2 Processing GCD Async To Main Queue: If there are blocks dispatched To main_queue, execute the block.
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
        } else {
            CFRUNLOOP_WAKEUP_FOR_SOURCE();
            voucher_t previousVoucher = _CFSetTSD(__CFTSDKeyMachMessageHasVoucher, (void *)voucherCopy, os_release);
            CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
            if (rls) {
                mach_msg_header_t *reply = NULL;
                // 9.3 Handling Source1: If a Source1 (port-based) emits an event, handle the 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); }}}// 10. Process Blocks
        __CFRunLoopDoBlocks(rl, rlm);
        
        // 11. Decide the process according to the previous processing results
        // 11.1 Exit RunLoop when the following occurs
        if (sourceHandledThisLoop && stopAfterHandle) {
            retVal = kCFRunLoopRunHandledSource;
        } else if (timeout_context->termTSR < mach_absolute_time()) {
            11.1.1 The timeout of the passed parameter marker is exceeded
            retVal = kCFRunLoopRunTimedOut;
        } else if (__CFRunLoopIsStopped(rl)) {
            11.1.2 The current RunLoop has been forced to stop by an external caller
            __CFRunLoopUnsetStopped(rl);
            retVal = kCFRunLoopRunStopped;
        } else if (rlm->_stopped) {
            11.1.3 The current operating mode has been stopped
            rlm->_stopped = false;
            retVal = kCFRunLoopRunStopped;
        } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
            // 11.1.4 Source /timer/ Observer is missing
            retVal = kCFRunLoopRunFinished;
        }
        voucher_mach_msg_revert(voucherState);
        os_release(voucherCopy);
        If the mode is not empty or stopped and the loop is not stopped, continue the loop.
    } while (0 == retVal);
    
    return retVal;
}
Copy the code

4. Message processing underlying functions

When a RunLoop does a callback, it usually does a long function call out. When you debug your code with a breakpoint, print the stack (BT) and see these functions on the call stack.

The following is a condensed version of each function. If you see the long function name in the call stack, look it up here to locate the specific call location:

{
    // 1. Notify Observers of RunLoop.
    AutoreleasePool: _objc_autoreleasePoolPush();
    __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopEntry);
 
    // 2. Notify Observers: RunLoop is about to handle Timer callbacks.
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeTimers);
   
    // 3. Notify Observers: RunLoop is about to process Source
 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeSources);
    // 4. Process Blocks
    __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);

    // 5. Handle the Source0 (non-port) callback (possibly again Blocks)
    __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(source0);
    __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);

    // 7. Notify Observers that threads of RunLoop are about to enter sleep.
    AutoreleasePool: _objc_autoreleasePoolPop(); _objc_autoreleasePoolPush();
 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeWaiting);

    // 8. RunLoop starts sleeping: wait for message to wake up, call mach_msg and wait for message to receive mach_port.
    mach_msg() -> mach_msg_trap();


    // 9. Notify Observers: RunLoop about ending hibernation
 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopAfterWaiting);

    If a Timer runs out of time, the Timer's callback is triggered.
    __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(timer);

    // 9.2 Processing GCD Async To Main Queue: If there are blocks dispatched To main_queue, execute the block.
    __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(dispatched_block);

    // 9.3 Handling Source1: If a Source1 (port-based) emits an event, handle the event
    __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(source1);
 
    // 10. Process Blocks
    __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
    
    // 12. Notify Observers: exit RunLoop
    AutoreleasePool: _objc_autoreleasePoolPop();
    __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopExit);
}
Copy the code

5. Implementation of dormancy

How does RunLoop implement true hibernation, as opposed to the following:

While (1);
while(1);
Copy the code

It is internally called by kernel-state.

Iii. Flow chart

Extract the above code logic as follows:

  • Yellow: notifies the Observer of phases;
  • Blue: logic for processing messages;
  • Green: branching judgment logic;

1. Execute logic

Flow chart of 2.

reference

link

  1. Objc source
  2. CF source
  3. Source note read
  4. Threading Programming Guide–RunLoop    Chinese translation
  5. Understand RunLoop in depth

The sample source code

  1. Source note read