What is the runLoop

A runloop, as the name suggests, gives the impression of running in a loop. The core code that runloop runs on is a stateful DO… The while loop. Each loop is the equivalent of a lap, and the thread processes events generated in the current lap. So why do threads have runloops? In fact, our APP can be understood as event-driven (including iOS and Android apps). Our touch screens, network callbacks, etc., are all events, i.e., events. These events will be distributed to our APP, and the APP will be distributed to the corresponding thread when it receives the event. Normally, if a thread does not have a runloop, then a thread can only execute one task at a time, and the thread will exit when it is finished. In order for APP threads to be able to handle events or wait for events (such as asynchronous events), they need to be kept alive, that is, they can’t exit early, and this is where runloop comes in handy. As we’ve already said, runloop is essentially a stateful DO… While loop, so as long as there is no timeout or deliberate exit, runLoop will always execute do… While, so the thread is guaranteed not to exit. It is not necessary to assign a runloop to the thread. If we want the thread to continue processing events, we need to bind a Runloop to the thread. That is, runloop ensures that the thread can always handle events. So the runloop can be interpreted as:

Keeps the program running and handling events. These events include but are not limited to user operations, timer tasks, kernel messages and sequential processing of various events. Because runloops are stateful, they save CPU resources by deciding what events to process and when. In general, events do not occur indefinitely, so there is no need for threads to run indefinitely. Runloop can go to sleep when there is no event processing, avoiding endless do… While running empty laps. We have to repeat the old cliche: one thread for every RunLoop, the main thread’s RunLoop starts by default, and the child threads’ RunLoop starts on demand (by calling the run method). The runloop is the thread’s event manager, or event manager for the thread, which manages the events to be processed by the thread in order, deciding which events to submit to the main thread for processing and when.

The ubiquitous runLoop

Here, I break the point in touchesBegan:withEvent, and then look at the function call stack. The red boxes are runloop-related functions. As you can see, the entire call stack can be divided into four parts. From bottom to top:

1. Load dyLD dynamic library and dyLD load other dynamic libraries

2. Run the main function

3. Runloop handles source, in this case source0

4.UIKit processes the touch event after receiving source0

In the figure above, there is a very long function in the call stack — CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION. In fact, almost all functions of all threads are called from the following six functions:

__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ // Callback to observer, __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ // Processes blocks added to runloop __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ // Handles events distributed to the main thread __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ // Handles timer callbacks __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ // Handles the source0 callback __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ // Handles the source1 callbackCopy the code

The six function names are long only for self-explanatory purposes. By looking at their names, we can see that all of these functions are calling_out, that is, all call-back functions. A runloop callback notifies the upper layer of what state the runloop is in or what event it is processing. The specific role of each function is explained in detail below, so there is no need to dwell on it here.

RunLoop structure

The runLoop structure is shown below:

It can be seen from the above figure that:

  • One Thread corresponds to one runloop
  • The NSRunLoop of the Cocoa layer encapsulates the CFRunLoop of the CF layer
  • One runloop corresponds to multiple Runloopmodes
  • A runloop can execute only one runLoopMode at a time, and a runloop can and must run only in one particular mode at a time. To switch the runLoopMode, you need to stop and exit the current runLoopMode to enter the new runLoopMode.
  • The advantage of executing one mode at a time is that the underlying design is relatively simple, preventing the different modes from being coupled together and affecting each other’s code
  • Another benefit is that different code can be executed in different modes, avoiding the interaction of upper-level business code.
  • Multiple modes and mode switching are key to smooth iOS app sliding.
  • Different code in the main thread specifies running in different modes to improve app fluency.
  • Each runLoopMode contains several Runloopsources, several RunloOptimers, and several RunloopObservers.

RunLoop structure definition

// RunLoop structure definition

struct __CFRunLoop { pthread_mutex_t _lock; __CFPort _wakeUpPort; // Manually wake up the port of runloop. CFRunLoopWakeUp will send a message to _wakeUpPort pthread_t _pthread; // The corresponding thread CFMutableSetRef _commonModes; CFMutableSetRef _commonModeItems; // Store sources, timers, observers CFRunLoopModeRef _currentMode for all commonmodes; // Current modeName CFMutableSetRef _modes; CFRunLoopModeRef struct _block_item *_blocks_head; // A pointer to the head of the list that holds all the blocks that need to be executed by runloop. The external adds a block node to the list by calling CFRunLoopPerformBlock. Runloop iterates over the list at CFRunLoopDoBlock, executing block struct _block_item *_blocks_tail one by one; // the end pointer is used to reduce the time complexity of adding blocks};Copy the code

The main APIS provided by RunLoop include functions related to obtaining RunLoop, functions related to running RunLoop, and functions related to operating source\timer\observer

// get RunLoop CF_EXPORT CFRunLoopRef CFRunLoopGetCurrent(void); CF_EXPORT CFRunLoopRef CFRunLoopGetMain(void); // Add commonMode CF_EXPORT void CFRunLoopAddCommonMode(CFRunLoopRef rl, CFStringRef mode); CF_EXPORT void CFRunLoopRun(void); CF_EXPORT SInt32 CFRunLoopRunInMode(CFStringRef mode, CFTimeInterval seconds, Boolean returnAfterSourceHandled); CF_EXPORT Boolean CFRunLoopIsWaiting(CFRunLoopRef rl); CF_EXPORT void CFRunLoopWakeUp(CFRunLoopRef rl); CF_EXPORT void CFRunLoopStop(CFRunLoopRef rl); CF_EXPORT Boolean CFRunLoopContainsSource(CFRunLoopRef RL, CFRunLoopSourceRef source, CFStringRef mode); Boolean CFRunLoopContainsSource(CFRunLoopRef RL, CFRunLoopSourceRef source, CFStringRef mode); CF_EXPORT void CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef mode); CF_EXPORT void CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef mode); CF_EXPORT CFRunLoopSourceRef CFRunLoopSourceCreate(CFAllocatorRef allocator, CFIndex order, CFRunLoopSourceContext *context); CF_EXPORT void CFRunLoopSourceSignal(CFRunLoopSourceRef source); / / the observer related operations CF_EXPORT Boolean CFRunLoopContainsObserver (CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef mode); CF_EXPORT void CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef mode); CF_EXPORT void CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef mode); CF_EXPORT CFRunLoopObserverRef CFRunLoopObserverCreate(CFAllocatorRef allocator, CFOptionFlags activities, Boolean repeats, CFIndex order, CFRunLoopObserverCallBack callout, CFRunLoopObserverContext *context); CF_EXPORT CFRunLoopObserverRef CFRunLoopObserverCreateWithHandler(CFAllocatorRef allocator, CFOptionFlags activities, Boolean repeats, CFIndex order, void (^block) (CFRunLoopObserverRef observer, CFRunLoopActivity activity)) CF_AVAILABLE(10_7, 5_0); CF_EXPORT Boolean CFRunLoopContainsTimer(CFRunLoopRef RL, CFRunLoopTimerRef timer, CFStringRef mode); // Boolean CFRunLoopContainsTimer(CFRunLoopRef RL, CFRunLoopTimerRef timer, CFStringRef mode); CF_EXPORT void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode); CF_EXPORT void CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode); CF_EXPORT CFRunLoopTimerRef CFRunLoopTimerCreate(CFAllocatorRef allocator, CFAbsoluteTime fireDate, CFTimeInterval interval, CFOptionFlags flags, CFIndex order, CFRunLoopTimerCallBack callout, CFRunLoopTimerContext *context); CF_EXPORT CFRunLoopTimerRef CFRunLoopTimerCreateWithHandler(CFAllocatorRef allocator, CFAbsoluteTime fireDate, CFTimeInterval interval, CFOptionFlags flags, CFIndex order, void (^block) (CFRunLoopTimerRef timer)) CF_AVAILABLE(10_7, 5_0); /* Making runloop execute a block * essentially inserts a block into a list of block objects maintained by runloop, and executes each of the blocks in the list that are specified to run in the current mode. */ CF_EXPORT void CFRunLoopPerformBlock(CFRunLoopRef rl, CFTypeRef mode, void (^block)(void)) CF_AVAILABLE(10_6, 4_0);Copy the code

RunLoop relationships with threads

Here are the functions that get the main thread runloop and the child thread runloop. As you can see, both functions call _CFRunLoopGet0() internally, and the CFRunLoopGet0() input parameter is thread.

Also, the runloop that fetches the child thread passes in the current thread fetched by the pthread_self() function. CFRunLoopGetCurrent must be called inside the thread to get the current thread’s RunLoop. This means that the RunLoop of the child thread must be retrieved within the child thread. The main thread does not have this limitation, but there is no need to get the main thread runloop in the child thread.

Get the main thread runloop

CFRunLoopRef CFRunLoopGetMain(void) { CHECK_FOR_FORK(); static CFRunLoopRef __main = NULL; // No retain needed runloop if (! __main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed return __main; }Copy the code

Gets the runloop of the child thread

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

_CFRunLoopGet0(

// Should only be called by Foundation // t==0 is a synonym for "main thread" that always Works CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) { kNilPthreadT)) { t = pthread_main_thread_np(); } __CFLock(&loopsLock); if (! __CFRunLoops) { __CFUnlock(&loopsLock); / / create a thread used for mapping and runloop dictionary CFMutableDictionaryRef dict = CFDictionaryCreateMutable (kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks); Runloop CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np()); // save main runloop, main_thread as key, Main runloop is value CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop); if (! OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) { CFRelease(dict); } CFRelease(mainLoop); __CFLock(&loopsLock); } // CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t)); __CFUnlock(&loopsLock); // Create a runloop and cache it in the dictionary if (! loop) { CFRunLoopRef newLoop = __CFRunLoopCreate(t); __CFLock(&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 __CFUnlock(&loopsLock); CFRelease(newLoop); } if (pthread_equal(t, pthread_self())) { _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL); If (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {// Register a callback when the thread is destroyed, Destroy the corresponding RunLoop _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop); } } return loop; }Copy the code

As you can see from reading the code above:

  • Runloops correspond to threads one by one in a key-value manner stored in a global dictionary
  • The RunLoop for the main thread is created when the global dictionary is initialized
  • The RunLoop of the child thread is created when it is first fetched and never created if it is not fetched
  • The RunLoop is destroyed when the thread is destroyed

CFRunLoopMode

Mode acts as a bridge between runloop and Source timer observer. The main Runloop registers five modes at startup. They are as follows:

  1. KCFRunLoopDefaultMode: The default Mode of the App, in which the main thread is normally run.
  2. UITrackingRunLoopMode: interface tracing Mode, used by ScrollView to track touch sliding, ensuring that the interface sliding is not affected by other modes.
  3. UIInitializationRunLoopMode: in the first to enter the first Mode when just start the App, start after the completion of the will no longer be used.
  4. GSEventReceiveRunLoopMode: accept system internal Mode of events, usually in less than.
  5. Kcfrunloopcommonmode: This is a placeholder Mode and has no effect.

You can see more of Apple’s internal modes here, but those modes are harder to come by in development.

A RunLoop contains several modes, each of which can contain several sources/timers/observers. Only one Mode can be specified each time the main function of RunLoop is called. This Mode is CurrentMode of RunLoop. If you need to switch Mode, you can only exit Loop and specify another Mode to enter. The main purpose of this is to separate the Source/Timer/Observer groups from each other.

There is a special mode in mode called commonMode. CommonMode is not a real mode, but a number of ordinary modes marked as commonMode. So commonMode is essentially a collection that stores the name of the mode, which is a string, of all modenames marked common. When we add Source \timer\ Observer to commonMode, we essentially iterate over all modes in the set, adding item in turn to each mode marked common.

When the program starts, the main thread runloop has two preset modes: kCFRunLoopDefaultMode and UITrackingRunLoopMode. By default it is defaultMode, and when you scroll through the scrollView list runloop will exit defaultMode and go to trackingMode. So, sometimes the timer event we add to defaultMode will not be executed while sliding the list. However, both kCFRunLoopDefaultMode and UITrackingRunLoopMode have been added to the commonMode set of runloop. That is, the two preset modes of the main thread are already marked as commonMode by default. We want our timer callback to be executed while sliding through the list by adding the Timer item to commonMode.

Mode structure definition

The structure definition of CFRunLoopMode is as follows. From the definition of RLM, it is easy to see the following information:

  • Defines a structure pointer CFRunLoopModeRef to __CFRunLoopMode *, and from then on only CFRunLoopModeRef is used throughout. Is equivalent to:

- typedef NSString * StringRef; StringRef name = @"VV ";

  • The runLoopMode structure contains a name that identifies the runLoopMode
  • RunLoopMode contains two collections, sources0 and source1.
  • The runLoopMode contains two arrays containing an Observer and a timer
typedef struct __CFRunLoopMode *CFRunLoopModeRef; struct __CFRunLoopMode { CFStringRef _name; // mode name Boolean _stopped; CFMutableSetRef _sources0; CFMutableSetRef _sources1; // sources1 event set CFMutableArrayRef _observers; CFMutableArrayRef _timers; CFMutableDictionaryRef _portToV1SourceMap; / / in the dictionary. Key is mach_port_t, value is CFRunLoopSourceRef __CFPortSet _portSet; // A collection of ports. Save all the ports that you want to listen for, _wakeUpPort, _timerPort are stored in this array CFIndex _observerMask; // Set _observerMask to observer _activities (CFRunLoopActivity state)};Copy the code

In Core Foundation, Apple only opens the following three apis for Mode operations (there are functions in Cocoa that do the same thing, not listed here) :

CF_EXPORT CFStringRef CFRunLoopCopyCurrentMode(CFRunLoopRef rl); // return all modes of the current RunLoop CF_EXPORT CFArrayRef CFRunLoopCopyAllModes(CFRunLoopRef rl); // Add a mode to the current RunLoop common modes CF_EXPORT void CFRunLoopAddCommonMode(CFRunLoopRef rl, CFStringRef mode);Copy the code

There is no way to create a CFRunLoopMode object directly, but we can call CFRunLoopAddCommonMode and pass in a string to add a commonMode to the RunLoop. The string is the name of the Mode. The Mode object should be created inside the RunLoop at this point.

Add commonMode source code

void CFRunLoopAddCommonMode(CFRunLoopRef rl, CFStringRef modeName) { if (! CFSetContainsValue(rl->_commonModes, modeName)) { CFSetRef set = rl->_commonModeItems ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModeItems) : NULL; CFSetAddValue(rl->_commonModes, modeName); if (NULL ! CFTypeRef context[2] = {rl, modeName}; CFTypeRef context[2] = {rl, modeName}; // Synchronize all sources/observers/timers in the commonModeItems array to the newly added mode (CFRunLoopModeRef instance) // iterate over each element in the set as __CFRunLoopAddItemsToCommonMode first parameter, the context as the second parameter, Call __CFRunLoopAddItemsToCommonMode CFSetApplyFunction (set, (__CFRunLoopAddItemsToCommonMode), (void *) context). CFRelease(set); }} else {}} / / add item to the item set of mode (array) in the static void __CFRunLoopAddItemsToCommonMode (const void * value, void *ctx) { CFTypeRef item = (CFTypeRef)value; CFRunLoopRef rl = (CFRunLoopRef)(((CFTypeRef *)ctx)[0]); CFStringRef modeName = (CFStringRef)(((CFTypeRef *)ctx)[1]); If (CFGetTypeID(item) == CFRunLoopSourceGetTypeID()) {if (CFGetTypeID(item) == cfrunloopSource ()) { (CFRunLoopSourceRef)item, modeName); } else if (CFGetTypeID (item) = = CFRunLoopObserverGetTypeID ()) {/ / item is the observer is added to the observer "array" CFRunLoopAddObserver(rl, (CFRunLoopObserverRef)item, modeName); } else if (CFGetTypeID(item) == CFRunLoopTimerGetTypeID()) {CFRunLoopAddTimer(rl, (CFRunLoopTimerRef)item, modeName); }}Copy the code

CFRunLoopSource

As shown in the figure below, the __CFRunLoopSource structure definition includes two versions of source, version0 and version1. Version0 and version1 correspond to source0 and source1 respectively.

typedef struct __CFRunLoopSource * CFRunLoopSourceRef; H is defined to give developers the ability to create and use a source.

One source corresponds to multiple runloops. The collection structure CFMutableBagRef is used to hold runloops rather than arrays or sets. The main reason is that bag is unordered and allows repetition. For more information see: developer.apple.com/documentati…

typedef struct __CFRunLoopSource * CFRunLoopSourceRef; Struct __CFRunLoopSource {CFIndex _order; struct __CFRunLoopSource {CFIndex _order; // The order of the souce (immutable) CFMutableBagRef _runLoops; Runloop union {CFRunLoopSourceContext version0; CFRunLoopSourceContext version0; // The struct of source0 (immutable) CFRunLoopSourceContext1 version1; // Struct of source1 (immutable)} _context; };Copy the code

The runloop corresponding to source is a set, indicating that source can be added to multiple runloops.

Source0 differs from Source1

Source0: Source0 is an internal event of App, managed by App itself, such as UIEvent and CFSocket are Source0. Source0 does not actively trigger events. When a source0 event is ready to be processed, CFRunLoopSourceSignal(source) is called and the source is marked as pending. CFRunLoopWakeUp(runloop) is then manually called to wake up the runloop to handle the event. The framework already takes care of these calls for us, such as the callback for the network request, the callback for the sliding touch, we don’t need to handle them ourselves.

Source1: Managed by RunLoop and kernel, driven by Mach port such as CFMachPort and CFMessagePort. Source1 contains a mach_port and a callback (function pointer) that is used to send messages to each other through the kernel and other threads. This Source actively wakes up the RunLoop thread.

Add the Source code

Adds a source to the source0 or source1 set of the corresponding mode. If the mode specified for source is commonMode, add source to runloop’s commonModeItems collection.

void CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef rls, CFStringRef modeName) {// Export commonMode of runloop (if modeName is commonMode) if (modeName == kcfrunLoopCommonMode) {CFSetRef  set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL; // initialize commonModeItems if (NULL == rl->_commonModeItems) {rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks); CFSetAddValue(rl->_commonModeItems, RLS); if (NULL ! // Create an array of length 2 storing runloop and runloopSource CFTypeRef context[2] = {rl, RLS}; // Set is the commonMode set, CFSetApplyFunction traverses the set, Add runloopSource to all labeled commonMode mode - > source0 CFSetApplyFunction in (or source1) (the set (__CFRunLoopAddItemToCommonModes), (void *)context); }} else {// modeName is not commonMode // Get runloop mode CFRunLoopModeRef RLM = __CFRunLoopFindMode(rl, modeName, true); // Initialize the set of runloopMode creation source0 & source1 (if NULL) if (NULL! = rlm && NULL == rlm->_sources0) { rlm->_sources0 = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks); rlm->_sources1 = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks); rlm->_portToV1SourceMap = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, NULL); } // If neither runloopMode sources0 nor sources1 contains a runloopSource to be added, add the runloopSource to the corresponding set if (NULL! = rlm && ! CFSetContainsValue(rlm->_sources0, rls) && ! CFSetContainsValue(rlm->_sources1, RLS)) {if (0 == RLS ->_context.version0.version) {// RLS is source0 CFSetAddValue(RLM ->_sources0, RLS); } else if (1 == RLS ->_context.version0.version) {// RLS is source1 CFSetAddValue(RLM ->_sources1, RLS); __CFPort src_port = rls->_context.version1.getPort(rls->_context.version1.info); if (CFPORT_NULL ! = src_port) {// key is src_port, value is RLS, CFDictionarySetValue(RLM ->_portToV1SourceMap, (const void *)(uintptr_t)src_port, rls); __CFPortSetInsert(src_port, rlm->_portSet); } } __CFRunLoopSourceLock(rls); If (NULL == RLS ->_runLoops) {// source has a collection member variable runLoops. Source retains run loops every time it adds a runloop to its collection. Sources retain run Loops! (Source will hold runloop!) rls->_runLoops = CFBagCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeBagCallBacks); // sources retain run loops! } // Update the runloopSource to CFBagAddValue(RLS ->_runloops, rl); __CFRunLoopSourceUnlock(rls); If (0 == RLS ->_context.version0.version) {if (NULL! = rls->_context.version0.schedule) { doVer0Callout = true; }}}} // If source0, To the outer layer (upper) the schedule of source0 callback function called the if (doVer0Callout) {RLS - > _context. Version0. The schedule (RLS - > _context. Version0. Info, rl, modeName); /* CALLOUT */ } }Copy the code

UIButton click event source0 or source1?

UIButton’s click event is source0 or source1, which is a lot of confusion. The breakpoint print stack looks like it’s called from source0, but some say source1. Source1 is managed by runloop and the kernel, driven by Mach Port. So the button click event is first received by source1 IOHIDEvent, then the callback __IOHIDEventSystemClientQueueCallback triggered source0 within (), Source0 again trigger _UIApplicationHandleEventQueue (). So printing the call stack finds that the UIButton event is triggered by source0. We can in __IOHIDEventSystemClientQueueCallback () make a — Breakpoint to verify this.

In fact, even without click the button, as long as a touch screen, can produce a __CFRunLoopDoSource1 to __IOHIDEventSystemClientQueueCallback () calls. When the button is clicked, in addition to the above process, there is a new call from GSEventRunModal -> CFRunLoopRunSpecific -> __CFRunLoopDoSources0, so it looks like the button click event is the source0 event that is triggered directly.

RunLoop timer

CFRunLoopTimerRef is a time-based trigger that is toll-free bridged and can be mixed with NSTimer. It contains a length of time and a callback (function pointer). When it is added to the RunLoop, the RunLoop registers the corresponding point in time, and when that point is reached, the RunLoop is woken up to execute that callback.

CFRunLoopTimer structure

struct __CFRunLoopTimer { uint16_t _bits; // flag the fire state CFRunLoopRef _runLoop; // Runloop CFMutableSetRef _rlModes; // mode set. CFAbsoluteTime _nextFireDate stores modeName of all modes containing the timer, which means that a timer may exist in multiple modes. // Next trigger time CFTimeInterval _interval; // Ideal interval (immutable) CFTimeInterval _tolerance; // Error allowed (variable) CFRunLoopTimerCallBack _callout; // timer callback};Copy the code

Different from source, the corresponding runloop of timer is a pointer to runloop rather than an array, so a timer can only be added to one runloop.

Add the source of timer

Effects: Add timer to RL ->commonModeItems, add timer to runloopMode, adjust timer position in runloopMode-> Timers array based on trigger time.

// Add timer to runloopMode, Add timer to rL ->commonModeItems void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef RLT, CFStringRef modeName) { if (! __CFIsValid(rlt) || (NULL ! = rlt->_runLoop && rlt->_runLoop ! = rl)) return; // Export commonMode of runloop (if modeName is commonMode) if (modeName == kCFRunLoopCommonModes) {CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL; Rl ->commonModeItems if (NULL == rl->_commonModeItems) {rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks); } CFSetAddValue(rl->_commonModeItems, rlt); if (NULL ! CFTypeRef context[2] = {rl, RLT}; CFTypeRef context[2] = {rl, RLT}; // Set is a commonMode set, CFSetApplyFunction iterates through the set, Add the context [1] for the RLT added to all marked as mode of commonMode CFSetApplyFunction (set, (__CFRunLoopAddItemToCommonModes), (void *)context); CFRelease(set); CFRunLoopModeRef RLM = __CFRunLoopFindMode(rl, modeName, true); if (NULL ! = rlm) { if (NULL == rlm->_timers) { CFArrayCallBacks cb = kCFTypeArrayCallBacks; cb.equal = NULL; rlm->_timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &cb); } } if (NULL ! = rlm && ! CFSetContainsValue(rlt->_rlModes, rlm->_name)) { __CFRunLoopTimerLock(rlt); if (NULL == rlt->_runLoop) { rlt->_runLoop = rl; } else if (rl ! = rlt->_runLoop) { return; } // Update the rlModes set of RLT. Add RLM ->name to name CFSetAddValue(RLT ->_rlModes, RLM ->_name); // Reposition meaning Reposition. This function is essentially called to calculate the position that the timer needs to be inserted into the runloopMode->timers array based on the time it takes for the timer to trigger the next time. Then insert timer into runloopMode-> Timers array __CFRepositionTimerInMode(RLM, RLT, false); __CFRunLoopTimerFireTSRUnlock(); // For backward compatibility, if the system version is later than CFSystemVersionLion and the timer executes a rL that is not the current runloop, wake up rL if (! _CFExecutableLinkedOnOrAfter(CFSystemVersionLion)) { if (rl ! = CFRunLoopGetCurrent()) CFRunLoopWakeUp(rl); }}}}Copy the code

Set the next timer trigger time source

Void CFRunLoopTimerSetNextFireDate (CFRunLoopTimerRef RLT, CFAbsoluteTime fireDate) {/ / trigger date is greater than the maximum limit time, If (TIMER_DATE_LIMIT < fireDate) fireDate = TIMER_DATE_LIMIT; uint64_t nextFireTSR = 0ULL; uint64_t now2 = mach_absolute_time(); CFAbsoluteTime now1 = CFAbsoluteTimeGetCurrent(); If (fireDate < now1) {nextFireTSR = now2; TIMER_INTERVAL_LIMIT, TIMER_INTERVAL_LIMIT, Set the next trigger time to now + TIMER_INTERVAL_LIMIT} else if (TIMER_INTERVAL_LIMIT < firedate-now1) {nextFireTSR = now2 + __CFTimeIntervalToTSR(TIMER_INTERVAL_LIMIT); } else { nextFireTSR = now2 + __CFTimeIntervalToTSR(fireDate - now1); } __CFRunLoopTimerLock(rlt); if (NULL ! = RLT ->_runLoop) {CFIndex CNT = CFSetGetCount(RLT ->_rlModes); // declare a stack structure named modes STACK_BUFFER_DECL(CFTypeRef, modes, CNT); CFSetGetValues(RLT ->_rlModes, (const void **)modes); for (CFIndex idx = 0; idx < cnt; {// retain CFRetain(modes[idx]); } CFRunLoopRef rl = (CFRunLoopRef)CFRetain(rlt->_runLoop); // Convert the modeName stored in the modes set to the mode structure instance, and then store the modes set for (CFIndex idx = 0; idx < cnt; idx++) { CFStringRef name = (CFStringRef)modes[idx]; modes[idx] = __CFRunLoopFindMode(rl, name, false); / / release after CFRelease (name); RLT ->_fireTSR = nextFireTSR; rlt->_nextFireDate = fireDate; for (CFIndex idx = 0; idx < cnt; idx++) { CFRunLoopModeRef rlm = (CFRunLoopModeRef)modes[idx]; If (RLM) {// Reposition definition reset. As the name implies, this function is used to reset the timer. By resetting, we mean adjusting the position of the timer in the runloopMode->timers array. Then calculate the position that the timer needs to be inserted into the runloopMode->timers array according to the time it takes for the timer to trigger the next time. Finally, insert timer into runloopMode-> Timers array __CFRepositionTimerInMode(RLM, RLT, true); }} // The above comment means: This line of code is used to set the timer to date, but does not directly apply to the runloop // In case we need to wake up the runloop manually, although this may be expensive // on the other hand, // If the rL of the timer is not the current runloop, then manually wake up if (rl! = CFRunLoopGetCurrent()) CFRunLoopWakeUp(rl); } else {RLT ->_fireTSR = nextFireTSR; rlt->_nextFireDate = fireDate; }}Copy the code

RunLoop observer

The Observer, as the name implies, is the same as the Observer pattern in our design pattern. Each Observer contains a callback (function pointer), which the Observer watches for state changes in the runloop and then executes. Runloop can observe six states, as follows:

// The six states of runloop, /* Run Loop observer Activities */ typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {kCFRunLoopEntry = (1UL << 0), // About to enter Loop kCFRunLoopBeforeTimers = (1UL << 1), KCFRunLoopBeforeSources = (1UL << 2), Source kCFRunLoopBeforeWaiting = (1UL << 5), KCFRunLoopAfterWaiting = (1UL << 6), kCFRunLoopExit = (1UL << 7), // Exit Loop kCFRunLoopAllActivities = 0x0FFFFFFFU};Copy the code

CFRunLoopObserver structure definition

struct __CFRunLoopObserver { CFRunLoopRef _runLoop; // Observer observed runloop CFOptionFlags _activities; // CFOptionFlags is an alias of type UInt. _activities specifies which state of runloop to observe. Once specified, it is immutable. CFRunLoopObserverCallBack _callout; // Observe the runloop state change callback (immutable)};Copy the code

An observer can observe only one Runloop, so an observer can only be added to one or more modes of a runloop.

Add Observer source

void CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef rlo, CFStringRef modeName) {if (modeName == kCFRunLoopCommonModes) {// Export runloop commonModes CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL; // initialize commonModeItems if (NULL == rl->_commonModeItems) {rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks); } // Add an observer to commonModeItems CFSetAddValue(rl->_commonModeItems, rLO); if (NULL ! = set) { CFTypeRef context[2] = {rl, rlo}; / / add the observer to all marked as mode of commonMode CFSetApplyFunction (set, (__CFRunLoopAddItemToCommonModes), (void *) context). } } else { rlm = __CFRunLoopFindMode(rl, modeName, true); if (NULL ! = rlm && NULL == rlm->_observers) { rlm->_observers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks); } if (NULL ! = rlm && ! CFArrayContainsValue(rlm->_observers, CFRangeMake(0, CFArrayGetCount(rlm->_observers)), rlo)) { Boolean inserted = false; for (CFIndex idx = CFArrayGetCount(rlm->_observers); idx--; ) { CFRunLoopObserverRef obs = (CFRunLoopObserverRef)CFArrayGetValueAtIndex(rlm->_observers, idx); if (obs->_order <= rlo->_order) { CFArrayInsertValueAtIndex(rlm->_observers, idx + 1, rlo); inserted = true; break; } } if (! inserted) { CFArrayInsertValueAtIndex(rlm->_observers, 0, rlo); } / / set runloopMode _observerMask to observer _activities (CFRunLoopActivity state) RLM - > _observerMask | = rlo - > _activities; __CFRunLoopObserverSchedule(rlo, rl, rlm); } if (NULL ! = rlm) { __CFRunLoopModeUnlock(rlm); } } __CFRunLoopUnlock(rl); }Copy the code

Custom Observer to listen for runloop status changes

The first argument tells the system how to allocate storage space to the observer. The second argument tells the system what type of state to listen on. The third argument tells the system whether to listen repeatedly. Listening to the corresponding state after the callback * / CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler (CFAllocatorGetDefault (), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {switch (activity) {case kCFRunLoopEntry: NSLog(@" about to enter RunLoop"); break; Case kCFRunLoopBeforeTimers: NSLog(@" About to process timer"); break; Case kCFRunLoopBeforeSources: NSLog(@" about to process source"); break; Case kCFRunLoopBeforeWaiting: NSLog(@" about to go to sleep "); break; Case kCFRunLoopAfterWaiting: NSLog(@" awakened from sleep "); break; Case kCFRunLoopExit: NSLog(@" exit RunLoop"); break; default: break; }}); /* Add an observer to the main thread's RunLoop to listen for the status of the RunLoop CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);Copy the code

RunLoop runs the relevant source code

The following figure shows several functions and call relationships related to CFRunLoop operation. CFRunLoopRun calls CFRunLoopRunSpecific calls __CFRunLoopRun.

CFRunLoopRunSpecific is mainly called by CFRunLoopRun and CFRunLoopRunInMode.

CFRunLoop runs the call chain

CFRunLoopRun source

/ / a do... Runloop void CFRunLoopRun(void) {/* DOES CALLOUT */ int32_t result; Do {result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false); } while (kCFRunLoopRunStopped ! = result && kCFRunLoopRunFinished ! = result); }Copy the code

CFRunLoopRunInMode source

SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
Copy the code

CFRunLoopRunSpecific source

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Return finish if (__CFRunLoopIsDeallocating(rL)) {return if (__CFRunLoopIsDeallocating(rL)) return kCFRunLoopRunFinished; CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false); Return finish: // 1> The mode to run is not empty // The following items are determined in the __CFRunLoopModeIsEmpty function: // 2>. CurrentMode to run is source0, source1, or timer that is not null // 3>. The mode of the block to be executed is the same as the mode to be run // 4> The mode of the block to be executed is commonMode and the mode to be run is contained in commonMode. The mode of the block to be executed includes the mode // 6> to be run. The mode of the block to be executed contains commonMode and the mode to be run contains commonMode. // The block to be executed is if (NULL) added to the runloop by the external (developer) calling CFRunLoopPerformBlock  == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) { return kCFRunLoopRunFinished; } volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl); CFRunLoopModeRef previousMode = rl->_currentMode; rl->_currentMode = currentMode; int32_t result = kCFRunLoopRunFinished; CurrentMode ->_observerMask and kCFRunLoopEntry // If the bitsum result is not 0, runloop is about to be entered And what's currentMode->_observerMask? // currentMode->_observerMask is essentially an Int that identifies the CFRunLoopActivity state of the currentMode. // Calling CFRunLoopAddObserver to add an observer to runloop assigns the observer activities bitbit or mode->_observerMask if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry); / / RunLoop run the core function result = __CFRunLoopRun (rl, currentMode, seconds, returnAfterSourceHandled, previousMode); Runloop if (currentMode->_observerMask & kCFRunLoopExit) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit); rl->_currentMode = previousMode; return result; }Copy the code

__CFRunLoopRun source

This function is the core function of runloop, and almost all events are handled in this function. In order to preserve the flavor of the source code, I have not oversimplified this function, so it looks very long. Refer to the comments for a quick idea of how functions work:


/**RunLoop的运行的最核心函数(进入和退出时runloop和runloopMode都会被加锁)
 * rl: 运行的runloop
 * rlm: runloop Mode
 * seconds: runloop超时时间
 * stopAfterHandle: 处理完时间后runloop是否stop,默认为false
 * previousMode: runloop上次运行的mode
 */
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    // 获取基于系统启动后的时钟"嘀嗒"数,其单位是纳秒
    uint64_t startTSR = mach_absolute_time();
    // 状态判断
    if (__CFRunLoopIsStopped(rl)) {
        __CFRunLoopUnsetStopped(rl);
        return kCFRunLoopRunStopped;
    } else if (rlm->_stopped) {
        rlm->_stopped = false;
        return kCFRunLoopRunStopped;
    }
    // 获取主线程接收消息的port备用。如果runLoop是mainRunLoop且后续内核唤醒的port等于主线程接收消息的port,主线程就处理这个消息
    mach_port_name_t dispatchPort = MACH_PORT_NULL;
    Boolean libdispatchQSafe = pthread_main_np() && ((HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL == previousMode) || (!HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && 0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ)));
    if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name)) dispatchPort = _dispatch_get_main_queue_port_4CF();
    
#if USE_DISPATCH_SOURCE_FOR_TIMERS
    // 初始化获取timer的port(source1)
    // 如果这个port和mach_msg发消息的livePort相等则说明timer时间到了,处理timer
    mach_port_name_t modeQueuePort = MACH_PORT_NULL;
    if (rlm->_queue) {
        modeQueuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue);
    }
#endif
    // 使用GCD实现runloop超时功能
    dispatch_source_t timeout_timer = NULL;
    struct __timeout_context *timeout_context = (struct __timeout_context *)malloc(sizeof(*timeout_context));
    // seconds是设置的runloop超时时间,一般为1.0e10,11.574万年,所以不会超时
    if (seconds <= 0.0) { // instant timeout
        seconds = 0.0;
        timeout_context->termTSR = 0ULL;
    } else if (seconds <= TIMER_INTERVAL_LIMIT) {
        dispatch_queue_t queue = pthread_main_np() ? __CFDispatchQueueGetGenericMatchingMain() : __CFDispatchQueueGetGenericBackground();
        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;
    }
    
    Boolean didDispatchPortLastTime = true;
    // returnValue 标识runloop状态,如果returnValue不为0就不退出。
    // returnValue可能的值:
    // enum {
    //     kCFRunLoopRunFinished = 1,
    //     kCFRunLoopRunStopped = 2,
    //     kCFRunLoopRunTimedOut = 3,
    //     kCFRunLoopRunHandledSource = 4
    // };
    int32_t retVal = 0;
    do {
        voucher_mach_msg_state_t voucherState = VOUCHER_MACH_MSG_STATE_UNCHANGED;
        voucher_t voucherCopy = NULL;
        // 消息缓冲区,用户缓存内核发的消息
        uint8_t msg_buffer[3 * 1024];
        // 消息缓冲区指针,用于指向msg_buffer
        mach_msg_header_t *msg = NULL;
        // 用于保存被内核唤醒的端口(调用mach_msg函数时会把livePort地址传进去供内核写数据)
        mach_port_t livePort = MACH_PORT_NULL;
        __CFPortSet waitSet = rlm->_portSet;
        
        __CFRunLoopUnsetIgnoreWakeUps(rl);
        // 2. 通知 Observers: RunLoop 即将触发 Timer 回调。
        // __CFRunLoopDoObservers内部会调用__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__这个函数,这个函数的参数包括observer的回调函数、observer、runloop状态
        if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        // 3. 通知 Observers: RunLoop 即将触发 Source0 (非port) 回调。
        if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
        //  执行被加入的block
        // 外部通过调用CFRunLoopPerformBlock函数向当前runloop增加block。新增加的block保存咋runloop.blocks_head链表里。
        // __CFRunLoopDoBlocks会遍历链表取出每一个block,如果block被指定执行的mode和当前的mode一致,则调用__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__执行之
        __CFRunLoopDoBlocks(rl, rlm);
        // 4. RunLoop 触发 Source0 (非port) 回调
        // __CFRunLoopDoSources0函数内部会调用__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__函数
        // __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__函数会调用source0的perform回调函数,即rls->context.version0.perform
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        // 如果rl处理了source0事件,那再处理source0之后的block
        if (sourceHandledThisLoop) {
            __CFRunLoopDoBlocks(rl, rlm);
        }
        // 标记是否需要轮询,如果处理了source0则轮询,否则休眠
        Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
        
        if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
            msg = (mach_msg_header_t *)msg_buffer;
            // 5. 如果有 Source1 (基于port的source) 处于 ready 状态,直接处理这个 Source1 然后跳转到第9步去处理消息。
            // __CFRunLoopServiceMachPort函数内部调用了mach_msg,mach_msg函数会监听内核给端口发送的消息
            // 如果mach_msg监听到消息就会执行goto跳转去处理这个消息
            // 第五个参数为0代表不休眠立即返回
            if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
                goto handle_msg;
            }
        }
        
        didDispatchPortLastTime = false;
        // 6. 通知 Observers: RunLoop 的线程即将进入休眠(sleep)。
        // 根据上面第4步是否处理过source0,来判断如果也没有source1消息的时候是否让线程进入睡眠
        if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        // runloop置为休眠状态
        __CFRunLoopSetSleeping(rl);
        // 通知进入休眠状态后,不要做任何用户级回调
        __CFPortSetInsert(dispatchPort, waitSet);
        // 标记休眠开始时间
        CFAbsoluteTime sleepStart = poll ? 0.0 : CFAbsoluteTimeGetCurrent();
        
#if USE_DISPATCH_SOURCE_FOR_TIMERS
        do {
            msg = (mach_msg_header_t *)msg_buffer;
            // 7. __CFRunLoopServiceMachPort内部调用mach_msg函数等待接受mach_port的消息。随即线程将进入休眠,等待被唤醒。 以下事件会会唤醒runloop:
            // mach_msg接收到来自内核的消息。本质上是内核向我们的port发送了一条消息。即收到一个基于port的Source事件(source1)。
            // 一个timer的时间到了(处理timer)
            // RunLoop自身的超时时间到了(几乎不可能)
            // 被其他调用者手动唤醒(source0)
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
            
            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
        msg = (mach_msg_header_t *)msg_buffer;
        __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
#endif
        // 计算线程沉睡的时长
        rl->_sleepTime += (poll ? 0.0 : (CFAbsoluteTimeGetCurrent() - sleepStart));
        
        __CFPortSetRemove(dispatchPort, waitSet);
        
        __CFRunLoopSetIgnoreWakeUps(rl);
        // runloop置为唤醒状态
        __CFRunLoopUnsetSleeping(rl);
        // 8. 通知 Observers: RunLoop对应的线程刚被唤醒。
        if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
        // 9. 收到&处理source1消息(第5步的goto会到达这里开始处理source1)
    handle_msg:;
        // 忽略端口唤醒runloop,避免在处理source1时通过其他线程或进程唤醒runloop(保证线程安全)
        __CFRunLoopSetIgnoreWakeUps(rl);
        
        if (MACH_PORT_NULL == livePort) {
            // livePort为null则什么也不做
            CFRUNLOOP_WAKEUP_FOR_NOTHING();
            // handle nothing
        } else if (livePort == rl->_wakeUpPort) {
            // livePort为wakeUpPort则只需要简单的唤醒runloop(rl->_wakeUpPort是专门用来唤醒runloop的)
            CFRUNLOOP_WAKEUP_FOR_WAKEUP();
        }
#if USE_DISPATCH_SOURCE_FOR_TIMERS
        else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
            CFRUNLOOP_WAKEUP_FOR_TIMER();
            // 9.1 如果一个 Timer 到时间了,触发这个Timer的回调
            // __CFRunLoopDoTimers返回值代表是否处理了这个timer
            if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                __CFArmNextTimerInMode(rlm, rl);
            }
        }
#endif
#if USE_MK_TIMER_TOO
        else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
            CFRUNLOOP_WAKEUP_FOR_TIMER();
            if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                __CFArmNextTimerInMode(rlm, rl);
            }
        }
#endif
        else if (livePort == dispatchPort) {
            CFRUNLOOP_WAKEUP_FOR_DISPATCH();
            /// 9.2 如果有dispatch到main_queue的block,执行block(也就是处理GCD通过port提交到主线程的事件)。
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
            sourceHandledThisLoop = true;
            didDispatchPortLastTime = true;
        } else {
            CFRUNLOOP_WAKEUP_FOR_SOURCE();
            /// 9.3 如果一个 Source1 (基于port) 发出事件了,处理这个事件
            // 根据livePort获取source(不需要name,从mode->_portToV1SourceMap字典中以port作为key即可取到source)
            CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
            if (rls) {
                mach_msg_header_t *reply = NULL;
                // 处理source1事件(触发source1的回调)
                // runloop 触发source1的回调,__CFRunLoopDoSource1内部会调用__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__
                sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
                // 如果__CFRunLoopDoSource1响应的数据reply不为空则通过mach_msg 再send给内核
                if (NULL != reply) {
                    (void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
                    CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);
                }
            }
        }
        if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
        /// 执行加入到Loop的block
        __CFRunLoopDoBlocks(rl, rlm);
        
        if (sourceHandledThisLoop && stopAfterHandle) {
            /// 进入loop时参数说处理完事件就返回。
            retVal = kCFRunLoopRunHandledSource; // 4
        } else if (timeout_context->termTSR < mach_absolute_time()) {
            /// 超出传入参数标记的超时时间了
            retVal = kCFRunLoopRunTimedOut; // 3
        } else if (__CFRunLoopIsStopped(rl)) {
            /// 被外部调用者强制停止了
            __CFRunLoopUnsetStopped(rl); // 2
            retVal = kCFRunLoopRunStopped;
        } else if (rlm->_stopped) {
            // 调用了_CFRunLoopStopMode将mode停止了
            rlm->_stopped = false;
            retVal = kCFRunLoopRunStopped; // 2
        } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
            // source/timer/observer一个都没有了
            retVal = kCFRunLoopRunFinished; // 1
        }
        // 如果retVal不是0,即未超时,mode不是空,loop也没被停止,那继续loop
    } while (0 == retVal);
    
    if (timeout_timer) {
        dispatch_source_cancel(timeout_timer);
        dispatch_release(timeout_timer);
    } else {
        free(timeout_context);
    }
    
    return retVal;
}
Copy the code

As shown below, the author draws a detailed function call diagram to illustrate the whole call process of the above runloop:

Manually wake up the runloop

  • static void __CFRunLoopTimeout(void *arg) {}

The interval is DISPATCH_TIME_FOREVER, so this won’t fire again. Since the runloop execution time is forever, all runloops never time out, which means __CFRunLoopTimeout is never executed.

  • CFRunLoopStop(CFRunLoopRef rl) {}

A call to CFRunLoopStop means the runloop is forcibly terminated. Even if CFRunLoopWakeUp is called, the current runloop will never wake up **. The _ _CFRunLoopSetStopped function is called inside CFRunLoopStop. The __CFRunLoopSetStopped implementation is rL ->_perRunData->stopped = 0x53544F50; . Combined with CFRunLoopWakeUp function by calling __CFRunLoopIsIgnoringWakeUps (rl) examined the rl – > _perRunData – > stopped if value is true, If the value is true, the CFRunLoopWakeUp function returns and does not wake up again.

  • CF_EXPORT void _CFRunLoopStopMode(CFRunLoopRef rl, CFStringRef modeName) {}

The _CFRunLoopStopMode function simply finds the corresponding mode by modeName and sets the mode stopped to true RLM ->_stopped = true; . Will not run runloop->perRunData->stopped.

  • void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) {}

The CFRunLoopWakeUp function is called by the CFRunLoopAddTimer function purely for backward compatibility. If the system version is later than CFSystemVersionLion and the RL executed by the timer is not the current runloop, the RL is woken up. Normally, CFRunLoopAddTimer does not call the CFRunLoopWakeUp function on mainstream machines, but because the timer handler has changed, older implementations need to be compatible. Call the CFRunLoopWakeUp function on older systems.

  • static void __CFRunLoopSourceWakeUpLoop(const void value, void context) {}

CFRunLoopWakeUp((CFRunLoopRef)value)

  • void CFRunLoopTimerSetNextFireDate(CFRunLoopTimerRef rlt, CFAbsoluteTime fireDate) {}

If the RL executed by the timer is not the current runloop, CFRunLoopWakeUp is called to wake up the RL manually

In addition to waking the runloop manually, the kernel can also wake the runloop automatically by sending a message to the port.

The code to wake up runloop manually

Void CFRunLoopWakeUp(CFRunLoopRef rl) {// __CFSendTrivialMachMessage internally calls mach_msg to send a message to the runloop's wakeUpPort to wake up the runloop kern_return_t ret = __CFSendTrivialMachMessage(rl->_wakeUpPort, 0, MACH_SEND_TIMEOUT, 0); } // Manually call mach_msg to rL ->_wakeUpPort sendMsg to wake up runloop static uint32_t __CFSendTrivialMachMessage uint32_t msg_id, CFOptionFlags options, uint32_t timeout) { kern_return_t result; / / configuration header... mach_msg_header_t header; header.msgh_remote_port = port; header.msgh_id = msg_id; / / send a message to the kernel awakens the runloop result = mach_msg (& header, MACH_SEND_MSG | options, the header. The msgh_size, 0, MACH_PORT_NULL, timeout, MACH_PORT_NULL); / /... return result; }Copy the code

GCD and RunLoop

When dispatch_async(dispatch_get_main_queue(), block) is called, libDispatch sends a message to the main thread’s RunLoop, the RunLoop wakes up and takes the block from the message, This block is executed in the callback CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE(). But this logic is limited to dispatches to the main thread, dispatches to other threads are still handled by libDispatch. Then you must ask: why don’t child threads have this logic to interact with the GCD? There are two reasons:

  • The main thread runloop is the event manager for the main thread. The runloop is responsible for when and what events the runloop handles. All tasks assigned to the main thread must be queued to the main thread runloop. For example: UI operations can only be performed on the main thread. Operating on the UI outside the main thread causes a lot of UI clutter and UI update delays.
  • Child threads do not accept GCD interactions. Because child threads don’t necessarily have runloops.

Relationship between AutoreleasePool and RunLoop

The App starts, apple registered in the main thread RunLoop two Observer, the callback is _wrapRunLoopWithAutoreleasePoolHandler ().

The first Observer monitors an event called Entry(about to enter Loop), which creates an automatic release pool within its callback by calling _objc_autoreleasePoolPush(). Its order is -2147483647, the highest priority, ensuring that the release pool is created before all other callbacks.

The second Observer monitors two events: calling _objc_autoreleasePoolPop() and _objc_autoreleasePoolPush() while waiting (ready to sleep) torelease the old pool and create a new one; _objc_autoreleasePoolPop() is called upon Exit(about to Exit the Loop) torelease the automatic release pool. The order of this Observer is 2147483647, the lowest priority, ensuring that its release pool occurs after all other callbacks.

The code that executes on the main thread is usually written inside such callbacks as event callbacks and Timer callbacks. These callbacks are surrounded by AutoreleasePool created by RunLoop, so there is no memory leak and the developer does not have to show that the Pool was created.

RunLoop application

Runloop has many applications in actual development. According to the source code, Apple officially stipulates that runloop can only run in one mode at a time. To switch mode, you must exit the current mode, so as to realize the isolation of different modes of Runloop. Mode isolation of runloop isolates tasks in different modes. Isolating different modes means isolating source, timer, and observer in different modes. The advantage of this is that the current mode only executes sourece, Timer, and Observer of the current mode, which greatly saves CPU resources. This is a neat design compared to running all tasks in one mode. But instead of strictly isolating every mode, Apple provides a mode called commonMode, which is not a real mode, to allow code to be executed in different modes (for example, the list must be rotated at the same time). It’s a collection of multiple modes. At the same time, the design method is worth learning.

Apple’s use of Runloop

Apple in AutoreleasePool, gesture recognition, incident response, the UI update, timer, NSObject delay call methods (performSelecter: afterDelay:) and so on are using RunLoop.

AFNetworking uses runloop

AFNetworking is also known to use runloops internally to keep threads alive: a runLoop is added to a child thread to keep it from exiting. So, when you need the child thread, when performing a task AFNetworking by calling the NSObject performSelector: onThread:.. Just throw the task to the RunLoop of the child thread.

SDWebImage’s use of runloop

The animation playback class SDAnimatedImageView in SDWebImage also has a runloop shadow. This class exposes a runloopMode property that allows the developer to specify the runloopMode for animation playback, otherwise the internal default mode will be used. The runloopMode property is eventually passed to SDDisplayLink, which is a wrapper for CADisplayLink on iOS. – (void)addToRunLoop:(NSRunLoop *)runloop forMode:(NSRunLoopMode)mode; Set the runloopMode to CADisplayLink.

Other places where runLoop is used are for lag monitoring, asynchronous drawing, and so on. In short, the thread must have a runloop if we want it to be able to handle tasks at any time.