What is the RunLoop

Ibireme’s article is highly recommended for an in-depth understanding of RunLoop

Runloop source code address

As for Runloop, I’ve always been confused about what it does, although I’ve known for a long time that its implementation is essentially a loop.

The execution logic of the code is top-down. If there is no Runloop, the program will exit after the code is executed, which corresponds to the actual scenario where the APP will exit immediately after opening.

Int main(int argc, const char * argv[]) {@autoreleasepool {NSLog(@" program executing... ); } return 0; } // log... Program ended with exit code: 0Copy the code

For example, when the code completes, the main function returns and the program exits.

Why doesn’t runloop-related code seem to have been written and the application continues to run consistently?

int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); }}Copy the code

This is because the application automatically helps us in UIApplicationMain… In did this thing.

Here’s a look at Runloop’s simplified pseudo-code, from a video shared by Sunnyxx:

function loop() {
    do{something to do = I sleep nothing don't look for me ();if(move bricks) {move bricks (); }else if(eat) {eat (); }}while(alive)}Copy the code

This pseudo-code is still a bit abstract, but it is important to understand that threads and runloops have a one-to-one correspondence, and sleep is defined as thread sleep. ], that is, when the application does not trigger any event, it will stop at the sleeping line of code, so as to save CPU computing resources, improve the program performance, until an event wakes up the application. For example, the brick moving event, the eating event. After processing, it will go to sleep until the next wake up, repeated loop, so that the program can handle all kinds of events and can run stably.

In fact, touch events, screen UI refreshes, delayed callbacks, and so on are implemented by Runloop.

The structure of the Runloop

Runloop: Runloop

struct __CFRunLoop {
    pthread_t _pthread;
    CFMutableSetRef _commonModes;     
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode;
    CFMutableSetRef _modes;
    // ...
};
Copy the code

This includes a thread member variable, _pthread, so you can see that Runloop is really thread dependent. You can also see that Runloop has a number of Model member variables. Let’s look at the Model structure:

struct __CFRunLoopMode {
    CFStringRef _name;
    CFMutableSetRef _sources0;
    CFMutableSetRef _sources1;
    CFMutableArrayRef _observers;
    CFMutableArrayRef _timers;
	// ...
};
Copy the code

Regardless of what these things do, at least we can now understand them as follows:

A Runloop contains several models, and each Mode contains several sources/timers/observers.

The Runloop Model

Model represents the running Mode of Runloop. Runloop can only specify one Model at a time as _currentMode. If you need to switch Mode, you can only exit the current Loop and select another Mode to enter. The main thread Runloop has two preset modes, and these are the two models exposed by the system:

  • KCFRunLoopDefaultMode: The normal state of the APP, in which the main thread is normally run, marked Common.

  • UITrackingRunLoopMode: App tracks the state of touch ScrollView sliding to ensure that the interface sliding is not affected by other modes. It has been marked as Common.

Notice that the Runloop structure has a _commonModes. This is because a Mode can mark itself as Common (by adding its ModeName to RunLoop’s commonModes), and any Model marked as Common can handle events. It can be understood as a disguised realization of multiple models running at the same time. The system also provides a string that operates on the Common flag ->kCFRunLoopCommonModes. If we want to handle events in both modes, we can use this string.

The Model of the Item

Source/Timer/Observer is referred to as mode item, different Model of Source0 / Source1 / Timer/Observer was separated, each other, If there is no any Source0 / Source1 / Timer Mode/Observer, RunLoop immediately exit.

Source

Source is where the event is generated, and its corresponding class is CFRunLoopSourceRef. Source has two versions: Source0 and Source1.

  • Source0Contains only one callback (function pointer), which does not actively fire events.
  • Source1Contains amach_portAnd a callback (function pointer) that is used to send messages to each other through the kernel and other threads. This kind ofSourceActive arousalRunLoopThe thread. Such as screen touch, screen lock and shaking.

Timer

The Timer class is CFRunLoopTimerRef, which is essentially NSTimer. When it is added to the RunLoop, the RunLoop registers the corresponding point in time, and when it does, the RunLoop wakes up to execute the callback.

Observer

The Observer corresponds to the CFRunLoopObserverRef class, and when the state of the RunLoop changes, the Observer receives the change through a callback. The following time points can be observed:

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry         = (1UL << 0), // About to enter Loop
    kCFRunLoopBeforeTimers  = (1UL << 1), // Timer is about to be processed
    kCFRunLoopBeforeSources = (1UL << 2), // Source is about to be processed
    kCFRunLoopBeforeWaiting = (1UL << 5), // About to go to sleep
    kCFRunLoopAfterWaiting  = (1UL << 6), // Just woke up from hibernation
    kCFRunLoopExit          = (1UL << 7), // About to exit Loop
};
Copy the code

The internal logic of Runloop

Opening the source code for the Runloop at the beginning is confusing, but as mentioned earlier, the touch events on the screen are handled by Runloop. To view the program’s function call stack, make a breakpoint:

As you can see from the figure, Runloop starts at 11, so search the source code for CFRunLoopRunSpecific functions. This is to explore the internal logic, but not the other details.

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    Getcurrentmode according to modeName
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
	// Set Runloop's Model
    CFRunLoopModeRef previousMode = rl->_currentMode;
    rl->_currentMode = currentMode;
	// Notify Observers that they are about to enter RunLoop
	__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    / / into the runloop
	result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
	// Notify Observers: RunLoop is about to exit
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
    return result;
}
Copy the code

Then enter __CFRunLoopRun(…) Function to view the internal condensed after the main logic source:

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 Sources1 exists
        if(MACH_PORT_NULL ! = dispatchPort && ! didDispatchPortLastTime) {// jump to handle_msg to handle Sources1soso
            goto handle_msg;
        }
        // Notify Observers that they are about to hibernate
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        // Start sleep
        __CFRunLoopSetSleeping(rl);

        // Wait for a message to wake up the current thread
        __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);
        // End the hibernation
        __CFRunLoopUnsetSleeping(rl);
        // Notify Observers: end hibernation
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);

    / / processing
    handle_msg:;
        // The timer wakes up
        if(rlm->_timerPort ! = MACH_PORT_NULL && livePort == rlm->_timerPort) {/ / handle the timer
            __CFRunLoopDoTimers(rl, rlm, mach_absolute_time())
        }
        // Wake up by GCD
        else if (livePort == dispatchPort) {
            / / handle the GCD
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
        // Wake up by source1
        } else {
            __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply);
        }

        / / processing Blocks
        __CFRunLoopDoBlocks(rl, rlm);
        
        // Set the return value
        if (sourceHandledThisLoop && stopAfterHandle) {
            retVal = kCFRunLoopRunHandledSource;
        } else if (timeout_context->termTSR < mach_absolute_time()) {
            retVal = kCFRunLoopRunTimedOut;
        } else if (__CFRunLoopIsStopped(rl)) {
            __CFRunLoopUnsetStopped(rl);
            retVal = kCFRunLoopRunStopped;
        } else if (rlm->_stopped) {
            rlm->_stopped = false;
            retVal = kCFRunLoopRunStopped;
        } else if(__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) { retVal = kCFRunLoopRunFinished; }}while (0 == retVal);
    return retVal;
}
Copy the code

You can see that there is indeed a loop inside a Runloop, and that there are Mach port, Timer, and Dispatch ways to wake up a Runloop

. Sources0 is a touch event, but it is not sources1. Sources1 is a touch event that wakes up Runloop. Because Sources0 cannot wake up runloop and then process click events in the sources0 callback.

Mach port in RunLoop

Due to my limited knowledge, I can only understand that Mach port is a system that can control hardware and receive hardware feedback, and then convert operations from hardware into mature UIEvent events and so on.

conclusion

This article mainly explains what Runloop is, of course, Runloop knowledge is not only this article. Runloop is also used for thread retention (AFNetworking 2.x), how the Timer does not stop when sliding, and the implementation of automatic release pools.