RunLoop flow chart
The upload – images. Jianshu. IO/upload_imag…
What does RunLoop do from the entry function?
CFRunLoopRun: CFRunLoopRun: do… 2.CFRunLoopRunSpecific passes the current Loop, default Model, second value 1.0e10, BOOL returnAfterSourceHandled.
CFRunLoopRunSpecific (CFRunLoopGetCurrent (), kCFRunLoopDefaultMode, 1.0 e10,false);
Copy the code
3. Based on the modeName passed in, __CFRunLoopFindMode is used to get the current CFRunLoopModeRef currentMode. 4. If no event is found or registered in mode, the loop is stopped
if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
Boolean did = false;
if (currentMode) __CFRunLoopModeUnlock(currentMode);
__CFRunLoopUnlock(rl);
return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
}
Copy the code
- Take the last run
mode
, the currentrunloop
themode
Replace it with what you found in step 3mode
CFRunLoopModeRef previousMode = rl->_currentMode;
rl->_currentMode = currentMode;
Copy the code
6. Initialize result as kCFRunLoopRunFinished, notify Observers: RunLoop is about to enter loop, and __CFRunLoopRun is set to result. RunLoop is about to exit.
int32_t result = kCFRunLoopRunFinished;
if(currentMode->_observerMask & kCFRunLoopEntry ){ /// 1. Notifying Observers: RunLoop is about to enter loop. __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry); } result = __CFRunLoopRun(rl, currentMode, seconds,returnAfterSourceHandled, previousMode);
if(currentMode->_observerMask & kCFRunLoopExit ){ /// 10. Notify Observers: RunLoop is about to exit. __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit); }Copy the code
7. When the RunLoop exits, it performs a series of operations. The Unlock is currently in the mode obtained in step 3, sets the _currentMode of the current RunLoop to the previousMode that was saved, and returns result.
__CFRunLoopModeUnlock(currentMode);
__CFRunLoopPopPerRunData(rl, previousPerRun);
rl->_currentMode = previousMode;
__CFRunLoopUnlock(rl);
return result;
Copy the code
Core method__CFRunLoopRun
What did you do?
1. Inform the Observer that RunLoop is about to enter
if (currentMode->_observerMask & kCFRunLoopEntry ){
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
}
Copy the code
__CFRunLoopRun executes what is in the dotted box above, described in the order shown. 2. Notify the Observer that the Timer will be processed
if (rlm->_observerMask & kCFRunLoopBeforeTimers) {
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
}
Copy the code
3. Inform the Observer that source0 will be processed
if (rlm->_observerMask & kCFRunLoopBeforeSources) {
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
}
Copy the code
4. Handle source0
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
Copy the code
5. If there is source1, go to step 9. Source1 contains a mach_port and a callback (function pointer),
msg = (mach_msg_header_t *)msg_buffer;
if(__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0)) {// If a message is received, Goto step 9 and start processing MSG goto handle_msg; }Copy the code
6. Notify the Observer that runloop is going to sleep. Poll is false if there is no sources0 event processing and no timeout; Poll is true if sources0 event processing or timeout occurs. If poll is false and RLM ->_observerMask is kCFRunLoopBeforeWaiting, __CFRunLoopDoObservers is executed.
if(! poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);Copy the code
7. Set runloop to sleep __CFRunLoopSetSleeping(rl); After executing an inner loop do… While, the thread enters the loop and goes to sleep until it receives a new message and then breaks out of the loop (being woken up) and continues executing the Run loop.
Here’s a twist: Why is the runloop set to sleep, but the thread?
Runloops correspond to threads one by one. A runloop corresponds to a core thread, which is core because runloops can be nested, but there can only be one core, and their relationship is stored in a global dictionary. Runloop is used to manage threads. When a thread’s Runloop is enabled, the thread will go to sleep after executing a task. When a task is available, the thread will be awakened to perform the task.
The paragraph above is taken from the bigwig
An internal loop is executed if USE_DISPATCH_SOURCE_FOR_TIMERS is waiting for awakening
do{// Read messages from the buffer MSG = (mach_MSG_header_t *)msg_buffer; / / 7. Call __CFRunLoopServiceMachPort, listeningwaitIf there is no port message, mach_MSg_trap will be entered and RunLoop will sleep until a port message is received or the __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY); MSG ->msgh_local_port, then livePort! =modeQueuePort, so it will walkelse
if(modeQueuePort ! = MACH_PORT_NULL && livePort == modeQueuePort) { // Drain the internal queue. If one of the callout blocks sets the timerFired flag,breakOut and service the timer. // Exhaust the internal queue if there is a callback block set to timerFired, breaks out of the loop and serves the timer.while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));
if (rlm->_timerFired) {
// Leave livePort as the queue port, and service timers below
rlm->_timerFired = false;
break; // Exit the loop to perform a wake up operation}else{// If MSG is not empty and does not match msg_buffer, release the MSG as if nothing happenedif (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
}
} else{// Go ahead and leave the inner loopbreak; // Exit the loop to perform a wake up operation}}while (1);
Copy the code
One of the things that really bothers me here is what all these various ports are and how livePorts are equal to Modequeueports.
if(modeQueuePort ! = MACH_PORT_NULL && livePort == modeQueuePort)Copy the code
How does this judgment work? I searched globally for modeQueuePort and found only one place with an assignment that required USE_DISPATCH_SOURCE_FOR_TIMERS.
modeQueuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue);
Copy the code
When the kernel successfully receives the message, the livePort is assigned the value MSG -> MSgh_LOCAL_port
if(MACH_MSG_SUCCESS == ret) {// Message received successfully *livePort = MSG? msg->msgh_local_port : MACH_PORT_NULL;return true;
}
Copy the code
So, as long as the message is successfully received, the livePort is not equal to the modeQueuePort, and the loop is broken.
Further down the line, however, you see a string of code with the same rules, but with a method that wakes up RunLoop.
#if USE_DISPATCH_SOURCE_FOR_TIMERS
if(modeQueuePort ! = MACH_PORT_NULL && livePort == modeQueuePort) { CFRUNLOOP_WAKEUP_FOR_TIMER();if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
// Re-arm the next timer, because we apparently fired early
__CFArmNextTimerInMode(rlm, rl);
}
}
Copy the code
Oh, my god, what shit? It’s hard to read it out, but it’s a conflict here, and I doubt life. Scared me into looking for information. Finally, I found an explanation of this passage in the big guy’s blog.
Instead of waking up after runLoop sleep, it jumps in at step 5 of runLoop10 to handle timer events
At last the inner circle was more or less clear
8. Inform the Observer that the thread has just been awakened. The hibernation is over.
if(! poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) { __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting); }Copy the code
9: Process the messages received on wake up, then go back to step 2.
handle_msg:;
__CFRunLoopSetIgnoreWakeUps(rl);
if (MACH_PORT_NULL == livePort) {
CFRUNLOOP_WAKEUP_FOR_NOTHING();
// handle nothing
} else if (livePort == rl->_wakeUpPort) {
CFRUNLOOP_WAKEUP_FOR_WAKEUP();
// do nothing on Mac OS
}
#if USE_DISPATCH_SOURCE_FOR_TIMERS
else if(modeQueuePort ! = MACH_PORT_NULL && livePort == modeQueuePort) {// This is not waking up from runLoop sleep but jumping from step 5 of runLoop10, Is to process the timer event / / www.jianshu.com/p/ec629063390f CFRUNLOOP_WAKEUP_FOR_TIMER (); 9.1 If a Timer runs out of time, trigger the Timer's callback.if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
// Re-arm the next timer, because we apparently fired early
__CFArmNextTimerInMode(rlm, rl);
}
}
#endif9.2 If there are blocks sent to main_queue, execute the block commandelse if (livePort == dispatchPort) {
CFRUNLOOP_WAKEUP_FOR_DISPATCH();
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
sourceHandledThisLoop = true;
didDispatchPortLastTime = true;
} else{/// 9.3 If a Source1 (port based) event is emitted, process the event CFRUNLOOP_WAKEUP_FOR_SOURCE(); // Despite the name, this worksfor windows handles as well
CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
if (rls) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
mach_msg_header_t *reply = NULL;
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); } // return to __CFRunLoopDoBlocks(rl, RLM);Copy the code
10. Inform the Observer that RunLoop is about to exit. Assign a value to retVal if retVal! = 0 will exit the loop and retVal will be returned.
if (sourceHandledThisLoop &&stopAfterHandle) {/// return when the loop is finished. retVal = kCFRunLoopRunHandledSource; }else if(timeout_context->termTSR < mach_absolute_time()) {retVal = kCFRunLoopRunTimedOut; }else if(__CFRunLoopIsStopped(rl)) {// external callers forcibly stopped __CFRunLoopUnsetStopped(rl); retVal = kCFRunLoopRunStopped; }else if(RLM ->_stopped) {RLM ->_stopped =false;
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
/// source/timer/ Observer lost none retVal = kCFRunLoopRunFinished; } // If there is no timeout, mode is not empty, and loop is not stopped, continue loop.Copy the code
Return to the outer method __CFRunLoopRun call and assign a value to result.
if(currentMode->_observerMask & kCFRunLoopExit ){ /// 10. Notify Observers: RunLoop is about to exit. __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit); }return result;
Copy the code
CFRunLoopRun ends when result == kCFRunLoopRunStopped && result == kCFRunLoopRunFinished
Complete annotated RunLoop source code