RunLoop isn’t a new topic in 20202, and there are plenty of articles on the subject that have been brilliantly summarized online.

As part of the iOS series, this article is a summary of some of the basics I explored in RunLoop, as well as some of the best RunLoop technology articles on the web.

If there are any errors in this article, please correct them.

RunLoop leading knowledge

IOS /OS X architecture

IOS evolution

Mac OS Classic had a great GUI, but the system design was terrible, especially the collaborative multitasking system and inefficient memory management, which was primitive by today’s standards.

The NeXTSTEP operating system was incorporated into Mac OS Classic along with Mach and OC from the architecture as part of Jobs’ return to Apple. It wasn’t so much the Mac OS Classic merging with NeXTSTEP as the Mac OS Classic replacing NeXTSTEP, and the transition didn’t happen all of a sudden.

Mac OS Classic had a good GUI but a bad system design, and NeXTSTEP had a great design but a very bland GUI. The result was a new, much more successful operating system that inherited the best of both — Mac OS X. The new Mac OS X was very similar in design and implementation to NeXTSTEP, Core components such as Cocoa, Mach, and Interface Builder are derived from NeXTSTEP

Originally called iPhone OS, iOS is Mac OS X’s answer to mobile platforms. IOS has the same operating system hierarchy as Mac OS X and the same operating system core, Dawin.

IOS Architecture

Apple officially divides the entire system into four levels:

The application layer includes graphics apps that users can access, such as Spotlight, Aqua(macOS), SpringBoard(iOS), and so on.

The application framework layer is the Cocoa and other frameworks that developers come into contact with.

The core framework layer includes various core frameworks, OpenGL and other contents.

The Darwin layer is the core of the operating system, including the XNU kernel (open source in 2017), drivers, and UNIX shell

Darwin architecture

The kernel of Darwin is XNU, which is Not Unix. XNU is a hybrid of two technologies, Mach and BSD. The BSD layer ensures the UNIX nature of Darwin systems, and the true kernel is Mach, but hidden from the outside. BSD above is user mode, all content can be accessed by the application, but the application cannot access kernel mode. When you need to switch from user mode to kernel mode, you need to use Mach trap to switch.

— Mac OS X & iOS

The three components above the hardware layer, Mach, BSD, and IOKit (along with a few others not noted above), together make up the XNU kernel.

The inner ring of the XNU kernel is called Mach, and as a microkernel, it provides only a very small number of basic services such as processor scheduling and IPC (inter-process communication).

The BSD layer can be viewed as an outer ring around the Mach layer, providing functions such as process management, file systems, and networking.

The IOKit layer is a framework that provides an object-oriented (C++) framework for device drivers.

Mach itself provides very limited apis, and Apple discourages the use of Mach’s apis, but they are so basic that nothing else can be done without them. In Mach, everything was implemented through its own objects, with processes, threads, and virtual memory all referred to as “objects.”

Unlike other architectures, Mach objects cannot be called directly, but can communicate with each other only through messaging.

Messages are the most basic concept in Mach. Messages are transmitted between two ports. This is the core of IPC (inter-process communication) in Mach.

— Ibireme, Understanding RunLoop

Mach_msg function

The mach_msg function is in the Message.h header file of the Mach kernel

This is the function that the kernel uses to send and receive messages on a port, and understanding this function is important to understand how Runloop works. Detailed instructions can be found here.

// Mach message infrastructure
typedef struct{
  // Message header
	mach_msg_header_t       header;
	/ / the message body
	mach_msg_body_t         body;
} mach_msg_base_t;

// Mach header structure
typedef struct{
  // The bits of the header
	mach_msg_bits_t       msgh_bits;
	// The size of the header
	mach_msg_size_t       msgh_size;
	// Remote port number
	mach_port_t           msgh_remote_port;
	// Local port number
	mach_port_t           msgh_local_port;
	// Credential port number
	mach_port_name_t      msgh_voucher_port;
	/ / message ID
	mach_msg_id_t         msgh_id;
} mach_msg_header_t;

/ / mach_msg function
mach_msg_return_t mach_msg(
    / / the message header
    mach_msg_header_t *msg,
    // Message option, which can be used to specify whether to send the message (MACH_SEND_MSG) or receive the message (MACH_RCV_MSG)
    mach_msg_option_t option,
    // Size of cache set when sending messages, unsigned integer
    mach_msg_size_t send_size,
    // The size of the cache set when receiving messages, an unsigned integer
    mach_msg_size_t rcv_size,
    // Port to receive messages, an unsigned integer
    mach_port_name_t rcv_name,
    // Message expiration time
    // When the option parameter contains MACH_SEND_TIMEOUT or MACH_RCV_TIMEOUT, an expiration time is passed in milliseconds
    mach_msg_timeout_t timeout,
    // The port to receive notifications
    // If the option parameter contains MACH_SEND_CANCEL and MACH_RCV_NOTIFY, set the port for receiving notifications. Otherwise, pass MACH_PORT_NULL.
    mach_port_name_t notify);
Copy the code

Mach_msg can be simply understood as a communication mechanism between multiple processes. Different processes can use the same message queue to communicate data. When mach_msg is used to read MSG from the message queue, timeout value can be set in the parameter. If MSG is not read before timeout, the current thread will remain dormant. This is why runloop can go to sleep when there are no more tasks to execute. — Decrypt Runloop MrPeak

To send and receive messages, the mach_msg() function actually calls a Mach trap, the function mach_MSg_trap (), which is the equivalent of a system call in Mach. When you call mach_MSg_trap () in user mode, the trap mechanism is triggered, switching to kernel mode; The mach_msg() function implemented by the kernel in the kernel state does the actual work. — Ibireme, Understanding RunLoop

CoreFoundation basis

CoreFoundation is a SET of C-based apis that provide basic data management and service capabilities for iOS. Because CoreFoundation objects are implemented based on C, there is also the concept of reference counting, while Foundation objects are implemented based on OC. “Bridge”, “bridge_retained”, and “bridge_transfer” are used for translation of two types of objects.

__bridge

id obj = [[NSObject alloc] init];
void *p = (__bridge void *)obj;
id o = (__bridge id)p;
Copy the code

Converting an Objective-C object type to void using a *bridge is the same as using the **unsafe_unretained keyword.

__bridge: Does only type conversion, does not modify the related object reference count, the original Core Foundation object when not in use, need to call CFRelease method

__bridge_retained

d obj = [[NSObject alloc] init]; 
void *p = (__bridge_retained void *)obj;
Copy the code

The name should make sense: when a type is converted, ownership of its object is also held by the converted variable. In MRC:

id obj = [[NSObject alloc] init];
void *p = obj;
[(id)p retain];
Copy the code

The following example verifies that outside the braces, p still refers to a valid entity. Indicates that she owns the object and that the object was not destroyed because it was outside the scope of its definition.

void *p = 0;
{
 id obj = [[NSObject alloc] init];
 p = (__bridge_retained void *)obj;
}
NSLog(@"class=%@", [(__bridge id)p class]);
Copy the code

__bridge_retained: After type conversion, the reference count of the relevant object is increased by 1. If the original Core Foundation object is not used, the CFRelease method needs to be called

__bridge_transfer

MRC:

// the p variable originally holds the ownership of the object
id obj = (id)p;
[obj retain];
[(id)p release];
Copy the code

The ARC:

// the p variable originally holds the ownership of the object
id obj = (__bridge_transfer id)p;
Copy the code

Retained by **bridge_transfer ** retained by the compiler, and released by **bridge_transfer.

__bridge_transfer: After a type conversion, the object’s reference count is managed by ARC. Core Foundation objects do not need to call the CFRelease method when they are not in use

Toll-Free bridged

There’s a toll-free bridging mechanism between CoreFoundation and Foundation. In the ERA of MRC, it is possible to convert two frame objects directly through type force. In the ARC era, however, you would need to use the toll-free Bridging mechanism. There are three different conversion mechanisms described above, but in Foundation, there is an easier way to do it. As follows:

// After using a CFBridgingRetain on an NSObject, the caller must take responsibility for calling CFRelease at an appropriate time.
NS_INLINE CF_RETURNS_RETAINED CFTypeRef _Nullable CFBridgingRetain(id _Nullable X) {
    return (__bridge_retained CFTypeRef)X;
}

NS_INLINE id _Nullable CFBridgingRelease(CFTypeRef CF_CONSUMED _Nullable X) {
    return (__bridge_transfer id)X;
}
Copy the code

CFBridgingRetain can replace **bridge_retained, and CFBridgingRelease can replace **bridge_transfer.

RunLoop que

What is the RunLoop

As defined in the Official Apple documentation

Run Loops are part of the fundamental infrastructure associated with threads.

A run loop is an event processing loop that you use to schedule work and coordinate the receipt of incoming events. The purpose of a run loop is to keep your thread busy when there is work to do and put your thread to sleep when there is None. RunLoop is an event loop that schedules tasks and handles events. The idea is to keep threads busy when they have something to do and dormant when they have nothing to do.

The NodeJS event loop is implemented in pseudo-code as follows:

    while(AppIsRunning) {
        id whoWakesMe = SleepForWakingUp();
        id event = GetEvent(whoWakesMe);
        HandleEvent(event);
    }
Copy the code

This pseudo-code was shared by Sunnyx’s RunLoop technology

As you can see, RunLoop is such a simple way to work. But the internal implementation is much more complex than the code above. We’ll take a deeper look at RunLoop in the next section.

OSX/iOS provides two such objects: NSRunLoop and CFRunLoopRef.

CFRunLoopRef is within the CoreFoundation framework and provides apis for pure C functions, all of which are thread-safe.

NSRunLoop is a wrapper based on CFRunLoopRef that provides object-oriented apis, but these apis are not thread-safe.

RunLoop underlying data structure

As shown in the figure above, there are multiple modes inside each RunLoop, and inside each mode are sources0, source1, observers, and timers. The elements in a mode are collectively called mode items. Let’s review the data structure definitions of these five classes:

  • CFRunLoopRef
  • CFRunLoopModeRef
  • CFRunLoopSourceRef
  • CFRunLoopTimerRef
  • CFRunLoopObserverRef

CFRunLoopRef

typedef struct __CFRunLoop * CFRunLoopRef;

struct __CFRunLoop
{
    // CoreFoundation runtime base information
    CFRuntimeBase _base;
    // Lock for the mode list operation
    pthread_mutex_t _lock; /* locked for accessing mode list */
    // Wake up the port
    __CFPort _wakeUpPort;  // used for CFRunLoopWakeUp
    // Whether it has been used
    Boolean _unused;
    // A data structure that runloop resets
    volatile _per_run_data *_perRunData; // reset for runs of the run loop
    // The thread of the runloop
    pthread_t _pthread;
    uint32_t _winthread;
    // Store the common mode set
    CFMutableSetRef _commonModes;
    // Set common mode item
    CFMutableSetRef _commonModeItems;
    // runloop current mode
    CFRunLoopModeRef _currentMode;
    // Store a set of modes
    CFMutableSetRef _modes;
    
    // Runloop internal block list header pointer
    struct _block_item* _blocks_head;
    // The pointer to the end of the runloop block list
    struct _block_item* _blocks_tail;
    // Run time point
    CFAbsoluteTime _runTime;
    // Sleep time point
    CFAbsoluteTime _sleepTime;
    CFTypeRef _counterpart;
};

// The RunLoop is reset after each run
typedef struct _per_run_data
{
    uint32_t a;
    uint32_t b;
    uint32_t stopped;   // Whether runloop stops
    uint32_t ignoreWakeUps; // Whether runloop has been awakened
} _per_run_data;

// List nodes
struct _block_item
{
    // point to the next _block_item
    struct _block_item* _next;
    // Can be either string or set, meaning that a block may correspond to one or more modes
    CFTypeRef _mode; // CFString or CFSet
    // Hold the actual block to execute
    void (^_block)(void);
};
Copy the code

As you can see from the code above, CFRunLoopRef is actually a structure pointer of type __CFRunLoop. The following attributes within __CFRunLoop need attention:

  • _pthread
  • _commonModes
  • _currentMode
  • _modes

CFRunLoopModeRef

typedef struct __CFRunLoopMode *CFRunLoopModeRef;

struct __CFRunLoopMode
{
    // CoreFoundation runtime base information
    CFRuntimeBase _base;
    // mutex, runloop must be locked before locking
    pthread_mutex_t _lock; /* must have the run loop locked before locking this */
    // The mode name
    CFStringRef _name;
    // mode Whether to stop
    Boolean _stopped;
    char _padding[3];
    // source0
    CFMutableSetRef _sources0;
    // source1
    CFMutableSetRef _sources1;
    // observers
    CFMutableArrayRef _observers;
    // timers
    CFMutableArrayRef _timers;
    CFMutableDictionaryRef _portToV1SourceMap;
    // A collection of ports
    __CFPortSet _portSet;
    / / the observer's mask
    CFIndex _observerMask;
    // If the GCD timer is defined
#if USE_DISPATCH_SOURCE_FOR_TIMERS
    // GCD timer
    dispatch_source_t _timerSource;
    / / the queue
    dispatch_queue_t _queue;
    // Set to true when the GCD timer is triggered
    Boolean _timerFired; // set to true by the source when a timer has fired
    Boolean _dispatchTimerArmed;
#endif
// if MK_TIMER is used
#if USE_MK_TIMER_TOO
    / / MK_TIMER port
    mach_port_t _timerPort;
    Boolean _mkTimerArmed;
#endif
#if DEPLOYMENT_TARGET_WINDOWS
    DWORD _msgQMask;
    void (*_msgPump)(void);
#endif
    // Timer soft critical point
    uint64_t _timerSoftDeadline; /* TSR */
    // Timer hard critical point
    uint64_t _timerHardDeadline; /* TSR */
};
Copy the code

CFRunLoopModeRef is a pointer to a __CFRunLoopMode structure. The following attributes within __CFRunLoopMode require attention:

  • _sources0
  • _sources1
  • _observers
  • _timers

CFRunLoopSourceRef

typedef struct __CFRunLoopSource * CFRunLoopSourceRef;

struct __CFRunLoopSource
{
    // CoreFoundation runtime base information
    CFRuntimeBase _base;
    uint32_t _bits;
    / / the mutex
    pthread_mutex_t _lock;
    // The priority of source. The value is small and the priority is higher
    CFIndex _order; /* immutable */
    / / runloop collection
    CFMutableBagRef _runLoops;
    // a consortium, indicating that source is either source0 or source1
    union {
        CFRunLoopSourceContext version0;  /* immutable, except invalidation */
        CFRunLoopSourceContext1 version1; /* immutable, except invalidation */
    } _context;
};

typedef struct {
    CFIndex version;
    // Source information
    void *  info;
    const void *(*retain)(const void *info);
    void    (*release)(const void *info);
    CFStringRef (*copyDescription)(const void *info);
    // Determine the source equal function
    Boolean (*equal)(const void *info1, const void *info2);
    CFHashCode  (*hash)(const void *info);
    void    (*schedule)(void *info, CFRunLoopRef rl, CFStringRef mode);
    void    (*cancel)(void *info, CFRunLoopRef rl, CFStringRef mode);
    // source Specifies the task block to execute
    void    (*perform)(void *info);
} CFRunLoopSourceContext;
Copy the code

As you can see from the code above, CFRunLoopSourceRef is actually a structure pointer of type __CFRunLoopSource. The __CFRunLoopSource structure is relatively simple, with a few attributes to focus on:

  • _order
  • _context
  • CFRunLoopSourceContextUnder theperformA function pointer

CFRunLoopTimerRef

typedef struct CF_BRIDGED_MUTABLE_TYPE(NSTimer) __CFRunLoopTimer * CFRunLoopTimerRef;

struct __CFRunLoopTimer
{
    // CoreFoundation runtime base information
    CFRuntimeBase _base;
    uint16_t _bits;
    / / the mutex
    pthread_mutex_t _lock;
    // Timer corresponds to the runloop
    CFRunLoopRef _runLoop;
    // Set of modes corresponding to timer
    CFMutableSetRef _rlModes;
    // Next trigger time
    CFAbsoluteTime _nextFireDate;
    // Timer Interval for each task
    CFTimeInterval _interval;        /* immutable */
    // Error allowed by timer
    CFTimeInterval _tolerance;       /* mutable */
    // Trigger time point
    uint64_t _fireTSR;               /* TSR units */
    // Priority of the timer
    CFIndex _order;                  /* immutable */
    // The task to be executed by the timer
    CFRunLoopTimerCallBack _callout; /* immutable */
    // Timer context
    CFRunLoopTimerContext _context;  /* immutable, except invalidation */
};

typedef struct {
    CFIndex	version;
    void *	info;
    const void *(*retain)(const void *info);
    void	(*release)(const void *info);
    CFStringRef	(*copyDescription)(const void *info);
} CFRunLoopTimerContext;

typedef void (*CFRunLoopTimerCallBack)(CFRunLoopTimerRef timer, void *info);
Copy the code

CFRunLoopTimerRef is a pointer to a __CFRunLoopTimer structure. And here __CFRunLoopTimer is preceded by CF_BRIDGED_MUTABLE_TYPE(NSTimer)

#define CF_BRIDGED_TYPE(T)		__attribute__((objc_bridge(T)))
#define CF_BRIDGED_MUTABLE_TYPE(T)	__attribute__((objc_bridge_mutable(T)))
#define CF_RELATED_TYPE(T,C,I)		__attribute__((objc_bridge_related(T,C,I)))
#else
#define CF_BRIDGED_TYPE(T)
#define CF_BRIDGED_MUTABLE_TYPE(T)
#define CF_RELATED_TYPE(T,C,I)
#endif
Copy the code

It can be seen from here that CFRunLoopTimerRef and NS Stimer are toll-free bridged.

There are several attributes in __CFRunLoopTimer that need to be focused on:

  • _order
  • _callout
  • _context

CFRunLoopObserverRef

typedef struct __CFRunLoopObserver * CFRunLoopObserverRef;

struct __CFRunLoopObserver
{
    // CoreFoundation runtime base information
    CFRuntimeBase _base;
    / / the mutex
    pthread_mutex_t _lock;
    // Observer corresponding runloop
    CFRunLoopRef _runLoop;
    // The observer observed how many runloops there were
    CFIndex _rlCount;
    CFOptionFlags _activities;          /* immutable */
    // Observer priority
    CFIndex _order;                     /* immutable */
    // Observer callback function
    CFRunLoopObserverCallBack _callout; /* immutable */
    // Observer context
    CFRunLoopObserverContext _context;  /* immutable, except invalidation */
};

typedef struct {
    CFIndex	version;
    void *	info;
    const void *(*retain)(const void *info);
    void	(*release)(const void *info);
    CFStringRef	(*copyDescription)(const void *info);
} CFRunLoopObserverContext;

typedef void (*CFRunLoopObserverCallBack)(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info);
Copy the code

As you can see from the code above, CFRunLoopObserverRef is actually a structure pointer of type __CFRunLoopObserver.

There are several attributes in __CFRunLoopObserver that need to be focused on:

  • _order
  • _callout
  • _context

CFRunLoopActivity

/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),
    kCFRunLoopBeforeTimers = (1UL << 1),
    kCFRunLoopBeforeSources = (1UL << 2),
    kCFRunLoopBeforeWaiting = (1UL << 5),
    kCFRunLoopAfterWaiting = (1UL << 6),
    kCFRunLoopExit = (1UL << 7),
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};
Copy the code

As you can see from the source code above, RunLoop has the following states

  • KCFRunLoopEntry: the Loop is about to enter
  • KCFRunLoopBeforeTimers: The Timer is to be processed
  • KCFRunLoopBeforeSources: Source is about to be processed
  • KCFRunLoopBeforeWaiting: is about to go to sleep
  • KCFRunLoopAfterWaiting: just woke up from sleep
  • KCFRunLoopExit: Loop is coming

RunLoop relationships with threads

To figure out how runloops relate to threads, start with the CFRunLoopGetMain and CFRunLoopGetCurrent functions:

CFRunLoopRef CFRunLoopGetMain(void)
{
    CHECK_FOR_FORK();
    // Declare an empty RunLoop
    static CFRunLoopRef __main = NULL; // no retain needed
    // If __main is empty, the _CFRunLoopGet0 function is called to get RunLoop
    if(! __main) __main = _CFRunLoopGet0(pthread_main_thread_np());// no CAS needed
    return __main;
}

CFRunLoopRef CFRunLoopGetCurrent(void)
{
    CHECK_FOR_FORK();
    // Get runloop from Thread Specific Data (TSD)
    CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
    // If you get it, return it
    if (rl)
        return rl;
    // If not in TSD, _CFRunLoopGet0 is called
    return _CFRunLoopGet0(pthread_self());
}
Copy the code

Pthread_main_thread_np () gets the main thread, and its internal implementation is as follows

#define pthread_main_thread_np() _CF_pthread_main_thread_np()
Copy the code

CF_EXPORT pthread_t _CF_pthread_main_thread_np(void); pthread_t _CF_pthread_main_thread_np(void) { return _CFMainPThread; }

void __CFInitialize(void) { …

_CFMainPThread = pthread_self();

. }

We then explore the _CFRunLoopGet0 function, which is implemented as follows

// A dictionary that stores runloops and threads
static CFMutableDictionaryRef __CFRunLoops = NULL;
static CFLock_t loopsLock = CFLockInit;

// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
// if t is 0, we want to get the main thread RunLoop
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t)
{
    Pthread_main_thread_np () ¶
    if (pthread_equal(t, kNilPthreadT))
    {
        t = pthread_main_thread_np();
    }
    / / lock
    __CFLock(&loopsLock);
    // If __CFRunLoops is empty, initialize the dictionary and add mainLoop to the dictionary
    if(! __CFRunLoops) {/ / unlock
        __CFUnlock(&loopsLock);
        // Initialize a mutable dictionary
        CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0.NULL, &kCFTypeDictionaryValueCallBacks);
        // Get the RunLoop corresponding to the main thread
        CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
        // Store runloop and thread in the dictionary, thread for key and runloop for value
        CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
        if(! OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void *volatile *)&__CFRunLoops))
        {
            // Release the dictionary
            CFRelease(dict);
        }
        / / release the mainLoop
        CFRelease(mainLoop);
        / / lock
        __CFLock(&loopsLock);
    }
    // Get the loop object from the dictionary based on the thread t passed in
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    // Open loops
    __CFUnlock(&loopsLock);
    // If loop is empty
    if(! loop) {// Create a new loop
        CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        // Loop loops
        __CFLock(&loopsLock);
        // Get loops from loops
        loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        if(! loop) {// If you don't find loop in the dictionary, put a new loop in the Loops dictionary
            CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
            loop = newLoop;
        }
        // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
        // Do not release runloop objects in the dictionary lock scope, because CFRunLoopDeallocate will release runloop objects
        __CFUnlock(&loopsLock);
        / / release newLoop
        CFRelease(newLoop);
    }
    // Check whether t is the current thread
    if (pthread_equal(t, pthread_self()))
    {
        // Set Thread Specific Data (TSD), which is also a dictionary
        // The __CFTSDKeyRunLoop is the key and the loop object value is stored in TSD
        /** // Set some thread specific data in a pre-assigned slot. Don't pick a random value. Make sure you're using a slot that is unique. Pass in a destructor to free this data, or NULL if none is needed. Unlike pthread TSD, The destructor is per-thread. // When the TSD of pthreads is different, CF_EXPORT void *_CFSetTSD(uint32_t slot, void *newVal, void (*destructor)(void *)); * /
        _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
        if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr))
        {
            _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS - 1), (void(*) (void*))__CFFinalizeRunLoop); }}return loop;
}
Copy the code

With the above source code, we have identified a data structure with a dictionary at the bottom. The dictionary stores pthread_t thread keys and CFRunLoopRef values. We can draw the following conclusions:

  • Each thread has a unique RunLoop object corresponding to it
  • Runloops are stored in a global Dictionary, with threads as keys and runloops as values
  • The thread is created without a RunLoop object; the RunLoop is created the first time it gets it
  • The RunLoop is destroyed at the end of the thread
  • The RunLoop for the main thread is automatically acquired (created), and RunLoop is not enabled for the child thread by default

Runloop underlying

RunLoop Boot mode

void CFRunLoopRun(void)
{ /* DOES CALLOUT */
    int32_t result;
    do
    {
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0 e10.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);
}
Copy the code

From the above source:

  • The default startup mode is throughCFRunLoopRunTo implement, the timeout is passed in1.0 e10This is a large number, so it can be interpreted as no timeout.
  • You can customize the boot mode and set other parameters, such as the timeout period and mode.

RunLoop core logic

Let’s go to CFRunLoopRunSpecific:

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled)
{ /* DOES CALLOUT */
    CHECK_FOR_FORK();
    // If the runloop is being recycled, return kCFRunLoopRunFinished to indicate that the runloop is complete
    if (__CFRunLoopIsDeallocating(rl))
        return kCFRunLoopRunFinished;
    // Lock runloop
    __CFRunLoopLock(rl);
    // Find the given mode from the runloop
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
    // If no mode is found and currentMode of the current runloop is null, enter if logic
    // if __CFRunLoopModeIsEmpty is empty, runloop has completed all tasks
    if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode))
    {
        Boolean did = false;
        // If currentMode is not null
        if (currentMode)
            // Unlock currentMode
            __CFRunLoopModeUnlock(currentMode);
        // Unlock runloop
        __CFRunLoopUnlock(rl);
        return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
    }
    // Temporarily extract per_run_data from runloop
    volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
    // Retrieve the current mode of runloop
    CFRunLoopModeRef previousMode = rl->_currentMode;
    // Assign the found mode to runloop's _curentMode, that is, the runloop completes the mode switch
    rl->_currentMode = currentMode;
    // Initialization returns result
    int32_t result = kCFRunLoopRunFinished;

    // If an Observer is registered to listen for kCFRunLoopEntry status (about to enter loop), notify the Observer
    if (currentMode->_observerMask & kCFRunLoopEntry)
        __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    // The runloop core function __CFRunLoopRun
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    // Notify the Observer if the observer is registered to listen for kCFRunLoopExit status (loop is about to be rolled out)
    if (currentMode->_observerMask & kCFRunLoopExit)
        __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
    // Unlock currentMode
    __CFRunLoopModeUnlock(currentMode);
    // Restore the original previousPerRun
    __CFRunLoopPopPerRunData(rl, previousPerRun);
    // Restore the original mode
    rl->_currentMode = previousMode;
    // Unlock runloop
    __CFRunLoopUnlock(rl);
    return result;
}
Copy the code

After simplifying the code in this section:

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled)
{ /* DOES CALLOUT */
    // Notify Observers to enter loop
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    
    // Specific things to do
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    
    // Notify Observers of exit loop
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);

    return result;
}
Copy the code

Work pays off, finally came to the core process __CFRunLoopRun function, to put it bluntly, a running cycle is a __CFRunLoopRun run.

static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode)
Copy the code

There are five __CFRunLoopRun arguments, each representing:

  • rlObject: CFRunLoopRef
  • rlm: Indicates the name of mode
  • seconds: Timeout period
  • stopAfterHandle: Whether to return directly after processing source
  • previousMode: Mode of the previous run cycle

The complete and condensed __CFRunLoopRun code is provided below. Readers can choose to read it according to their own needs.

__CFRunLoopRun Complete code

static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode)
{
    // Get the start time
    uint64_t startTSR = mach_absolute_time();

    if (__CFRunLoopIsStopped(rl))
    {
        // If Runloop is stopped, change the stop status of the underlying data structure of Runloop to flag
        __CFRunLoopUnsetStopped(rl);
        // Return the status kCFRunLoopRunStopped
        return kCFRunLoopRunStopped;
    }
    else if (rlm->_stopped)
    {
        // When Mode is stopped, change the Mode member _stopped to false
        rlm->_stopped = false;
        // Return the status kCFRunLoopRunStopped
        return kCFRunLoopRunStopped;
    }

    // Determine whether there is a Mach Port for the main queue
    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
    Fetch the corresponding Mach Port from the mode member variable _queue
    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

    // Declare an empty GCD timer
    dispatch_source_t timeout_timer = NULL;
    // Initializes a timeout context structure pointer object
    struct __timeout_context *timeout_context = (struct __timeout_context *)malloc(sizeof(*timeout_context));
    // If the runloop timeout is less than or equal to 0
    if (seconds <= 0.0)
    { // instant timeout
        // Set seconds to 0s
        seconds = 0.0;
        // set the termTSR property of the timeout context to 0,0 ULL for an Unsigned Long Long
        timeout_context->termTSR = 0ULL;
    }
    else if (seconds <= TIMER_INTERVAL_LIMIT)
    {
        // If the runloop timeout is less than or equal to TIMER_INTERVAL_LIMIT
        If the main thread exists, get the main queue of the main thread
        // If the main thread does not exist, get the global queue
        dispatch_queue_t queue = pthread_main_np() ? __CFDispatchQueueGetGenericMatchingMain() : __CFDispatchQueueGetGenericBackground();
        // Pass in the queue and initialize a GCD object of type timer
        timeout_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0.0.queue);
        // Retain the GCD object
        dispatch_retain(timeout_timer);
        // Assign the GCD object to the ds property of the timeout context structure
        timeout_context->ds = timeout_timer;
        // Retain the runloop object and assign to the RL property of the Timeout context structure
        timeout_context->rl = (CFRunLoopRef)CFRetain(rl);
        // Add the start time of the runloop to the time to run to get the runloop life time
        // Then assign to the termTSR property of the timeout context structure
        timeout_context->termTSR = startTSR + __CFTimeIntervalToTSR(seconds);
        
        // Sets the context of the GCD object to a custom "timeout context" structure
        dispatch_set_context(timeout_timer, timeout_context); // source gets ownership of context
        
        // Setting the callback of the GCD object can be understood as the task to be performed
        /** static void __CFRunLoopTimeout(void *arg) { struct __timeout_context *context = (struct __timeout_context *)arg; context->termTSR = 0ULL; CFRUNLOOP_WAKEUP_FOR_TIMEOUT(); CFRunLoopWakeUp(context->rl); // The interval is DISPATCH_TIME_FOREVER, so this won't fire again} 2. The CFRUNLOOP_WAKEUP_FOR_TIMEOUT() macro is inside a useless do-while(0) operation that can be ignored by 3. The runloop */ is then woken up by the CFRunLoopWakeUp function
        dispatch_source_set_event_handler_f(timeout_timer, __CFRunLoopTimeout);
        // Set the cancellation callback of the GCD object
        dispatch_source_set_cancel_handler_f(timeout_timer, __CFRunLoopTimeoutCancel);
        
        // Add the start time to the given runloop expiration time, because the result is in seconds and needs to be converted to nanoseconds, so multiply by 10 ^ 9
        uint64_t ns_at = (uint64_t)((__CFTSRToTimeInterval(startTSR) + seconds) * 1000000000ULL);
        
        // Set the GCD timer
        // The second argument indicates when the timer starts, that is, if CFRunLoopRun comes in
        // Since the value passed in is 1.0e10, the timer will never fire, which means the runloop will never time out
        // The third parameter indicates the timer interval of nanoseconds. The DISPATCH_TIME_FOREVER parameter is passed, indicating that the timer is fired only once, not multiple times
        // The fourth parameter indicates the nanosecond fault tolerance time of the timer
        dispatch_source_set_timer(timeout_timer, dispatch_time(1, ns_at), DISPATCH_TIME_FOREVER, 1000ULL);
        // Start GCD timer
        dispatch_resume(timeout_timer);
    }
    else
    { // infinite timeout
        // Set the timeout to 9999999999
        // Then set the termTSR property of the custom Timeout context structure to UNIT64_MAX
        seconds = 9999999999.0;
        timeout_context->termTSR = UINT64_MAX;
    }

    // Initialize whether a Boolean was last sent through the Dispatch port
    Boolean didDispatchPortLastTime = true;
    int32_t retVal = 0;
    do
    {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
        voucher_mach_msg_state_t voucherState = VOUCHER_MACH_MSG_STATE_UNCHANGED;
        voucher_t voucherCopy = NULL;
#endif
        // Initialize an unsigned int8 integer message buffer with a size of 3KB
        uint8_t msg_buffer[3 * 1024];
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
        // Initialize an empty MSG structure pointer variable of type mach_MSG_header_t
        mach_msg_header_t *msg = NULL;
        // Initialize a port with the value MACH_PORT_NULL
        mach_port_t livePort = MACH_PORT_NULL;

        // get _portSet from mode
        __CFPortSet waitSet = rlm->_portSet;

        // Set ignoreWakeUps to 0 for runloop's underlying data structure
        __CFRunLoopUnsetIgnoreWakeUps(rl);

        // Check whether the mask and upper kCFRunLoopBeforeTimers enumeration values of the Mode observer are 1
        // If successful, notify Observers that Timers are about to be processed
        if (rlm->_observerMask & kCFRunLoopBeforeTimers)
            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        
        // Check if the mask and the kCFRunLoopBeforeSources enumeration are 1 in the observer of mode
        // If successful, notify Observers that they are about to handle Sources
        if (rlm->_observerMask & kCFRunLoopBeforeSources)
            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);

        // Process Blocks, passing runloop and mode
        __CFRunLoopDoBlocks(rl, rlm);

        // Process Source0, passing runloop and mode, and stopAfterHandle
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        // If processing Sources0 succeeds, process Blocks again
        if (sourceHandledThisLoop)
        {
            __CFRunLoopDoBlocks(rl, rlm);
        }
        
        // If source0 succeeds or the timeout is 0, set poll to true
        Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);

        // If the primary queue port is not empty and the last loop did not have a primary queue port
        if(MACH_PORT_NULL ! = dispatchPort && ! didDispatchPortLastTime) {#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
            // Initialize a MSG of the mach_MSG_header_t structure
            msg = (mach_msg_header_t *)msg_buffer;
            // Check whether there is a message on the port of the main queue. If so, enter handle_msg
            if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL))
            {
                // There is a message coming from the port of the main queue and runloop needs to be woken up
                goto handle_msg;
            }
#endif
        }
        
        // Make the last loop main queue value empty
        didDispatchPortLastTime = false;

        // No message is pulled from port, and mode's _observerMask is true with kCFRunLoopBeforeWaiting,
        // Notify Observers that they are about to hibernate
        if(! poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);/** CF_INLINE void __CFRunLoopSetSleeping(CFRunLoopRef rl) { __CFBitfieldSetValue(((CFRuntimeBase *)rl)->_cfinfo[CF_INFO_BITS], 1, 1, 1); } * /
        // Set the flag for runloop sleeping
        __CFRunLoopSetSleeping(rl);
        // do not do any user callouts after this point (after notifying of sleeping)
        // Do not make any user calls after runloop has gone to sleep

        // 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.
        
        /** // The local-to-this-activation ports must be pushed in // iteration for each loop, since this mode can be run again and we do not // want these ports to be serviced. * /
        // Add the primary queue port to waitSet
        __CFPortSetInsert(dispatchPort, waitSet);

        / / unlock mode
        __CFRunLoopModeUnlock(rlm);
        / / unlock runloop
        __CFRunLoopUnlock(rl);

        SleepStart is 0 if poll is true, otherwise the current time
        CFAbsoluteTime sleepStart = poll ? 0.0 : CFAbsoluteTimeGetCurrent();

#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
#if USE_DISPATCH_SOURCE_FOR_TIMERS
        // This do-while loop is for the GCD timer
        do
        {
            if (kCFUseCollectableAllocator)
            {
                // objc_clear_stack(0);
                // <rdar://problem/16393959>
                // Set the msg_buffer buffer to 0
                memset(msg_buffer, 0.sizeof(msg_buffer));
            }
            msg = (mach_msg_header_t *)msg_buffer;
            // Check whether the port of waitSet has a message
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);

            // Check that modeQueuePort is not empty and livePort is modeQueuePort
            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.
                /** Clears the internal queue. If one of the annotation blocks has the timerFired flag set, interrupt and service the timer. * /
                while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue))
                    ;
                // If mode's _timerFired is true, set it to false and exit the do-while loop
                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);
            // <rdar://problem/16393959>
            // Set the msg_buffer buffer to 0
            memset(msg_buffer, 0.sizeof(msg_buffer));
        }
        msg = (mach_msg_header_t *)msg_buffer;
        // Check whether the port of waitSet has a message
        __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
#endif

#endif
        // Lock runloop
        __CFRunLoopLock(rl);
        // Lock mode
        __CFRunLoopModeLock(rlm);

        // If poll is true, the home team wakes up the Runloop; if poll is true, the home team gets the runloop's sleep time
        rl->_sleepTime += (poll ? 0.0 : (CFAbsoluteTimeGetCurrent() - sleepStart));

        // 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.
        
        
        /** CF_INLINE kern_return_t __CFPortSetRemove(__CFPort port, __CFPortSet portSet) { if (MACH_PORT_NULL == port) { return -1; } return mach_port_extract_member(mach_task_self(), port, portSet); } * /
        // Remove dispatchPort from waitSet because dispatchPort is added to waitSet in each runloop iteration
        __CFPortSetRemove(dispatchPort, waitSet);
        
        /** CF_INLINE void __CFRunLoopSetIgnoreWakeUps(CFRunLoopRef rl) { rl->_perRunData->ignoreWakeUps = 0x57414B45; // 'WAKE'} WAKE runloop */
        __CFRunLoopSetIgnoreWakeUps(rl);

        // user callouts now OK again
        // Set the sleeping flag of runloop to 0
        __CFRunLoopUnsetSleeping(rl);
        
        // Notify Observers to end hibernation
        if(! poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting); handle_msg:;/** CF_INLINE void __CFRunLoopSetIgnoreWakeUps(CFRunLoopRef rl) { rl->_perRunData->ignoreWakeUps = 0x57414B45; // 'WAKE'} indicates that a port message has been received and needs to be processed
        __CFRunLoopSetIgnoreWakeUps(rl);

        // If the wakeup port is empty, nothing is done
        if (MACH_PORT_NULL == livePort)
        {
            CFRUNLOOP_WAKEUP_FOR_NOTHING();
            // handle nothing
        }
        // If the wakeup port is _wakeUpPort of runloop, CFRUNLOOP_WAKEUP_FOR_WAKEUP() is called
        // CFRUNLOOP_WAKEUP_FOR_WAKEUP() does nothing
        else if (livePort == rl->_wakeUpPort)
        {
            CFRUNLOOP_WAKEUP_FOR_WAKEUP();
            // do nothing on Mac OS
        }
#if USE_DISPATCH_SOURCE_FOR_TIMERS
        // If the wake port is modeQueuePort, the timer woke runloop
        else if(modeQueuePort ! = MACH_PORT_NULL && livePort == modeQueuePort) {// Call CFRUNLOOP_WAKEUP_FOR_TIMER,
            CFRUNLOOP_WAKEUP_FOR_TIMER();
            / / the timer
            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
        // If the wakeup port is mode's _timerPort
        else if(rlm->_timerPort ! = MACH_PORT_NULL && livePort == rlm->_timerPort) { 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
            / / the timer
            if(! __CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {// Re-arm the next timer__CFArmNextTimerInMode(rlm, rl); }}#endif
        // If the wake port is dispatchPort, it is the primary queue that wakes Runloop
        else if (livePort == dispatchPort)
        {
            // Execute CFRUNLOOP_WAKEUP_FOR_DISPATCH()
            CFRUNLOOP_WAKEUP_FOR_DISPATCH();
            / / unlock mode
            __CFRunLoopModeUnlock(rlm);
            / / unlock runloop
            __CFRunLoopUnlock(rl);
            // set TSD with __CFTSDKeyIsInGCDMainQ as key and 6 as value
            _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6.NULL);
            // Perform the main queue task
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
            // set TSD with __CFTSDKeyIsInGCDMainQ as key and 0 as value
            _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0.NULL);
            // Lock runloop
            __CFRunLoopLock(rl);
            // Lock mode
            __CFRunLoopModeLock(rlm);
            // flag that this loop handles the primary queue
            sourceHandledThisLoop = true;
            didDispatchPortLastTime = true;
        }
        else
        {
            // The source1 awakens
            CFRUNLOOP_WAKEUP_FOR_SOURCE();

            // If we received a voucher from this mach_msg, then put a copy of the new voucher into TSD. CFMachPortBoost will look in the TSD for the voucher. By using the value in  the TSD we tie the CFMachPortBoost to this received mach_msg explicitly without a chance for anything in between the two pieces of code to set the voucher again.
            voucher_t previousVoucher = _CFSetTSD(__CFTSDKeyMachMessageHasVoucher, (void *)voucherCopy, os_release);

            // Despite the name, this works for windows handles as well
            // Fetch source1 from the port
            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;
                // Execute the source1z task
                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); }}// Restore the previous voucher
            _CFSetTSD(__CFTSDKeyMachMessageHasVoucher, previousVoucher, os_release);
        }
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
        if(msg && msg ! = (mach_msg_header_t *)msg_buffer)
            free(msg);
#endif
        // Process Blocks, passing runloop and mode
        __CFRunLoopDoBlocks(rl, rlm);

        /** CFRunLoopRunInMode() // Reasons for CFRunLoopRunInMode() to Return enum {kCFRunLoopRunFinished = 1, Loop kCFRunLoopRunStopped = 2, loop kCFRunLoopRunTimedOut = 3, / / loop timeout kCFRunLoopRunHandledSource = 4 / / loop for the home side column mission}; * /
        Exit the do-while loop if the main queue task is executed and stopAfterHandle is true
        if (sourceHandledThisLoop && stopAfterHandle)
        {
            retVal = kCFRunLoopRunHandledSource;
        }
        // If time out, exit the do-while loop
        else if (timeout_context->termTSR < mach_absolute_time())
        {
            retVal = kCFRunLoopRunTimedOut;
        }
        // If runloop has stopped, exit the do-while loop
        else if (__CFRunLoopIsStopped(rl))
        {
            __CFRunLoopUnsetStopped(rl);
            retVal = kCFRunLoopRunStopped;
        }
        // Exit the do-while loop if the _stopped property of mode is true
        else if (rlm->_stopped)
        {
            rlm->_stopped = false;
            retVal = kCFRunLoopRunStopped;
        }
        // If mode is null, exit the do-while loop
        else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode))
        {
            retVal = kCFRunLoopRunFinished;
        }

#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
        voucher_mach_msg_revert(voucherState);
        os_release(voucherCopy);
#endif

    } while (0 == retVal);

    // If the GCD timer is enabled
    if (timeout_timer)
    {
        // Stop the timer and release the resource
        dispatch_source_cancel(timeout_timer);
        dispatch_release(timeout_timer);
    }
    else
    {
        // Release the timeout context structure pointer
        free(timeout_context);
    }

    return retVal;
}
Copy the code

__CFRunLoopRun Simplifies code

static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode)
{
    int32_t retVal = 0;
    do
    {
        // Notify Observers that Timers are about to be processed
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        
        // Notify Observers that they are about to process Sources
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);

        / / processing Blocks
        __CFRunLoopDoBlocks(rl, rlm);

        / / Source0 processing
        if (__CFRunLoopDoSources0(rl, rlm, stopAfterHandle))
        {
            / / processing Blocks
            __CFRunLoopDoBlocks(rl, rlm);
        }

        Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);

        // Check whether Source1 exists
        if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL))
        {
            // If there is Source1, jump to handle_msg
            goto handle_msg;
        }

            
        didDispatchPortLastTime = false;

        // Notify Observers that they are about to hibernate
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
            
        __CFRunLoopSetSleeping(rl);

        CFAbsoluteTime sleepStart = poll ? 0.0 : CFAbsoluteTimeGetCurrent();
        
        do
        {
            if (kCFUseCollectableAllocator)
            {
                // objc_clear_stack(0);
                // <rdar://problem/16393959>
                memset(msg_buffer, 0.sizeof(msg_buffer));
            }
            msg = (mach_msg_header_t *)msg_buffer;

            // Wait for another message to wake up the current thread
            __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);

        // user callouts now OK again
        __CFRunLoopUnsetSleeping(rl);
        
        // Notify Observers to end hibernation
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);

    handle_msg:
        if(Awakened by timer) {/ / deal with timers
            __CFRunLoopDoTimers(rl, rlm, mach_absolute_time());
        }

        else if(awakened by GCD) {// Process the GCD main queue
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
        }
        else
        {
            // Wake up by Source1
            __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
        }

        / / processing Blocks
        __CFRunLoopDoBlocks(rl, rlm);

        if (sourceHandledThisLoop && stopAfterHandle)
        {
            retVal = kCFRunLoopRunHandledSource;
        }
        else if (timeout_context->termTSR < mach_absolute_time())
        {
            retVal = kCFRunLoopRunTimedOut;
        }
        else if (__CFRunLoopIsStopped(rl))
        {
            __CFRunLoopUnsetStopped(rl);
            retVal = kCFRunLoopRunStopped;
        }
        else if (rlm->_stopped)
        {
            rlm->_stopped = false;
            retVal = kCFRunLoopRunStopped;
        }
        else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode))
        {
            retVal = kCFRunLoopRunFinished;
        }

        voucher_mach_msg_revert(voucherState);
        os_release(voucherCopy);

    } while (0 == retVal);

    return retVal;
}
Copy the code

Tasks performed by RunLoop

From __CFRunLoopRun, we know that RunLoop performs the following tasks:

  • __CFRunLoopDoObservers
  • __CFRunLoopDoBlocks
  • __CFRunLoopDoSources0
  • __CFRunLoopDoSource1
  • __CFRunLoopDoTimers
  • __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__

Below, we will analyze one by one:

__CFRunLoopDoObservers

/* rl is locked, rlm is locked on entrance and exit */
static void __CFRunLoopDoObservers() __attribute__((noinline));
static void __CFRunLoopDoObservers(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopActivity activity)
{ /* DOES CALLOUT */
    CHECK_FOR_FORK();

    // Check whether the number of observers corresponding to the passed mode is 0
    CFIndex cnt = rlm->_observers ? CFArrayGetCount(rlm->_observers) : 0;
    if (cnt < 1)
        return;

    /* Fire the observers */
    
    // #define STACK_BUFFER_DECL(T, N, C) T N[C]
    // There are two cases
    // If the number of observers in mode is less than or equal to 1024, then it equals CFRunLoopObserverRef buffer[CNT]
    // If the number of observers in mode is greater than 1024, it is equivalent to CFRunLoopObserverRef buffer[1]
    // So the cache size is allocated by the number of observers
    STACK_BUFFER_DECL(CFRunLoopObserverRef, buffer, (cnt <= 1024)? cnt :1);
    
    
    // If the number of observers is less than 1024, assign the buffer cache obtained in the previous step to collectedObservers
    // If the number of observers is greater than 1024, the malloc function is called to allocate space. The final space size is = number of observers * CFRunLoopObserverRef type memory size
    // We get an array pointer variable collectedObservers of type CFRunLoopObserverRef
    CFRunLoopObserverRef *collectedObservers = (cnt <= 1024)? buffer : (CFRunLoopObserverRef *)malloc(cnt * sizeof(CFRunLoopObserverRef));
    
    CFIndex obs_cnt = 0;
    Observers // loop through all observers in the incoming runloop mode
    for (CFIndex idx = 0; idx < cnt; idx++)
    {
        // Take an observer from the observers collection
        CFRunLoopObserverRef rlo = (CFRunLoopObserverRef)CFArrayGetValueAtIndex(rlm->_observers, idx);
        
        // Enter the logic inside if if the following three conditions are met
        // 1. Check whether the current observer state matches the incoming state by using the and operation
        // 2. Check whether the observer is valid
        /** // Bit 3 in the base reserved bits is used for invalid state in run loop objects CF_INLINE Boolean __CFIsValid(const  void *cf) { return (Boolean)__CFBitfieldGetValue(((const CFRuntimeBase *)cf)->_cfinfo[CF_INFO_BITS], 3, 3); } CF_INLINE void __CFSetValid(void *cf) { __CFBitfieldSetValue(((CFRuntimeBase *)cf)->_cfinfo[CF_INFO_BITS], 3, 3, 1); } CF_INLINE void __CFUnsetValid(void *cf) { __CFBitfieldSetValue(((CFRuntimeBase *)cf)->_cfinfo[CF_INFO_BITS], 3, 3, 0); } * /
        // 3. Check whether the Observer has been fired
        /** Bit 0 of the base reserved bits is used for firing state Bit 1 of the base reserved bits is used for repeats state CF_INLINE Boolean __CFRunLoopObserverIsFiring(CFRunLoopObserverRef rlo) { return (Boolean)__CFBitfieldGetValue(((const CFRuntimeBase *)rlo)->_cfinfo[CF_INFO_BITS], 0, 0); } * /
        
        /** After exploring the second and third steps, you can see that the CFRuntimeBase Bit 0 is used to indicate the startup state, Bit 1 is used to indicate the repeated state, and Bit 3 is used to indicate the available state of the runloop object */
        
        if (0! = (rlo->_activities & activity) && __CFIsValid(rlo) && ! __CFRunLoopObserverIsFiring(rlo)) {Retain the Observer and store it into the collectedObservers collection
            // The CoreFoundation framework requires manual MRC, that is, CFRetain is called to connect to the received object. At the same time, a paired CFRelease operation is required to prevent memory leakscollectedObservers[obs_cnt++] = (CFRunLoopObserverRef)CFRetain(rlo); }}// Unlocks the incoming mode
    // The underlying implementation is a mutex
    /** CF_INLINE void __CFRunLoopModeUnlock(CFRunLoopModeRef rlm) { //CFLog(6, CFSTR("__CFRunLoopModeLock unlocking %p"), rlm); pthread_mutex_unlock(&(rlm->_lock)); } * /
    __CFRunLoopModeUnlock(rlm);
    // Unlocks the incoming runloop
    // The underlying implementation is a mutex
    /** CF_INLINE void __CFRunLoopUnlock(CFRunLoopRef rl) { // CFLog(6, CFSTR("__CFRunLoopLock unlocking %p"), rl); pthread_mutex_unlock(&(((CFRunLoopRef)rl)->_lock)); } * /
    __CFRunLoopUnlock(rl);
    // Iterate according to the variable obs_cnt, which is the number of valid observers, inside the for loop above
    for (CFIndex idx = 0; idx < obs_cnt; idx++)
    {
        // Retrieve an observer
        CFRunLoopObserverRef rlo = collectedObservers[idx];
        
        // Lock the observer
        // The underlying implementation is a mutex
        /** CF_INLINE void __CFRunLoopObserverLock(CFRunLoopObserverRef rlo) { pthread_mutex_lock(&(rlo->_lock)); // CFLog(6, CFSTR("__CFRunLoopObserverLock locked %p"), rlo); } * /
        __CFRunLoopObserverLock(rlo);
        
        // Check again if the Observer is invalid
        if (__CFIsValid(rlo))
        {
            // Retrieve the repeated state from the observer, then invert it and assign it to the Boolean variable doInvalidate
            // If the observer repeat is true, then doInvalidate is false.
            // If observer repeat is false, doInvalidate is true,
            / / is called CFRunLoopObserverInvalidate to the observer from the subordinate runloop, then the observer is releasedBoolean doInvalidate = ! __CFRunLoopObserverRepeats(rlo);// Set the observer runtimeBase bit 0 to 1, indicating that the observer has been started
            /** CF_INLINE void __CFRunLoopObserverSetFiring(CFRunLoopObserverRef rlo) { __CFBitfieldSetValue(((CFRuntimeBase *)rlo)->_cfinfo[CF_INFO_BITS], 0, 0, 1); } * /
            __CFRunLoopObserverSetFiring(rlo);
            
            // Unlock observer
            __CFRunLoopObserverUnlock(rlo);
            
            // Call the __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ function, passing the observer's corresponding callout, and the observer itself, The current status of runloop and observer context information
            __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(rlo->_callout, rlo, activity, rlo->_context.info);
            
            if (doInvalidate)
            {
                CFRunLoopObserverInvalidate(rlo);
            }
            // Remove firing status of the Observer
            __CFRunLoopObserverUnsetFiring(rlo);
        }
        else
        {
            // Note The Observer is invalid and can be unlocked
            __CFRunLoopObserverUnlock(rlo);
        }
        // Since the Observer has either triggered a callback or has failed to do anything, it is necessary to release the Observer and start the next loop
        CFRelease(rlo);
    }
    // Lock runloop
    __CFRunLoopLock(rl);
    // Lock mode
    __CFRunLoopModeLock(rlm);

    // If the set is not equal to buffer, it is initialized by malloc and free is required
    if(collectedObservers ! = buffer)free(collectedObservers);
}
Copy the code

We can see that the __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ is finally executed, and we go inside it:

static void __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__() __attribute__((noinline));
static void __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(CFRunLoopObserverCallBack func, CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
    if (func)
    {
        func(observer, activity, info);
    }
    asm __volatile__(""); // thwart tail-call optimization
}
Copy the code

__CFRunLoopDoBlocks

/** Matches mode from runloop's _block_item list, and executes the block task */
static Boolean __CFRunLoopDoBlocks(CFRunLoopRef rl, CFRunLoopModeRef rlm)
{ // Call with rl and rlm locked
    Struct _block_item *_blocks_head; Struct _block_item *_blocks_tail; Struct _block_item {struct _block_item *_next; // next _block_item CFTypeRef _mode; // CFString or CFSet // mode can be a string or a set void (^_block)(void); // block }; So the underlying runloop stores the _block_item structure pointer object */ as a single linked list
    // If the block header pointer is null, the current runloop has no blocks to execute and returns false
    if(! rl->_blocks_head)return false;
    // If mode is empty or the mode name is empty, false is also returned
    if(! rlm || ! rlm->_name)return false;

    // Whether a block is executed
    Boolean did = false;
    // Retrieve the block header pointer
    struct _block_item *head = rl- > _blocks_head;
    // Fetch the end pointer to the block list
    struct _block_item *tail = rl- > _blocks_tail;
    
    // Empty the block header pointer on the runloop
    rl->_blocks_head = NULL;
    // Void the end pointer of the block list on runloop
    rl->_blocks_tail = NULL;
    // Get commonModes of runloop
    CFSetRef commonModes = rl->_commonModes;
    // Get the name of the mode passed in
    CFStringRef curMode = rlm->_name;
    // Unlock mode
    __CFRunLoopModeUnlock(rlm);
    // Unlock runloop
    __CFRunLoopUnlock(rl);
    
    // Initialize a null pointer
    struct _block_item *prev = NULL;
    // Initialize a pointer to the head of the list
    struct _block_item *item = head;
    // Iterate through the block list from the obtained block header pointer
    while (item)
    {
        // The current _block_item structure variable
        struct _block_item *curr = item;
        // Move the pointer forward one position for the next loop
        item = item->_next;
        // Initialize the doit Boolean value
        Boolean doit = false;
        // Determine whether the mode of _block_item is a string or a set, that is, whether the block_item corresponds to one mode or multiple modes
        if (CFStringGetTypeID() == CFGetTypeID(curr->_mode))
        {
            // indicates a mode
            // Determine the two cases
            // 1. Check whether the mode passed in is equal to the mode inside block_item
            // 2. Check whether the mode inside block_item is kCFRunLoopCommonModes, and check whether the commonModes are passed in
            // If one of the above conditions is true, doit is false otherwise
            doit = CFEqual(curr->_mode, curMode) || (CFEqual(curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(commonModes, curMode));
        }
        else
        {
            // Indicates multiple modes
            // Also judge two cases
            // 1. Check whether the mode set inside _block_item contains the mode passed in
            // 2. Check whether the mode set inside _block_item contains kCFRunLoopCommonModes and whether there are any passed modes in the commonModes
            // If one of the above conditions is true, doit is false otherwise
            doit = CFSetContainsValue((CFSetRef)curr->_mode, curMode) || (CFSetContainsValue((CFSetRef)curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(commonModes, curMode));
        }
        As long as the _block_item node satisfies the mode matching relation, it executes its internal block task
        
        // If doit is false, the block task is not _block_item, so prev points to _block_item
        // Since item = item->_next has been done previously, item already refers to the next _block_item
        if(! doit) prev = curr;// If doit is true, the current _block_item is used to execute the block task
        if (doit)
        {
            // This can be divided into three cases
            The destination node is any node between the beginning and the end
            // The target node is the head node
            // The destination node is the last node
            // The target node needs to be deleted because the block task has been completed
            
            // Check whether the prev pointer is null. If not, point _next of the prev pointer to item. Note that item is the _block_item to traverse next time
            // The function here is case 1
            if (prev)
                prev->_next = item;
            
            // There are two boundary values, namely, the task to execute the block is the head node or the tail node
            
            // Determine if the _block_time currently being traversed is equal to the head pointer
            // Since the head points to the head of the list before entering the while loop, it can enter the condition of the logic inside the if
            // The head is the node where the block task is to be executed. At this point, the head pointer points to the next node. At the end of the loop, there is a judgment,
            // If head is not empty, then rl->_blocks_head is redirected to head, that is, the node that performed the block and happened to be the head node is deleted.
            
            // The function here is case two
            if (curr == head)
                head = item;
            
            // Determine if the _block_time being traversed is equal to the tail pointer
            // Since the tail pointer is the last node of the list pointed to before the while loop, it can enter the logic condition inside if
            // The tail pointer points to the same node as the prev pointer.
            // The prev pointer points to the preceding node of the tail node, which is the last node of the block
            
            // The function here is case 3
            if (curr == tail)
                tail = prev;
            
            // Retrieve the block in _block_item that is currently being traversed
            void (^block)(void) = curr->_block;
            // Releases the mode for _block_item if it is currently being traversed
            CFRelease(curr->_mode);
            // Release the _block_item that is currently being traversed
            free(curr);
            
            // Judge doIT again
            if (doit)
            {
                // Pass the block to execute the task
                __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
                // Set did to true
                did = true;
            }
            / / release the block
            Block_release(block); // do this before relocking to prevent deadlocks where some yahoo wants to run the run loop reentrantly from their dealloc
            // Releasing blcok before relocking prevents programmers from rerunning runloop in the dealloc method and causing deadlocks}}// Lock runloop
    __CFRunLoopLock(rl);
    // Lock mode
    __CFRunLoopModeLock(rlm);
    // If the head pointer is not null, the if logic is also required
    if (head)
    {
        Rl -> _blockS_head = tail->_next = NULL
        tail->_next = rl->_blocks_head;
        // make rl->_blocks_head point to the node to which the head pointer points
        Rl ->_blocks_head = rl->_blocks_head = rl->_blocks_head
        // If the block to execute is found in the while loop above, head points to
        rl->_blocks_head = head;
        // The rl->_blocks_tail pointer is null, so the if logic must be advanced
        Rl ->_blocks_tail points to the node to which the tail pointer points
        if(! rl->_blocks_tail) rl->_blocks_tail = tail; }return did;
}
Copy the code

You can see that the actual callback is performed by the CFRUNLOOP_IS_CALLING_OUT_TO_A__BLOCK function, which has the following internal implementation

static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__() __attribute__((noinline));
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(void (^block)(void))
{
    if (block)
    {
        block();
    }
    asm __volatile__(""); // thwart tail-call optimization
}
Copy the code

__CFRunLoopDoSources0

/* rl is locked, rlm is locked on entrance and exit */
static Boolean __CFRunLoopDoSources0(CFRunLoopRef rl, CFRunLoopModeRef rlm, Boolean stopAfterHandle) __attribute__((noinline));
static Boolean __CFRunLoopDoSources0(CFRunLoopRef rl, CFRunLoopModeRef rlm, Boolean stopAfterHandle)
{ /* DOES CALLOUT */
    CHECK_FOR_FORK();
    // Initialize sources and empty it
    CFTypeRef sources = NULL;
    // initialize the return value sourceHandled and set it to false to indicate whether source0 has been handled
    Boolean sourceHandled = false;

    /* Fire the version 0 sources */
    if (NULL! = rlm->_sources0 &&0 < CFSetGetCount(rlm->_sources0))
    {
        // Check that mode _source0 is not empty and has a size greater than 0
        // CFSetApplyFunction (Calls a function once for each value in the set.)
        // This function executes the passed pointer to each element in the passed set
        // The first argument is set
        // The second argument is a function pointer to be executed once on each element of set
        // The third argument is passed as the second argument to the function pointer
        CFSetApplyFunction(rlm->_sources0, (__CFRunLoopCollectSources0), &sources);
    }
    Source0 = source0 = source0
    if (NULL! = sources) {// Unlock mode
        __CFRunLoopModeUnlock(rlm);
        // Unlock runloop
        __CFRunLoopUnlock(rl);
        // Sources can be a runloop source or an array of runloop sources
        // sources is either a single (retained) CFRunLoopSourceRef or an array of (retained) CFRunLoopSourceRef
        if (CFGetTypeID(sources) == CFRunLoopSourceGetTypeID())
        {
            Sources is a single runloop source
            // The type is strong
            CFRunLoopSourceRef rls = (CFRunLoopSourceRef)sources;
            // Lock RLS
            __CFRunLoopSourceLock(rls);
            if (__CFRunLoopSourceIsSignaled(rls))
            {
                Signaled RLS is signaled
                Signaled the signs off
                __CFRunLoopSourceUnsetSignaled(rls);
                // Determine whether the RLS is valid
                if (__CFIsValid(rls))
                {
                    // Unlock the RLS
                    __CFRunLoopSourceUnlock(rls);
                    // Execute the source0 callback
                    /** static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__() __attribute__((noinline)); static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(void (*perform)(void *), void *info) { if (perform) { perform(info); } asm __volatile__(""); // thwart tail-call optimization } */
                    __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(rls->_context.version0.perform, rls->_context.version0.info);
                    CHECK_FOR_FORK();
                    // Set the result of processing source0 to true
                    sourceHandled = true;
                }
                else{ __CFRunLoopSourceUnlock(rls); }}else
            {
                Signaled RLS is not signaled__CFRunLoopSourceUnlock(rls); }}else
        {
            // sources is an array
            // Fetch the number of source0
            CFIndex cnt = CFArrayGetCount((CFArrayRef)sources);
            // Sort sources, ascending by _order
            /** static CFComparisonResult __CFRunLoopSourceComparator(const void *val1, const void *val2, void *context) { CFRunLoopSourceRef o1 = (CFRunLoopSourceRef)val1; CFRunLoopSourceRef o2 = (CFRunLoopSourceRef)val2; if (o1->_order < o2->_order) return kCFCompareLessThan; if (o2->_order < o1->_order) return kCFCompareGreaterThan; return kCFCompareEqualTo; } * /
            CFArraySortValues((CFMutableArrayRef)sources, CFRangeMake(0, cnt), (__CFRunLoopSourceComparator), NULL);
            // Iterate through the sources array
            for (CFIndex idx = 0; idx < cnt; idx++)
            {
                // Fetch a source0 RLS
                CFRunLoopSourceRef rls = (CFRunLoopSourceRef)CFArrayGetValueAtIndex((CFArrayRef)sources, idx);
                // Lock RLS
                __CFRunLoopSourceLock(rls);
                if (__CFRunLoopSourceIsSignaled(rls))
                {
                    Signaled RLS is signaled
                    Signaled the signs off
                    __CFRunLoopSourceUnsetSignaled(rls);
                    // Determine whether the RLS is valid
                    if (__CFIsValid(rls))
                    {
                        // Unlock the RLS
                        __CFRunLoopSourceUnlock(rls);
                        // Execute the source0 callback
                        /** static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__() __attribute__((noinline)); static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(void (*perform)(void *), void *info) { if (perform) { perform(info); } asm __volatile__(""); // thwart tail-call optimization } */
                        __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(rls->_context.version0.perform, rls->_context.version0.info);
                        CHECK_FOR_FORK();
                        // Set the result of processing source0 to true
                        sourceHandled = true;
                    }
                    else{ __CFRunLoopSourceUnlock(rls); }}else
                {
                    Signaled RLS is not signaled
                    __CFRunLoopSourceUnlock(rls);
                }
                // If a source0 process is complete and stopAfterHandle is true, the loop is broken
                if (stopAfterHandle && sourceHandled)
                {
                    break; }}}/ / release sources
        CFRelease(sources);
        // Lock runloop
        __CFRunLoopLock(rl);
        // Unlock mode
        __CFRunLoopModeLock(rlm);
    }
    // returns the result of processing source0
    return sourceHandled;
}
Copy the code

The actual callback is the __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ function, which is internally implemented as follows

static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__() __attribute__((noinline));
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(void (*perform)(void *), void *info)
{
    if (perform)
    {
        perform(info);
    }
    asm __volatile__(""); // thwart tail-call optimization
}
Copy the code

__CFRunLoopDoSource1

// msg, size and reply are unused on Windows
static Boolean __CFRunLoopDoSource1() __attribute__((noinline));
static Boolean __CFRunLoopDoSource1(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopSourceRef rls
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
                                    ,
                                    mach_msg_header_t *msg, CFIndex size, mach_msg_header_t **reply
#endif
)
{ /* DOES CALLOUT */
    CHECK_FOR_FORK();
    // Initialize whether the source1 result was successfully processed
    Boolean sourceHandled = false;

    /* Fire a version 1 source */
    // Retian source1
    CFRetain(rls);
    // Unlock mode
    __CFRunLoopModeUnlock(rlm);
    // Unlock runloop
    __CFRunLoopUnlock(rl);
    // lock source1
    __CFRunLoopSourceLock(rls);
    // If source1 is valid
    if (__CFIsValid(rls))
    {
        /** // Bit 1 of the base reserved bits is used for signalled state CF_INLINE Boolean __CFRunLoopSourceIsSignaled(CFRunLoopSourceRef rls) { return (Boolean)__CFBitfieldGetValue(rls->_bits, 1, 1); } CF_INLINE void __CFRunLoopSourceSetSignaled(CFRunLoopSourceRef rls) { __CFBitfieldSetValue(rls->_bits, 1, 1, 1); } CF_INLINE void __CFRunLoopSourceUnsetSignaled(CFRunLoopSourceRef rls) { __CFBitfieldSetValue(rls->_bits, 1, 1, 0); } * /
        // Set the source1 status to inactive
        __CFRunLoopSourceUnsetSignaled(rls);
        / / unlock source1
        __CFRunLoopSourceUnlock(rls);
        __CFRunLoopDebugInfoForRunLoopSource(rls);
        // Perform the source1 callback
        __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(rls->_context.version1.perform,
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
                                                                   msg, size, reply,
#endif
                                                                   rls->_context.version1.info);
        CHECK_FOR_FORK();
        sourceHandled = true;
    }
    else
    {
        // Write to log, source1 is invalid
        if (_LogCFRunLoop)
        {
            CFLog(kCFLogLevelDebug, CFSTR("%p (%s) __CFRunLoopDoSource1 rls %p is invalid"), CFRunLoopGetCurrent(), *_CFGetProgname(), rls);
        }
        // Unlock source1
        __CFRunLoopSourceUnlock(rls);
    }
    / / release source1
    CFRelease(rls);
    // Lock runloop
    __CFRunLoopLock(rl);
    // Lock mode
    __CFRunLoopModeLock(rlm);
    return sourceHandled;
}
Copy the code

The real callback function is __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__, internally implemented as follows:


static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__() __attribute__((noinline));
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
    void *(*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info),
    mach_msg_header_t *msg, CFIndex size, mach_msg_header_t **reply,
#else
    void (*perform)(void *),
#endif
    void *info)
{
    if (perform)
    {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
        *reply = perform(msg, size, kCFAllocatorSystemDefault, info);
#else
        perform(info);
#endif
    }
    asm __volatile__(""); // thwart tail-call optimization
}
Copy the code

__CFRunLoopDoTimers

// rl and rlm are locked on entry and exit
static Boolean __CFRunLoopDoTimers(CFRunLoopRef rl, CFRunLoopModeRef rlm, uint64_t limitTSR)
{ /* DOES CALLOUT */
    // Result of initializing whether the timer was successfully processed
    Boolean timerHandled = false;
    // Initializes an empty timers collection
    CFMutableArrayRef timers = NULL;
    // Iterate over the mode _ITEMrs timer array
    for (CFIndex idx = 0, cnt = rlm->_timers ? CFArrayGetCount(rlm->_timers) : 0; idx < cnt; idx++)
    {
        / / remove the timer
        CFRunLoopTimerRef rlt = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(rlm->_timers, idx);
        
        // If timer is valid and has not yet been triggered
        if(__CFIsValid(rlt) && ! __CFRunLoopTimerIsFiring(rlt)) {// And the timer's trigger time is less than or equal to the specified time
            if (rlt->_fireTSR <= limitTSR)
            {
                // Initializes the timers collection
                if(! timers) timers = CFArrayCreateMutable(kCFAllocatorSystemDefault,0, &kCFTypeArrayCallBacks);
                // Add timer to timers collectionCFArrayAppendValue(timers, rlt); }}}// Iterate over the timers collection, which is filled with timers that need to work
    for (CFIndex idx = 0, cnt = timers ? CFArrayGetCount(timers) : 0; idx < cnt; idx++)
    {
        / / remove the timer
        CFRunLoopTimerRef rlt = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(timers, idx);
        // Perform the timer callback
        Boolean did = __CFRunLoopDoTimer(rl, rlm, rlt);
        timerHandled = timerHandled || did;
    }
    // Release the timers collection
    if (timers)
        CFRelease(timers);
    return timerHandled;
}

static Boolean __CFRunLoopDoTimer(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopTimerRef rlt)
{ /* DOES CALLOUT */
    // Result of successfully initializing timer
    Boolean timerHandled = false;
    uint64_t oldFireTSR = 0;

    /* Fire a timer */
    / / for the timer to retain
    CFRetain(rlt);
    // Lock the timer
    __CFRunLoopTimerLock(rlt);

    // If the timer is valid, the timer trigger time is not reached, the timer is not triggered, and the timer's runloop is the passed runloop, enter the if logic
    if(__CFIsValid(rlt) && rlt->_fireTSR <= mach_absolute_time() && ! __CFRunLoopTimerIsFiring(rlt) && rlt->_runLoop == rl) {// Initializes the empty context_info
        void *context_info = NULL;
        // Initializes the empty context_release function pointer
        void (*context_release)(const void *) = NULL;
        // If the retain function pointer to the timer context property is not empty, enter the if logic
        if (rlt->_context.retain)
        {
            // Assign info retain to context_info
            context_info = (void *)rlt->_context.retain(rlt->_context.info);
            // Assign the timer's release pointer to context_release
            context_release = rlt->_context.release;
        }
        else
        {
            // Retrieve the timer's info
            context_info = rlt->_context.info;
        }
        // Whether the interval of the timer is 0
        Boolean doInvalidate = (0.0 == rlt->_interval);
        // Set the bits of the timer to 1
        __CFRunLoopTimerSetFiring(rlt);
        // Just in case the next timer has exactly the same deadlines as this one, we reset these values so that the arm next timer code can correctly find the next timer in the list and arm the underlying timer.
        // Just in case a timer has exactly the same expiration date as that timer, we reset these values so that the enable next timer code correctly finds the next timer in the list and enables the base timer.
        rlm->_timerSoftDeadline = UINT64_MAX;
        rlm->_timerHardDeadline = UINT64_MAX;
        / / unlock the timer
        __CFRunLoopTimerUnlock(rlt);
        // Trigger the lock on timer
        __CFRunLoopTimerFireTSRLock();
        / / remove _fireTSR
        oldFireTSR = rlt->_fireTSR;
        // Trigger unlock for timer
        __CFRunLoopTimerFireTSRUnlock();
        
        // Assemble the next timer
        __CFArmNextTimerInMode(rlm, rl);

        // Unlock mode
        __CFRunLoopModeUnlock(rlm);
        // Unlock runloop
        __CFRunLoopUnlock(rl);
        // Perform the real timer callback
        __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(rlt->_callout, rlt, context_info);
        CHECK_FOR_FORK();
        // If the timer runs only once
        if (doInvalidate)
        {
            // Make timer invalid
            CFRunLoopTimerInvalidate(rlt); /* DOES CALLOUT */
        }
        // If the context_release pointer is not null, context_info is released
        if (context_release)
        {
            context_release(context_info);
        }
        // Lock runloop
        __CFRunLoopLock(rl);
        // Unlock mode
        __CFRunLoopModeLock(rlm);
        // Lock the timer
        __CFRunLoopTimerLock(rlt);
        // Set the result of processing timer to true
        timerHandled = true;
        // Set the bits of the timer to 0
        __CFRunLoopTimerUnsetFiring(rlt);
    }
    // If the timer is valid and the timer is processed successfully
    if (__CFIsValid(rlt) && timerHandled)
    {
        /* This is just a little bit tricky: we want to support calling * CFRunLoopTimerSetNextFireDate() from within the callout and * honor that new time here if it is a later date, otherwise * it is completely ignored. */
        // If the old trigger time is less than the current trigger time
        if (oldFireTSR < rlt->_fireTSR)
        {
            /* Next fire TSR was set, and set to a date after the previous * fire date, so we honor it. */
            __CFRunLoopTimerUnlock(rlt);
            // The timer was adjusted and repositioned, during the
            // callout, but if it was still the min timer, it was
            // skipped because it was firing. Need to redo the
            // min timer calculation in case rlt should now be that
            // timer instead of whatever was chosen.
            __CFArmNextTimerInMode(rlm, rl);
        }
        else
        {
            uint64_t nextFireTSR = 0LL;
            uint64_t intervalTSR = 0LL;
            if (rlt->_interval <= 0.0) {}else if (TIMER_INTERVAL_LIMIT < rlt->_interval)
            {
                intervalTSR = __CFTimeIntervalToTSR(TIMER_INTERVAL_LIMIT);
            }
            else
            {
                intervalTSR = __CFTimeIntervalToTSR(rlt->_interval);
            }
            if (LLONG_MAX - intervalTSR <= oldFireTSR)
            {
                nextFireTSR = LLONG_MAX;
            }
            else
            {
                if (intervalTSR == 0)
                {
                    // 15304159: Make sure we don't accidentally loop forever here
                    CRSetCrashLogMessage("A CFRunLoopTimer with an interval of 0 is set to repeat");
                    HALT;
                }
                uint64_t currentTSR = mach_absolute_time();
                nextFireTSR = oldFireTSR;
                while (nextFireTSR <= currentTSR)
                {
                    nextFireTSR += intervalTSR;
                }
            }
            CFRunLoopRef rlt_rl = rlt->_runLoop;
            if (rlt_rl)
            {
                CFRetain(rlt_rl);
                CFIndex cnt = CFSetGetCount(rlt->_rlModes);
                STACK_BUFFER_DECL(CFTypeRef, modes, cnt);
                CFSetGetValues(rlt->_rlModes, (const void **)modes);
                // To avoid A->B, B->A lock ordering issues when coming up
                // towards the run loop from a source, the timer has to be
                // unlocked, which means we have to protect from object
                // invalidation, although that's somewhat expensive.
                for (CFIndex idx = 0; idx < cnt; idx++)
                {
                    CFRetain(modes[idx]);
                }
                __CFRunLoopTimerUnlock(rlt);
                for (CFIndex idx = 0; idx < cnt; idx++)
                {
                    CFStringRef name = (CFStringRef)modes[idx];
                    modes[idx] = (CFTypeRef)__CFRunLoopFindMode(rlt_rl, name, false);
                    CFRelease(name);
                }
                __CFRunLoopTimerFireTSRLock();
                rlt->_fireTSR = nextFireTSR;
                rlt->_nextFireDate = CFAbsoluteTimeGetCurrent() + __CFTimeIntervalUntilTSR(nextFireTSR);
                for (CFIndex idx = 0; idx < cnt; idx++)
                {
                    CFRunLoopModeRef rlm = (CFRunLoopModeRef)modes[idx];
                    if (rlm)
                    {
                        __CFRepositionTimerInMode(rlm, rlt, true);
                    }
                }
                __CFRunLoopTimerFireTSRUnlock();
                for (CFIndex idx = 0; idx < cnt; idx++)
                {
                    __CFRunLoopModeUnlock((CFRunLoopModeRef)modes[idx]);
                }
                CFRelease(rlt_rl);
            }
            else{ __CFRunLoopTimerUnlock(rlt); __CFRunLoopTimerFireTSRLock(); rlt->_fireTSR = nextFireTSR; rlt->_nextFireDate = CFAbsoluteTimeGetCurrent() + __CFTimeIntervalUntilTSR(nextFireTSR); __CFRunLoopTimerFireTSRUnlock(); }}}else
    {
        / / unlock the timer
        __CFRunLoopTimerUnlock(rlt);
    }
    // Release timer
    CFRelease(rlt);
    return timerHandled;
}
Copy the code

The real callback is the CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION function, which has the following internal implementation:

static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__() __attribute__((noinline));
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(CFRunLoopTimerCallBack func, CFRunLoopTimerRef timer, void *info)
{
    if (func)
    {
        func(timer, info);
    }
    asm __volatile__(""); // thwart tail-call optimization
}
Copy the code

CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE

static void __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__() __attribute__((noinline));
static void __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(void *msg)
{
    _dispatch_main_queue_callback_4CF(msg);
    asm __volatile__(""); // thwart tail-call optimization
}
Copy the code

conclusion

The resources

Understand Runloop-ibireme in depth

Decrypt runloop-mrpeak

The Core Foundation,