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: notice
Observers
What’s next - __CFRunLoopDoBlocks: processing
Blocks
- __CFRunLoopDoSources0: processing
Sources0
- __CFRunLoopDoSources1: processing
Sources1
- __CFRunLoopDoTimers: processing
Timers
- 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. call
mach_msg()
Switch from user mode to kernel mode and wait for messages. - Wake up the thread immediately when there is a message to be processed
mach_msg()
Return to user mode to process messages.
This is how RunLoop sleep works, and how RunLoop and simple do… While loop differences:
RunLoop
When hibernating, the current thread does nothing and the CPU does not allocate resources.- simple
do... while
Loop: 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