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-while
Loop, every time you loop around, you make a judgmentretVal
To decide whether to end the loop and continue executing the code outside the loop.
A brief description of Runloop’s functions
- Runloopthe
do-while
In 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 programRunloopthe
do-while
On 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-while
The 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 storage
source0
,source1
,observer
As well astimer
- CFRunLoopSourceRef– divided into
source0
andsource1
Source0: includes touch event processing, [performSelector: onThread:] source1: includes port-based communication between threads, and system event capturing
- CFRunLoopTimerRef–
timer
Events, 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 status
BeforeWaiting
When.
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 navigator
See the complete function call stack
And then you can go throughLLDBinstructionbt
To 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 basedport
Communication between threads. What is theSystem Event Capture? And how to understandBased on theport
Communication between threads? In fact, when we tap our finger on the screen, the first thing that happens is a system event that passessource1
To receive the capture, and then fromSpringboardProgram wrapped assource0
It’s distributed to the application to process, so we’re receiving touch events inside the App, which issource0
The 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 thatobserver
It’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 switchedobserver
Listen 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 secondtimerEvent
Methods 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 secondModeaddtimer
Event 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.c
Runloop 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 source0
The answer can be found in the function call stackObviously, in the stack of function calls triggered by touch events, the CF framework originally passedCFRunLoopRunSpecific
Functions intoRunloopIs then called__CFRunLoopRun
You 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
__CFRunLoopServiceMachPort
Hibernate 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