Internal logic of RunLoop
Create runloop
static CFRunLoopRef __CFRunLoopCreate(pthread_t t) { CFRunLoopRef loop = NULL; CFRunLoopModeRef rlm; uint32_t size = sizeof(struct __CFRunLoop) - sizeof(CFRuntimeBase); loop = (CFRunLoopRef)_CFRuntimeCreateInstance(kCFAllocatorSystemDefault, __kCFRunLoopTypeID, size, NULL); if (NULL == loop) { return NULL; } (void)__CFRunLoopPushPerRunData(loop); __CFRunLoopLockInit(&loop->_lock); loop->_wakeUpPort = __CFPortAllocate(); if (CFPORT_NULL == loop->_wakeUpPort) HALT; __CFRunLoopSetIgnoreWakeUps(loop); loop->_commonModes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks); CFSetAddValue(loop->_commonModes, kCFRunLoopDefaultMode); loop->_commonModeItems = NULL; loop->_currentMode = NULL; loop->_modes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks); loop->_blocks_head = NULL; loop->_blocks_tail = NULL; loop->_counterpart = NULL; loop->_pthread = t; #if DEPLOYMENT_TARGET_WINDOWS loop->_winthread = GetCurrentThreadId(); #else loop->_winthread = 0; #endif rlm = __CFRunLoopFindMode(loop, kCFRunLoopDefaultMode, true); if (NULL != rlm) __CFRunLoopModeUnlock(rlm); return loop; }
From the above code, it can be seen that runloop corresponds to thread one by one, and only kcfrnloop defaultmode is available in the runloop creation mode.
Get runloop
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) { if (pthread_equal(t, kNilPthreadT)) { t = pthread_main_thread_np(); } __CFSpinLock(&loopsLock); if (!__CFRunLoops) { __CFSpinUnlock(&loopsLock); CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks); CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np()); CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop); if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) { CFRelease(dict); } CFRelease(mainLoop); __CFSpinLock(&loopsLock); } CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t)); __CFSpinUnlock(&loopsLock); if (!loop) { CFRunLoopRef newLoop = __CFRunLoopCreate(t); __CFSpinLock(&loopsLock); loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t)); if (!loop) { CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop); loop = newLoop; } // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it __CFSpinUnlock(&loopsLock); CFRelease(newLoop); } if (pthread_equal(t, pthread_self())) { _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL); if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) { _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop); } } return loop; }
- runloop is the existence of several global dictionaries
- Get runloop from CFRunloops with thread as key
- Only when the runloop is obtained can the runloop be created
runloop run
void CFRunLoopRun(void) { /* DOES CALLOUT */ int32_t result; do { // Runloop mode timeout corresponding to the current thread returnAfterSourceHandled result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false); CHECK_FOR_FORK(); } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result); } SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */ CHECK_FOR_FORK(); return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled); }
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */ CHECK_FOR_FORK(); if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;// Released or not __CFRunLoopLock(rl); //Lock up CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false); //modeName -> currentMode if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) { //There is no source0 source1 timer in the mode correspondence Boolean did = false; if (currentMode) __CFRunLoopModeUnlock(currentMode); __CFRunLoopUnlock(rl); return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished; //End } volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl); //Set the information before running 0start 1 sleeping 2stoped CFRunLoopModeRef previousMode = rl->_currentMode; rl->_currentMode = currentMode;// Switch mode int32_t result = kCFRunLoopRunFinished; if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry); // Notify kcfrnloop entry result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode); //Function if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit); //Notify kcfrnloopexit __CFRunLoopModeUnlock(currentMode); __CFRunLoopPopPerRunData(rl, previousPerRun); rl->_currentMode = previousMode; __CFRunLoopUnlock(rl); return result; }
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) { uint64_t startTSR = mach_absolute_time(); if (__CFRunLoopIsStopped(rl)) {// runloop stope __CFRunLoopUnsetStopped(rl); return kCFRunLoopRunStopped; } else if (rlm->_stopped) { //runloop mode stop rlm->_stopped = false; return kCFRunLoopRunStopped; } mach_port_name_t dispatchPort = MACH_PORT_NULL; //port //libdispatchQSafe is false in the main thread Boolean libdispatchQSafe = pthread_main_np() && ((HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL == previousMode) || (!HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && 0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ))); //The main thread's mach port and the main thread's communication port if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name)) dispatchPort = _dispatch_get_main_queue_port_4CF(); #if USE_DISPATCH_SOURCE_FOR_TIMERS //Distribute queue ports to mode mach_port_name_t modeQueuePort = MACH_PORT_NULL; if (rlm->_queue) { modeQueuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue); if (!modeQueuePort) { CRASH("Unable to get port for run loop mode queue (%d)", -1); } } #endif // By setting the timeout dispatch_source_t timeout_timer = NULL; struct __timeout_context *timeout_context = (struct __timeout_context *)malloc(sizeof(*timeout_context)); if (seconds <= 0.0) { // instant timeout seconds = 0.0; timeout_context->termTSR = 0ULL; } else if (seconds <= TIMER_INTERVAL_LIMIT) { //TIMER_INTERVAL_LIMIT 504911232.0 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, DISPATCH_QUEUE_OVERCOMMIT); timeout_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); dispatch_retain(timeout_timer); timeout_context->ds = timeout_timer; timeout_context->rl = (CFRunLoopRef)CFRetain(rl); timeout_context->termTSR = startTSR + __CFTimeIntervalToTSR(seconds); dispatch_set_context(timeout_timer, timeout_context); // source gets ownership of context dispatch_source_set_event_handler_f(timeout_timer, __CFRunLoopTimeout); dispatch_source_set_cancel_handler_f(timeout_timer, __CFRunLoopTimeoutCancel); uint64_t ns_at = (uint64_t)((__CFTSRToTimeInterval(startTSR) + seconds) * 1000000000ULL); dispatch_source_set_timer(timeout_timer, dispatch_time(1, ns_at), DISPATCH_TIME_FOREVER, 1000ULL); dispatch_resume(timeout_timer); } else { // infinite timeout seconds = 9999999999.0; timeout_context->termTSR = UINT64_MAX; } //Start cycle Boolean didDispatchPortLastTime = true; int32_t retVal = 0; do { uint8_t msg_buffer[3 * 1024]; #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI mach_msg_header_t *msg = NULL; mach_port_t livePort = MACH_PORT_NULL; #elif DEPLOYMENT_TARGET_WINDOWS HANDLE livePort = NULL; Boolean windowsMessageReceived = false; #endif __CFPortSet waitSet = rlm->_portSet; __CFRunLoopUnsetIgnoreWakeUps(rl); //Notify observers to start processing timer event kcfrnloopbeforetimers if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers); //Notify observers to start processing the source event kcfrnloopbeforesources if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources); //Handle the corresponding block under the current mode of runloop __CFRunLoopDoBlocks(rl, rlm); //Process source0 (not port based) Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle); //Handle the corresponding block under the current mode of runloop if (sourceHandledThisLoop) { __CFRunLoopDoBlocks(rl, rlm); } Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR); //If there is an event on the port, skipping to handle "MSG for the first execution will not enter the judgment, because didDispatchPortLastTime is true, which contains the event source1 of the port if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) { #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI msg = (mach_msg_header_t *)msg_buffer; if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0)) { goto handle_msg; } #elif DEPLOYMENT_TARGET_WINDOWS if (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) { goto handle_msg; } #endif } didDispatchPortLastTime = false; //Notify observers that kcfrnloop before waiting is about to enter sleep state if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting); __CFRunLoopSetSleeping(rl); // do not do any user callouts after this point (after notifying of sleeping) // Must push the local-to-this-activation ports in on every loop // iteration, as this mode could be run re-entrantly and we don't // want these ports to get serviced. __CFPortSetInsert(dispatchPort, waitSet); __CFRunLoopModeUnlock(rlm); __CFRunLoopUnlock(rl); #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI #if USE_DISPATCH_SOURCE_FOR_TIMERS //The cycle starts to read the port information continuously. If the port has wake-up information, wake up the current runLoop do { if (kCFUseCollectableAllocator) { objc_clear_stack(0); memset(msg_buffer, 0, sizeof(msg_buffer)); } msg = (mach_msg_header_t *)msg_buffer; __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY); if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) { // Drain the internal queue. If one of the callout blocks sets the timerFired flag, break out and service 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; } else { if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg); } } else { // Go ahead and leave the inner loop. break; } } while (1); #else if (kCFUseCollectableAllocator) { objc_clear_stack(0); memset(msg_buffer, 0, sizeof(msg_buffer)); } msg = (mach_msg_header_t *)msg_buffer; __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY); #endif #elif DEPLOYMENT_TARGET_WINDOWS // Here, use the app-supplied message queue mask. They will set this if they are interested in having this run loop receive windows messages. __CFRunLoopWaitForMultipleObjects(waitSet, NULL, poll ? 0 : TIMEOUT_INFINITY, rlm->_msgQMask, &livePort, &windowsMessageReceived); #endif __CFRunLoopLock(rl); __CFRunLoopModeLock(rlm); // Must remove the local-to-this-activation ports in on every loop // iteration, as this mode could be run re-entrantly and we don't // want these ports to get serviced. Also, we don't want them left // in there if this function returns. __CFPortSetRemove(dispatchPort, waitSet); __CFRunLoopSetIgnoreWakeUps(rl); // user callouts now OK again __CFRunLoopUnsetSleeping(rl); //Notify observer that kcfrnloop after waiting is about to wake up if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting); //Handle event source1 event for port handle_msg:; //Ignore wake-up call __CFRunLoopSetIgnoreWakeUps(rl); #if DEPLOYMENT_TARGET_WINDOWS if (windowsMessageReceived) { // These Win32 APIs cause a callout, so make sure we're unlocked first and relocked after __CFRunLoopModeUnlock(rlm); __CFRunLoopUnlock(rl); if (rlm->_msgPump) { rlm->_msgPump(); } else { MSG msg; if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE | PM_NOYIELD)) { TranslateMessage(&msg); DispatchMessage(&msg); } } __CFRunLoopLock(rl); __CFRunLoopModeLock(rlm); sourceHandledThisLoop = true; // To prevent starvation of sources other than the message queue, we check again to see if any other sources need to be serviced // Use 0 for the mask so windows messages are ignored this time. Also use 0 for the timeout, because we're just checking to see if the things are signalled right now -- we will wait on them again later. // NOTE: Ignore the dispatch source (it's not in the wait set anymore) and also don't run the observers here since we are polling. __CFRunLoopSetSleeping(rl); __CFRunLoopModeUnlock(rlm); __CFRunLoopUnlock(rl); __CFRunLoopWaitForMultipleObjects(waitSet, NULL, 0, 0, &livePort, NULL); __CFRunLoopLock(rl); __CFRunLoopModeLock(rlm); __CFRunLoopUnsetSleeping(rl); // If we have a new live port then it will be handled below as normal } #endif if (MACH_PORT_NULL == livePort) { CFRUNLOOP_WAKEUP_FOR_NOTHING(); // // handle nothing } else if (livePort == rl->_wakeUpPort) { CFRUNLOOP_WAKEUP_FOR_WAKEUP(); //awaken // do nothing on Mac OS #if DEPLOYMENT_TARGET_WINDOWS // Always reset the wake up port, or risk spinning forever ResetEvent(rl->_wakeUpPort); #endif } #if USE_DISPATCH_SOURCE_FOR_TIMERS else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) { CFRUNLOOP_WAKEUP_FOR_TIMER(); // timer callback if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) { // Re-arm the next timer, because we apparently fired early __CFArmNextTimerInMode(rlm, rl); } } #endif #if USE_MK_TIMER_TOO else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) { //Handling timer wakeup CFRUNLOOP_WAKEUP_FOR_TIMER(); // On Windows, we have observed an issue where the timer port is set before the time which we requested it to be set. For example, we set the fire time to be TSR 167646765860, but it is actually observed firing at TSR 167646764145, which is 1715 ticks early. The result is that, when __CFRunLoopDoTimers checks to see if any of the run loop timers should be firing, it appears to be 'too early' for the next timer, and no timers are handled. // In this case, the timer port has been automatically reset (since it was returned from MsgWaitForMultipleObjectsEx), and if we do not re-arm it, then no timers will ever be serviced again unless something adjusts the timer list (e.g. adding or removing timers). The fix for the issue is to reset the timer here if CFRunLoopDoTimers did not handle a timer itself. 9308754 if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) { // Re-arm the next timer __CFArmNextTimerInMode(rlm, rl); } } #endif else if (livePort == dispatchPort) { //Main thread callback CFRUNLOOP_WAKEUP_FOR_DISPATCH(); __CFRunLoopModeUnlock(rlm); __CFRunLoopUnlock(rl); _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL); #if DEPLOYMENT_TARGET_WINDOWS void *msg = 0; #endif //If there is a block from dispatch to main queue, execute block. __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg); _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL); __CFRunLoopLock(rl); __CFRunLoopModeLock(rlm); sourceHandledThisLoop = true; didDispatchPortLastTime = true; } else { // If a source 1 (port based) issues an event, handle the event CFRUNLOOP_WAKEUP_FOR_SOURCE(); // Despite the name, this works for 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); } #elif DEPLOYMENT_TARGET_WINDOWS sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls) || sourceHandledThisLoop; #endif } } #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg); #endif // Execute the block added to the Loop __CFRunLoopDoBlocks(rl, rlm); // When entering the loop, the parameter says to return after handling the event. if (sourceHandledThisLoop && stopAfterHandle) { retVal = kCFRunLoopRunHandledSource; // Timeout exceeded for incoming parameter token }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; //mode stop } else if (rlm->_stopped) { rlm->_stopped = false; retVal = kCFRunLoopRunStopped; ////No source/timer/observer } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) { retVal = kCFRunLoopRunFinished; } } while (0 == retVal); //Release timer if (timeout_timer) { dispatch_source_cancel(timeout_timer); dispatch_release(timeout_timer); } else { free(timeout_context); } return retVal; }
The runloop code is organized as follows:
///Start with DefaultMode void CFRunLoopRun(void) { CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false); } ///Starts with the specified Mode, allowing the RunLoop timeout to be set int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) { return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled); } ///Implementation of RunLoop int CFRunLoopRunSpecific(runloop, modeName, seconds, stopAfterHandle) { ///First, find the corresponding mode according to the modeName CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false); ///If there is no source/timer/observer in mode, return directly. if (__CFRunLoopModeIsEmpty(currentMode)) return; ///1. Notify observers that runloop is about to enter the loop. __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry); ///Internal function, enter loop __CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled) { Boolean sourceHandledThisLoop = NO; int retVal = 0; do { ///2. Notify observers that runloop is about to trigger Timer callback. __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers); ///3. Notify observers that runloop is about to trigger the Source0 (non port) callback. __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources); ///Execute the added block __CFRunLoopDoBlocks(runloop, currentMode); ///4. RunLoop triggers the Source0 (non port) callback. sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle); ///Execute the added block __CFRunLoopDoBlocks(runloop, currentMode); ///5. If Source1 (based on port) is in the ready state, process this Source1 directly and jump to process the message. if (__Source0DidDispatchPortLastTime) { Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg) if (hasMsg) goto handle_msg; } ///Notify observers that the thread of runloop is about to enter sleep. if (!sourceHandledThisLoop) { __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting); } ///7. Call the mach? Msg to wait for the message of the mach? Port to be accepted. The thread will go to sleep until it is awakened by one of the following events. ///• an event based on a port Source. ///• it's time for a Timer ///• the timeout time of RunLoop itself is up ///• wake up manually by what other callers __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) { mach_msg(msg, MACH_RCV_MSG, port); // thread wait for receive msg } ///8. Notify observers that the thread of runloop has just been woken up. __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting); ///Receive message, process message. handle_msg: ///9.1 if a Timer reaches the time, the Timer's callback will be triggered. if (msg_is_timer) { __CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time()) } ///9.2 if there is a block from dispatch to main queue, execute block. else if (msg_is_dispatch) { __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg); } ///9.3 if a source 1 (port based) sends an event, handle the event else { CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort); sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg); if (sourceHandledThisLoop) { mach_msg(reply, MACH_SEND_MSG, reply); } } ///Execute the block added to the Loop __CFRunLoopDoBlocks(runloop, currentMode); if (sourceHandledThisLoop && stopAfterHandle) { ///When entering the loop, the parameter says to return after handling the event. retVal = kCFRunLoopRunHandledSource; } else if (timeout) { ///Timeout exceeded for incoming parameter token retVal = kCFRunLoopRunTimedOut; } else if (__CFRunLoopIsStopped(runloop)) { ///Forced to stop by an external caller retVal = kCFRunLoopRunStopped; } else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) { ///No source/timer/observer retVal = kCFRunLoopRunFinished; } ///If there is no timeout, there is no time in mode, and the loop has not been stopped, then continue to loop. } while (retVal == 0); } ///10. Notify observers that runloop is about to exit. __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit); }
As you can see, in fact, RunLoop is such a function, with a do while loop inside. When you call CFRunLoopRun(), the thread stays in the loop until it times out or is manually stopped.
Parameters and methods to be noted:
dispatchPort: the port of the main thread. The purpose is to communicate with the main thread
modeQueuePort: port assigned by current runloop current mode
__Timeout ﹐ context: in the context of timeout ﹐ timeout ﹐ context - > how long does termtsr timeout
livePort: the currently active port (send message source1 to the thread through port)
__CFRunLoopDoBlocks(rl, rlm)
struct _block_item { struct _block_item *_next; CFTypeRef _mode; // CFString or CFSet void (^_block)(void); }; static Boolean __CFRunLoopDoBlocks(CFRunLoopRef rl, CFRunLoopModeRef rlm) { // Call with rl and rlm locked if (!rl->_blocks_head) return false; if (!rlm || !rlm->_name) return false; Boolean did = false; struct _block_item *head = rl->_blocks_head; struct _block_item *tail = rl->_blocks_tail; rl->_blocks_head = NULL; rl->_blocks_tail = NULL; CFSetRef commonModes = rl->_commonModes; CFStringRef curMode = rlm->_name; __CFRunLoopModeUnlock(rlm); __CFRunLoopUnlock(rl); struct _block_item *prev = NULL; struct _block_item *item = head; // Loop block event while (item) { struct _block_item *curr = item; item = item->_next; Boolean doit = false; //Next, determine whether the mode of the block is the same as the current mode if (CFStringGetTypeID() == CFGetTypeID(curr->_mode)) { doit = CFEqual(curr->_mode, curMode) || (CFEqual(curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(commonModes, curMode)); } else { doit = CFSetContainsValue((CFSetRef)curr->_mode, curMode) || (CFSetContainsValue((CFSetRef)curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(commonModes, curMode)); } if (!doit) prev = curr; if (doit) { if (prev) prev->_next = item; if (curr == head) head = item; if (curr == tail) tail = prev; void (^block)(void) = curr->_block; CFRelease(curr->_mode); free(curr); if (doit) { //block call __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block); did = true; } Block_release(block); // do this before relocking to prevent deadlocks where some yahoo wants to run the run loop reentrantly from their dealloc } } __CFRunLoopLock(rl); __CFRunLoopModeLock(rlm); if (head) { tail->_next = rl->_blocks_head; rl->_blocks_head = head; if (!rl->_blocks_tail) rl->_blocks_tail = tail; } return did; }
Pass
dispatch_async(queue, block);
If the added block is stored in the linked list and the block is executed, it needs to judge whether the current mode is the same as the block mode.
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);
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) { 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(); } else { CFRUNLOOP_POLL(); } ret = mach_msg(msg, MACH_RCV_MSG|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); 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; }
Operate on msg and liveport
The value of * * buffer msg corresponding to msg can be brought back
If there is a corresponding msg * liveport = msg - > msgh? Local? Port in the port, otherwise * liveport = nach? Port? Null;
Process sequence:
reference material: