What is the Runloop

A Runloop, as the name suggests, runs a loop. First of all, it is related to the running process of the root program, and secondly, it is a circular effect. But if you explain it that way, I’m afraid no one will understand.

To understand Runloop, you need to understand some of the concepts associated with it and how it works on its own.

Programs without Runloop

Let’s create a new command line project with Xcode. The code in the main.m file looks like this

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");
    }
    return 0;
}
Copy the code

Run the program, the program is executing the code NSLog(@”Hello, World!” ); After that, return 0; Roll out the program, which is a linear execution flow, from top to bottom, easy to understand.

When we meet Runloop

Let’s create a new iOS project, and you’ll see the main.m file look something like this

#import <UIKit/UIKit.h>
#import "AppDelegate.h"

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

We know that once we run the app, we’re going to go into the app, and then the app doesn’t quit, it just keeps running. Have you ever wondered why the same main function doesn’t exit? Yeah, that’s Runloop.

In main.m, there is no Runloop. In main.m, there is Runloop in UIApplicationMain().

It still feels a little stiff, so let’s write it in pseudocodeUIApplicationMain()Internal situationTo put it bluntly,RunloopIt’s really just ado-whileLoop, every time you loop around, you make a judgmentretValTo decide whether to end the loop and continue executing the code outside the loop.

A brief description of Runloop’s functions

  • Runloopthedo-whileIn essence, it is to keep the program running and not exit (just think of the app on the mobile phone if it is opened and directly quit, there may be no mobile phone in the world).
  • The whole point of keeping your app running is to handle events in your app (touch events, timer events), because those events aren’t programmed when you’re writing your app, and you never know when you’re going to click a button, right? So the linear way we started with the program, you can’t handle this scenario. When Runloop is added, it can handle events that have already been received by the program in a single loop, and the program will continue to accept new events as they are processed, so that the next loop can continue to process new events.
  • ifRunloopAfter a loop, the program suddenly has no more events to collect for it to handle, and it goes to sleep, essentially shutting down the programRunloopthedo-whileOn a piece of code inside the loop (Note that the program stops, not exits), if after a while the procedure isRunloopReceived the new event, itsdo-whileThe loop is reactivated by the system to continue running. The advantage of this mechanism is that whenRunloopDuring hibernation, the CPU does not need to wait in front of it and can instead allocate time and energy to other transactions, which improves CPU efficiency.

If you think of the CPU as a mother, the Runloop is the newborn baby. When the baby is awake, the mother has to watch it all the time and has no time to do anything else. When the baby is asleep, the mother has time to do something else. Without the Runloop sleep mechanism, this baby would have never slept at all

Understand Runloop objects in depth

The iOS Runloop API

  • Foundation: NSRunLoop
  • Core Foundation: CFRunLoopRef

NSRunLoop and CFRunLoopRef both stand for Runloop objects. NSRunLoop is based on a layer of OC wrapping of CFRunLoopRef, which is open source

Get the Runloop object

  • Foundation

[NSRunloop currentRunLoop]; Get the current thread’s RunLoop object [NSRunLoop mainRunLoop]; Get the Runloop object for the main thread

  • Core Foundation

CFRunLoopGetCurrent(); Get the RunLoop object CFRunLoopGetMain() for the current thread; Get the Runloop object for the main thread

Runloop with thread

Why do runloops have to be about threads? As we know, every line of code in the program is executed in the thread (main thread/child thread), and the above four types of code that obtain the Runloop object are no exception. They must run in the thread. A Runloop is designed to keep the program from exiting. More specifically, a Runloop is designed to keep a thread from exiting. As long as there are threads remaining, the program will not exit, because threads are the basic unit of program execution and scheduling.

The thread has a one-to-one relationship with a Runloop. A newly created thread has no Runloop object. The Runloop object is created when we first get a Runloop in the thread through the API above and binds the Runloop object to the thread store through a global dictionary. Form a one-to-one relationship.

The Runloop is destroyed at the end of the thread, the main thread’s Runloop is automatically acquired (created), and the child thread does not have Runloop enabled by default (until you retrieve it in that thread). Once created, the RunLoop object is stored in a global Dictionary, with the thread as the key and the RunLoop object as the value.

Cfrunloop.c: CFRunLoopGetCurrent() : CFRunLoopGetCurrent() : CFRunLoopGetCurrent() : CFRunLoopGetCurrent

CFRunLoopRef CFRunLoopGetCurrent(void) {
    CHECK_FOR_FORK();
    CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
    if (rl) return rl;
    return _CFRunLoopGet0(pthread_self());
}
Copy the code

In CFRunLoopGetCurrent(), _CFRunLoopGet0 is used to get the Runloop object

Runloop object underlying structure

The definition of Runloop can be found in cfrunloop. c

* * * * * * * * * * * * * * 🥝 🥝 🥝 🥝 __CFRunLoop 🥝 🥝 🥝 🥝 * * * * * * * * * * *typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef;
struct __CFRunLoop {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;          /* locked for accessing mode list */
    __CFPort _wakeUpPort;// used for CFRunLoopWakeUp 
    Boolean _unused;
    volatile _per_run_data *_perRunData;  // reset for runs of the run loop
    uint32_t _winthread;

 // my goal is 100. My goal is 100. // my goal is 100
    pthread_t _pthread;
    CFMutableSetRef _commonModes;
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode;
    CFMutableSetRef _modes;
 // my goal is 100. My goal is 100. // my goal is 100

    struct _block_item* _blocks_head;
    struct _block_item* _blocks_tail;CFAbsoluteTime _runTime; CFAbsoluteTime _sleepTime; CFTypeRef _counterpart; }; * * * * * * * * * * * * * * 🥝 🥝 🥝 🥝 __CFRunLoopMode 🥝 🥝 🥝 🥝 * * * * * * * * * * *typedef struct __CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoopMode {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;  /* must have the run loop locked before locking this */
    Boolean _stopped;
    char _padding[3];

    // my goal is 100. My goal is 100. // my goal is 100
    CFStringRef _name;
    CFMutableSetRef _sources0;
    CFMutableSetRef _sources1;
    CFMutableArrayRef _observers;
    CFMutableArrayRef _timers;
    // my goal is 100. My goal is 100. // my goal is 100


    CFMutableDictionaryRef _portToV1SourceMap;
    __CFPortSet _portSet;
    CFIndex _observerMask;
#if USE_DISPATCH_SOURCE_FOR_TIMERS
    dispatch_source_t _timerSource;
    dispatch_queue_t _queue;
    Boolean _timerFired; // set to true by the source when a timer has fired
    Boolean _dispatchTimerArmed;
#endif
#if USE_MK_TIMER_TOO
    mach_port_t _timerPort;
    Boolean _mkTimerArmed;
#endif
#if DEPLOYMENT_TARGET_WINDOWS
    DWORD _msgQMask;
    void (*_msgPump)(void);
#endif
    uint64_t _timerSoftDeadline; /* TSR */
    uint64_t _timerHardDeadline; /* TSR */
};
Copy the code

We need to learn about the five classes associated with Runloop

  • CFRunLoopRef — This is the Runloop object
  • CFRunLoopModeRef— Its interior mainly consists of four containers, respectively for storagesource0,source1,observerAs well astimer
  • CFRunLoopSourceRef– divided intosource0andsource1

Source0: includes touch event processing, [performSelector: onThread:] source1: includes port-based communication between threads, and system event capturing

  • CFRunLoopTimerReftimerEvents, including the timer events we set,[performSelector: withObject: afterDelay:]
  • CFRunLoopObserverRef— Listener,RunloopWhen the status changes, the listener is notified of the function callback, and the UI interface is refreshed to listen for Runloop statusBeforeWaitingWhen.

The relationship among the above classes can be described in the following figure

As can be seen from the figure, a RunLoop object contains several Runloopmodes. The internal RunLoop contains these runloopmodes through a collection container _modes.

The core of RunLoopMode is an array containing source0, source1, Observer, and timer. The currentMode is a pointer to one of the runloopmodes of the RunLoop object. This represents the RunLoopMode that RunLoop is currently running, and by “running” means, RunLoop will currently execute only the events included in RunLoopMode to which _currentMode points (source0, source1, Observer, timer).

In addition, the RunLoop object has two other members, _commonModes and _commonModeItems, which are explained later.

The RunLoop object also contains a thread object, _pThread, that corresponds to the thread object.

source0

This includes touch event handling, [performSelector: onThread:], which can also be verified in code. So let’s first look at touch events, override methods in ViewController

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    NSLog(@" Tap screen");
}
Copy the code

Put in a breakpoint, and then run the program, tap the screen, and it will stop atSometimes we can’t do it in Xcodedebug navigatorSee the complete function call stack

And then you can go throughLLDBinstructionbtTo print out the complete function call stack information on the consoleYou can see that the system is going through aCFThe function of__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__To invoke theUIKitThe name of the function that handles events is clear,RunloopNow we’re dealing with onesource0. And by the same method, we can prove that[performSelector: onThread: ]It’s going to be onesource0.

- (void)viewDidLoad {
    [super viewDidLoad];
// Create a thread
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(runThread) object:nil];
    // Start the thread
    [thread start];
    // Add the Perform event to the thread
    [self performSelector:@selector(performEvent) onThread:thread withObject:nil waitUntilDone:YES];
// The following method also produces source0
// [self performSelector:@selector(performEvent) onThread:thread withObject:nil waitUntilDone:YES modes:@[NSDefaultRunLoopMode]];} - (void)runThread {
    // Make sure the thread is resident
    [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] run];
}

- (void)performEvent {
    NSLog(@perform Perform);
}
Copy the code

source1

It’s described abovesource1Includes system event capture and basedportCommunication between threads. What is theSystem Event Capture? And how to understandBased on theportCommunication between threads? In fact, when we tap our finger on the screen, the first thing that happens is a system event that passessource1To receive the capture, and then fromSpringboardProgram wrapped assource0It’s distributed to the application to process, so we’re receiving touch events inside the App, which issource0The relationship between one and the other should be clarified.

Port-based communication between threadsIt can be roughly understood by the following illustration

CFRunLoopTimerRef

Also, you can look at the NSTimer event and [performSelector: withObject:] in Xcode with the bt directive in LLDB. Function call stack for afterDelay] events, which are all hoisted by the __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ function. As you can see from the function name, they do belong to timer events (CFRunLoopTimerRef)

CFRunLoopObserverRef

We know thatobserverIt’s for listening in.RunloopThe state. It can also handle UI refresh, so how does all of our UI control code get executed? Here is the following Runloop stateThere are several kinds of them

/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),// Enter the runloop
    kCFRunLoopBeforeTimers = (1UL << 1),// The timer event is about to be processed
    kCFRunLoopBeforeSources = (1UL << 2),// The source event will be processed soon
    kCFRunLoopBeforeWaiting = (1UL << 5),// About to go to sleep (waiting for messages to wake up)
    kCFRunLoopAfterWaiting = (1UL << 6),// End of sleep (wake up by message)
    kCFRunLoopExit = (1UL << 7),// Exit the runloop
    kCFRunLoopAllActivities = 0x0FFFFFFFU// Set all of the above states
};
Copy the code

To see the state of Runloop change during debugging, you can add an Observer through the Runloop API as follows

/ / create the observer
    // Created by block
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, true.0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        // Observer callback processing
        switch (activity) {
            case kCFRunLoopEntry:
                NSLog(@"kCFRunLoopEntry");
                break;
            case kCFRunLoopBeforeTimers:
                NSLog(@"kCFRunLoopBeforeTimers");
                break;
            case kCFRunLoopBeforeSources:
                NSLog(@"kCFRunLoopBeforeSources");
                break;
            case kCFRunLoopBeforeWaiting:
                NSLog(@"kCFRunLoopBeforeWaiting");
                break;
            case kCFRunLoopAfterWaiting:
                NSLog(@"kCFRunLoopAfterWaiting");
                break;
            case kCFRunLoopExit:
                NSLog(@"kCFRunLoopExit");
                break;

            default:
                break; }})// Add an observer to the runloop
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
    / / release the observer
    CFRelease(observer);
Copy the code

After the program runs, you will see the following print repeatedly on the consoleAs you can see, the state of Runloop will be switchedobserverListen to.

_modes and _commonModes

You may wonder why there are so many runloopmodes inside a RunLoop, and why not put all the events in one Mode. Let’s use a practical example to illustrate this problem.

First, we’re in an iOS project, and we’re adding one to the interfaceUITextView

Then start a loop timer in the ViewController

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerEvent) userInfo:nil repeats:YES];
}

- (void)timerEvent {
    NSLog(@" Handle Timer events");
}

@end
Copy the code

After running the program, the console call is invoked every one secondtimerEventMethods to performHow does the system do that? Well, actually,[NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerEvent) userInfo:nil repeats:YES];Inside is one of the RunLoop objects inside the current thread (main thread) every secondModeaddtimerEvent and put it onThe timer containerInside,

It is then processed in a continuous loop of the RunLoop. To handle a timer event, call the OC method or block bound to the timer.

When you swipe that UITextView that we just added, you’ll notice that the timerEvent method in the console has stopped. Why? This question is often asked in iOS interviews, and I’m sure you know the answer. There are several runloopmodes in the RunLoop, and two of them are called RunloopModes

  • kCFRunLoopDefaultMode

The default Mode of the App, which is usually used to run the main thread

  • UITrakingRunLoopMode

Interface tracking Mode, as the name implies, App has the touch sliding event of Scrollview, which will be put into the event container of this Mode. Therefore, when the user operates the Scrollview on the interface through the screen, App will switch to this Mode and process the current sliding event.

Above we through scheduledTimerWithTimeInterval method increase the timer events, in fact is the system default on the main thread RunLoop kCFRunLoopDefaultMode, when we do not slide the screen, the main thread running in this Mode, the So we can handle the timer event that we added.

When we swipe the screen, the main thread is switched to the UITrakingRunLoopMode, so it can’t handle our timer event, because the timer event is not added to the Mode of the previous thread.

The nice thing about this division is that you can separate the priorities and not interfere with each other. Because Apple’s highest concept is user experience first, when users slide the interface, Apple believes that the best experience is to make users feel as little lag as possible. If time-consuming events and sliding events are processed in the same Mode at the same time, interface lag may occur. Having multiple modes enables events to be processed separately to ensure user experience. The name UITrakingRunLoopMode reminds developers not to add irrelevant, time-consuming operation events to the Mode, so as not to affect the user experience.

To solve the problem in the above example and allow the sliding interface to handle timer events at the same time, you need to use another method of NSTimer to add timers

- (void)viewDidLoad {
    [super viewDidLoad];
// Create a timer
    NSTimer *timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
        NSLog(2 "@" timer events);
    }];
// Add timer to the specified mode of RunLoop
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}
Copy the code

In this code, I’m adding the timer that I’m creating to NSRunLoopCommonModes, which is not actually a mode, it can be interpreted as a tag, The modes that are tagged with this tag are put into a container member within the RunLoop called _commonModes, which is a CFMutableSetRef. By default, NSRunLoopCommonModes kCFRunLoopDefaultMode + UITrakingRunLoopMode = NSRunLoopCommonModes So they are added to _commonModes. According to the code above, the timer will not be added to a specific Mode, but to the _commonModeItems container of the RunLoop. As long as the App is running in one of the modes included in _commonModeItems, the events in _commonModeItems are handled. Of course, don’t forget that the Mode that you run contains events that are handled by itself. The above is the method to solve the problem of timer failure and the underlying principle.

The Runloop process is described from source code

We discussed aboveRunloopThe internal loop is divided into several states during execution, and the order in which these states are switched, as well as the internal execution logic of the Runloop, is to be seen in source code. The source file for RunLoopCFRunLoop.cRunloop is a very complex, pure C implementation, which you might not be used to, and there are many functions in it, which is the entry function of Runloop. Actually, let’s prove it up hereThe touch event belongs to source0The answer can be found in the function call stackObviously, in the stack of function calls triggered by touch events, the CF framework originally passedCFRunLoopRunSpecificFunctions intoRunloopIs then called__CFRunLoopRunYou can tell from the name that this must be the entrance. So let’s look at these two functions, because they’re both complicated, just to make it easier to understandRunloopThe execution logic, the code is simplified, the core step code is retained, and marked as(1) ~ ⑫The main steps are shown below

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    
    / / 📢 📢 📢 📢 * * * * * * (1) 📢 📢 📢 📢 informed observer -- -- -- -- -- -- -- -- -- -- kCFRunLoopEntry
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);

    / / 🚗 🚗 🚗 🚗 🚗 🚗 🚗 runloop 🚗 started
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);

    / / 📢 📢 📢 📢 ⑫ * * * * * * 📢 📢 📢 📢 informed observer -- -- -- -- -- -- -- -- -- -- kCFRunLoopEntry
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
 
    return result;
}


static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {

    //⚠️⚠️⚠️ exits the do-while loop with the tag retVal
    int32_t retVal = 0;

    //♥️♥️♥️ Runloop is the core of such a do-while loop
    do {
        
      / / 📢 📢 📢 📢 * * * * * * (2) 📢 📢 📢 📢 informed observer -- -- -- -- -- kCFRunLoopBeforeTimers
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);


      / / 📢 📢 📢 📢 * * * * * * (3) 📢 📢 📢 📢 informed observer -- -- -- -- -- kCFRunLoopBeforeSources
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);


      / / ⚙ ️ ⚙ ️ ⚙ ️ ⚙ ️ * * * * * * (4) ⚙ ️ ⚙ ️ ⚙ ️ ⚙ ️ processing Blocks
	   __CFRunLoopDoBlocks(rl, rlm);


      / / ⚙ ️ ⚙ ️ ⚙ ️ ⚙ ️ * * * * * * (5) ⚙ ️ ⚙ ️ ⚙ ️ ⚙ ️ processing source0 -- -- -- -- -- -- --
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);

        if (sourceHandledThisLoop) {
            //⚙️⚙️⚙️⚙️ disk ⚙️ password Blocks if necessary
            __CFRunLoopDoBlocks(rl, rlm);
	}


      //♦️♦️♦️♦️***⑥***♦️♦️

        if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {

            //🎯🎯🎯🎯🎯 maximum if source1 exists, go to the label handle_msg
            goto handle_msg;
        }


      / / 📢 📢 📢 📢 * * * * * * 7 📢 📢 📢 📢 informed observer -- -- -- -- -- kCFRunLoopBeforeWaiting
	   __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);

      // Start sleep
	  __CFRunLoopSetSleeping(rl);

      // Wait for another message to wake up the current thread
      __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
            
      // Thread wake up
	  __CFRunLoopUnsetSleeping(rl);


     / / 📢 📢 📢 📢 end 📢 📢 📢 📢 informed observer -- -- -- -- -- kCFRunLoopAfterWaiting ended dormancy
      __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);

/ / 🎯 🎯 🎯
handle_msg://⚙️⚙️⚙️⚙️***⑨***⚙️⚙️


        //🥝🥝🥝🥝 port is woken up by timer
        if(rlm->_timerPort ! = MACH_PORT_NULL && livePort == rlm->_timerPort) {/ / 🔧 🔧 🔧 🔧 🔧 🔧 🔧 🔧 deal with the timer
            __CFRunLoopDoTimers(rl, rlm, mach_absolute_time())
        }
        //🥝🥝🥝🥝🥝 Is woken up by the GCD
        else if (livePort == dispatchPort) {
        / / 🔧 🔧 🔧 🔧 🔧 🔧 🔧 🔧 processing the GCD
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
            
        } 
        / / 🥝 🥝 🥝 🥝 🥝 source1
        else {
        / / 🔧 🔧 🔧 🔧 🔧 🔧 🔧 🔧 Source1 processing
            __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
		
	    }


      / / ⚙ ️ ⚙ ️ ⚙ ️ ⚙ ️ * * * * * * attending ⚙ ️ ⚙ ️ ⚙ ️ ⚙ ️ processing Blocks
	   __CFRunLoopDoBlocks(rl, rlm);
        
      //⚙️⚙️⚙️⚙️*** *⚙️⚙️⚙️⚙️ ⚙️*** ⚙️⚙️
	  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

The above execution process is summarized as follows

Here are the seven core operation units in RunLoop

  • 1.__CFRunLoopDoSource1: handles the source1 event, which is called internally

__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__

  • 2.__CFRunLoopDoSources0: handles the source0 event, which is called internally

__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__

  • 3.__CFRunLoopDoObservers: notifies the observer that an internal call was made

__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__

  • (4)__CFRunLoopDoTimers: Handles the timer event, which is internally called

__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__

  • 5.__CFRunLoopDoBlocks: process blocks, which is internally called

__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__

  • 6.__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__: Handles GCD asynchronous main thread tasks
  • All landowners__CFRunLoopServiceMachPortHibernate the thread and wait for the message to wake up

Note point one — GCD and RunLoop

GCD and RunLoop are two separate mechanisms, most of the time unrelated to each other. RunLoop has a core operation called __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__, which means RunLoop is serving the main thread queue. GCD says something to be handled by RunLoop. In fact, when we call asynchronously from the child thread back to the main thread to execute a task, GCD will dump the main thread task to RunLoop, which is finally passed internally to GCD for processing through __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__. This is the case in the following code

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
   NSLog(@" Tap screen");
   
   dispatch_async(dispatch_get_global_queue(0.0), ^ {NSLog(@" Child thread events");
       dispatch_async(dispatch_get_main_queue(), ^{
           NSLog(@back to main thread);
           
       });
   });
}
Copy the code

The function call is as follows

Note the details of thread sleep

__CFRunLoopServiceMachPort is the __CFRunLoopServiceMachPort that enables RunLoop to save CPU resources, improve performance, and sleep when there is something to do. The purpose of this function is to block the thread so that it actually stops executing and waits to be woken up. So how does this block work?

Instead of continuing with the following code, you might want to use an infinite loop while(1){; }, so that none of the rest of the code executes, but this is not true sleep, just the program goes to while(1){; } While (1){} while(1){; } the compiled instructions are being executed repeatedly by the CPU, so they still need CPU resources.

The __CFRunLoopServiceMachPort function is a true hibernation that causes the current thread to stop executing assembly instructions without consuming CPU resources. It actually calls mach_msg() function inside, which is an API provided by the system kernel. It enables us, as application layer developers, to call kernel-level functions. Thread sleep is a kernel-level operation.

The internal structure of the RunLoop and how it works are sorted out