First, pick up the content that was not put down due to the limitation of the number of words in the previous chapter.

__CFRunLoopFindMode

The __CFRunLoopFindMode function finds the corresponding CFRunLoopModeRef from the _modes of rL based on modeName, locks it and returns. If not found and create is true, create a __CFRunLoopMode and add it to the _modes of rL, then lock and return. If create is false, return NULL.

static CFRunLoopModeRef __CFRunLoopFindMode(CFRunLoopRef rl, CFStringRef modeName, Boolean create) {
    // Used to check whether a given process is forked
    CHECK_FOR_FORK(a);Struct __CFRunLoopMode struct __CFRunLoopMode
    CFRunLoopModeRef rlm;
    
    // create an instance of struct __CFRunLoopMode,
    // call memset to set all SRLM memory space to 0.
    struct __CFRunLoopMode srlm;
    memset(&srlm, 0.sizeof(srlm));
    
    // __kCFRunLoopModeTypeID now represents the CFRunLoopMode class. The actual value is the index of the Run loop mode class in __CFRuntimeClassTable.
    
    // CfrunloopCreate calls CFRunLoopGetTypeID(),
    Run loop (CFRunLoop) and Run loop mode (CFRunLoopMode);
    // __kCFRunLoopModeTypeID = _CFRuntimeRegisterClass(&__CFRunLoopModeClass), __kCFRunLoopModeTypeID is then the index of the RunLoopMode class in the global class table.
    // (__CFRunLoopModeClass can be understood as a static global "class object" (the actual value is one). The _CFRuntimeRegisterClass function puts it into a global __CFRuntimeClassTable.)

    // SRLM itself is empty memory, now equivalent to setting SRLM as an object of the Run Loop Mode class.
    CFRuntimeBase _cfinfo; SRLM contains the run loop mode class information.
    _CFRuntimeSetInstanceTypeIDAndIsa(&srlm, __kCFRunLoopModeTypeID);
    
    // Set SRLM's mode name to the input parameter modeName
    srlm._name = modeName;
    
    // Select CFRunLoopModeRef from rl->_modes hash table & SRLM CFRunLoopModeRef
    rlm = (CFRunLoopModeRef)CFSetGetValue(rl->_modes, &srlm);
    
    // If found, lock and return RLM.
    if (NULL! = rlm) { __CFRunLoopModeLock(rlm);return rlm;
    }
    
    // If it is not found and the create value is false, NULL is returned.
    if(! create) {return NULL;
    }
    
    // Create a CFRunLoopMode instance and return its address
    rlm = (CFRunLoopModeRef)_CFRuntimeCreateInstance(kCFAllocatorSystemDefault, __kCFRunLoopModeTypeID, sizeof(struct __CFRunLoopMode) - sizeof(CFRuntimeBase), NULL);
    
    // If RLM creation fails, NULL is returned
    if (NULL == rlm) {
        return NULL;
    }
    
    // initialize RLM's pthread_mutex_t _lock to a mutex recursive lock.
    // (PTHREAD_MUTEX_RECURSIVE is used internally by __CFRunLoopLockInit to indicate a recursive lock, allowing the same thread to lock the same lock multiple times and requiring a corresponding number of unlock operations)
    __CFRunLoopLockInit(&rlm->_lock);
    
    // Initialize _name
    rlm->_name = CFStringCreateCopy(kCFAllocatorSystemDefault, modeName);
    
    // The following is an initial assignment of a set of member variables
    rlm->_stopped = false;
    rlm->_portToV1SourceMap = NULL;
    
    // _sources0, _sources1, _observers, and _timers are all in empty initial state
    rlm->_sources0 = NULL;
    rlm->_sources1 = NULL;
    rlm->_observers = NULL;
    rlm->_timers = NULL;
    
    rlm->_observerMask = 0;
    rlm->_portSet = __CFPortSetAllocate(); // CFSet requests space initialization
    rlm->_timerSoftDeadline = UINT64_MAX;
    rlm->_timerHardDeadline = UINT64_MAX;
    
    // ret is a temporary variable. The initial value is KERN_SUCCESS, which represents the result of adding port to RLM ->_portSet.
    // If add fails, CRASH,
    kern_return_t ret = KERN_SUCCESS;
    
#if USE_DISPATCH_SOURCE_FOR_TIMERS
    // For macOS, use dispatch_source to construct timer
    
    // _timerFired is set to false first and then to true when the timer's callback is executed
    rlm->_timerFired = false;
    
    / / the queue
    rlm->_queue = _dispatch_runloop_root_queue_create_4CF("Run Loop Mode Queue".0);
    
    // Build queuePort with _queue of mode
    mach_port_t queuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue);
    
    // If queuePort is NULL, crash. (Unable to create run loop mode queue port.)
    if (queuePort == MACH_PORT_NULL) CRASH("*** Unable to create run loop mode queue port. (%d) ***".- 1);
    
    // DISPATCH_SOURCE_TYPE_TIMER is used to construct the dispatch_source type, indicating a timer
    rlm->_timerSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0.0, rlm->_queue);
    
    // Here we use a __block pointer variable to change the _timerFired value inside the block below. (If only the value is changed here, feel the pointer is enough, can not use __block modifier)
    // This block is executed when the _timerSource is called back.
    __block Boolean *timerFiredPointer = &(rlm->_timerFired);
    dispatch_source_set_event_handler(rlm->_timerSource, ^{
        *timerFiredPointer = true;
    });
    
    // Set timer to far out there. The unique leeway makes this timer easy to spot in debug output.
    // Set the timer at a distance. Unique leeway makes the timer easy to find debugging output. (DISPATCH_TIME_FOREVER, DISPATCH_TIME_FOREVER interval)
    _dispatch_source_set_runloop_timer_4CF(rlm->_timerSource, DISPATCH_TIME_FOREVER, DISPATCH_TIME_FOREVER, 321);
    / / start
    dispatch_resume(rlm->_timerSource);
    
    // Add queuePort to RLM's _portSet.
    ret = __CFPortSetInsert(queuePort, rlm->_portSet);
    // If add fails, crash. (Unable to insert timer port into port set.)
    if(KERN_SUCCESS ! = ret)CRASH("*** Unable to insert timer port into port set. (%d) ***", ret);
#endif

#if USE_MK_TIMER_TOO
    // mk constructs the timer
    
    // Build the timer port
    rlm->_timerPort = mk_timer_create(a);// Add RLM's _timerPort to RLM's _portSet as well.
    ret = __CFPortSetInsert(rlm->_timerPort, rlm->_portSet);
    // If add fails, crash. (Unable to insert timer port into port set.)
    if(KERN_SUCCESS ! = ret)CRASH("*** Unable to insert timer port into port set. (%d) ***", ret);
#endif
    
    // Add rL's _wakeUpPort to RLM's _portSet.
    // The _wakeUpPort of the run loop is inserted into the _portSet of all modes.
    ret = __CFPortSetInsert(rl->_wakeUpPort, rlm->_portSet);
    // If add fails, crash. (Unable to insert the wake port into the port set.)
    if(KERN_SUCCESS ! = ret)CRASH("*** Unable to insert wake up port into port set. (%d) ***", ret);
    
#if DEPLOYMENT_TARGET_WINDOWS
    rlm->_msgQMask = 0;
    rlm->_msgPump = NULL;
#endif

    // Add RLM to the _modes of RL,
    // (essentially adding RLM to _MODES hash table)
    CFSetAddValue(rl->_modes, rlm);
    
    // Free, RLMS are held by RL ->_modes and will not be destroyed
    CFRelease(rlm);
    
    // Lock and return RLM
    __CFRunLoopModeLock(rlm);    /* return mode locked */
    return rlm;
}
Copy the code

Ret = __CFPortSetInsert(rl->_wakeUpPort, RLM ->_portSet) adds the _wakeUpPort of the Run loop object to the _portSet port set of each Run loop mode object. That is, when a run loop has multiple run loop modes, each run loop mode will have the _wakeUpPort of the run loop.

On macOS, the _timerFired field of run Loop mode is set to true within the timer callback event of the _timerSource timer of run loop Mode, indicating that the timer is fired.

_sources0, _sources1, _observers, _observers, _observers, _observers, _observers, _observers, _observers, _observers, _sources0, _sources1, _observers, _timers We need to add run loop mode item by ourselves, and their corresponding data types in the code layer are respectively: CFRunLoopSourceRef CFRunLoopObserverRef CFRunLoopTimerRef CFRunLoopSourceRef

Let’s start with the content of this article.

CFRunLoopSourceRef (struct __CFRunLoopSource *)

CFRunLoopSourceRef is the event source (input source). It has two member variables version0 and version1 in the _context union. They correspond to source0 and source1, respectively, which we have mentioned several times before.

typedef struct __CFRunLoopSource * CFRunLoopSourceRef;

struct __CFRunLoopSource {
    CFRuntimeBase _base; // All CF "instances" start from this structure
    uint32_t _bits;
    pthread_mutex_t _lock; // Mutex recursive lock
    CFIndex _order; /* immutable */CFMutableBagRef _runLoops; The lower the source priority, the higher the priority.// Run loop set
    union {
        CFRunLoopSourceContext version0; /* immutable, except invalidation */
        CFRunLoopSourceContext1 version1; /* immutable, except invalidation */
    } _context;
};
Copy the code

CFRunLoopSourceContext version0 is used in _context when __cfrunloopSourcecontext represents the data structure of source0. Here is the definition of CFRunLoopSourceContext.

// #if __LLP64__
// typedef unsigned long long CFOptionFlags;
// typedef unsigned long long CFHashCode;
// typedef signed long long CFIndex;
// #else
// typedef unsigned long CFOptionFlags;
// typedef unsigned long CFHashCode;
// typedef signed long CFIndex;
// #endif

typedef struct {
    CFIndex version;
    void * info; // Source information
    const void *(*retain)(const void *info); / / retain functions
    void (*release)(const void *info); / / release function
    CFStringRef (*copyDescription)(const void *info); // Returns a function that describes a string
    Boolean (*equal)(const void *info1, const void *info2); // A function to determine whether the source object is equal
    CFHashCode (*hash)(const void *info); // hash function
    
    CFRunLoopSourceContext = CFRunLoopSourceContext1;
    // The main differences between the two are as follows, and they also represent the different capabilities of source0 and source1.
    
    void (*schedule)(void *info, CFRunLoopRef rl, CFStringRef mode); // The callback that is fired when source0 is added to the run loop (see CFRunLoopAddSource below)
    void (*cancel)(void *info, CFRunLoopRef rl, CFStringRef mode); // The callback that is triggered when source0 is removed from the run loop
    
    // source0 task block to execute, The callback when the source0 event is fired, and finally the __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ function is called to perform(info)
    void (*perform)(void *info);
} CFRunLoopSourceContext;
Copy the code

CFRunLoopSourceContext1 version1 is used in _context when __CFRunLoopSource represents the data structure of source1. Here is the definition of CFRunLoopSourceContext1.

typedef struct {
    CFIndex version;
    void * info; // Source information
    const void *(*retain)(const void *info); / / retain functions
    void (*release)(const void *info); / / release function
    CFStringRef (*copyDescription)(const void *info); // Returns a function that describes a string
    Boolean (*equal)(const void *info1, const void *info2); // A function to determine whether the source object is equal
    CFHashCode (*hash)(const void *info); // hash function
    
    CFRunLoopSourceContext = CFRunLoopSourceContext1 = CFRunLoopSourceContext1
    // The main differences between the two are as follows, and they also represent the different capabilities of source0 and source1.
    
#if(TARGET_OS_MAC && ! (TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) || (TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)
    mach_port_t (*getPort)(void *info); // getPort function pointer to get the mach_port_t object from the function when source is added to the run loop.
    void * (*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info); // Perform refers to what the run loop will do when it is woken up
#else
    void * (*getPort)(void *info);
    void (*perform)(void *info);
#endif
} CFRunLoopSourceContext1;
Copy the code

Above is the data structure associated with __CFRunLoopSource. Now let’s look at the source creation function (first look at the registration of the CFRunLoopSource class).

CFRunLoopSourceGetTypeID

__CFRunLoopSourceClass is an instance of the CFRuntimeClass structure that represents the CFRunLoopSource “class object”.

static const CFRuntimeClass __CFRunLoopSourceClass = {
    _kCFRuntimeScannedObject,
    "CFRunLoopSource".NULL.// init
    NULL.// copy
    __CFRunLoopSourceDeallocate, // Destruct function
    __CFRunLoopSourceEqual, // evaluate the equivalence function
    __CFRunLoopSourceHash, // hash function
    NULL,
    __CFRunLoopSourceCopyDescription // Describe the function
};
Copy the code

The CFRunLoopSourceGetTypeID function registers __CFRunLoopSourceClass in the class table and returns its index in the class table.

CFTypeID CFRunLoopSourceGetTypeID(void) {
    static dispatch_once_t initOnce;
    dispatch_once(&initOnce, ^{ 
        __kCFRunLoopSourceTypeID = _CFRuntimeRegisterClass(&__CFRunLoopSourceClass); 
    });
    return __kCFRunLoopSourceTypeID;
}
Copy the code

CFRunLoopSourceCreate

&mesp; CFRunLoopSourceCreate creates source0 or source1 based on the input context.

CFRunLoopSourceRef CFRunLoopSourceCreate(CFAllocatorRef allocator, CFIndex order, CFRunLoopSourceContext *context) {
    // Used to check whether a given process is forked
    CHECK_FOR_FORK(a);// Local variables
    CFRunLoopSourceRef memory;
    uint32_t size;
    
    // Context cannot be NULL; otherwise, crash will occur
    if (NULL == context) CRASH("*** NULL context value passed to CFRunLoopSourceCreate(). (%d) ***".- 1);
    
    // Calculate the size of the __CFRunLoopSource structure except for the CFRuntimeBase field
    size = sizeof(struct __CFRunLoopSource) - sizeof(CFRuntimeBase);
    
    // Create an instance of __CFRunLoopSource and return its pointer
    memory = (CFRunLoopSourceRef)_CFRuntimeCreateInstance(allocator, CFRunLoopSourceGetTypeID(), size, NULL);
    if (NULL == memory) {
        return NULL;
    }
    
    // Set the _cfinfo field in memory
    __CFSetValid(memory);
    
    // Set the value of _bits field
    __CFRunLoopSourceUnsetSignaled(memory);
    
    // initialize _lock to a mutex recursive lock
    __CFRunLoopLockInit(&memory->_lock);
    
    memory->_bits = 0;
    memory->_order = order; / / the order assignment
    memory->_runLoops = NULL;
    
    size = 0;
    
    Source0 and source1 have different memory sizes depending on the context
    switch (context->version) {
        case 0:
            size = sizeof(CFRunLoopSourceContext);
        break;
        case 1:
            size = sizeof(CFRunLoopSourceContext1);
        break;
    }
    
    // Set the memory context value to context
    objc_memmove_collectable(&memory->_context, context, size);
    
    // If the context's retain function is not NULL, the retain function is invoked on its info
    if (context->retain) {
        memory->_context.version0.info = (void *)context->retain(context->info);
    }
    
    return memory;
}
Copy the code

It’s basically asking for space, and then initializing some fields. Let’s see how source is added to mode (starting with an implementation of the __CFSetValid function).

__CFSetValid

The __CFSetValid function is used to set the _cfinfo value of CFRuntimeBase.

// In CFRunLoopSourceCreate it is __CFSetValid(memory); Such a call
// uint8_t _cfinfo[4];

#defineCF_INFO_BITS (!! (__CF_BIG_ENDIAN__) * 3)// under x86_64 macOS, CF_INFO_BITS is 0

#if defined(__BIG_ENDIAN__) // Big-endian mode
#define __CF_BIG_ENDIAN__ 1
#define __CF_LITTLE_ENDIAN__ 0
#endif

#if defined(__LITTLE_ENDIAN__) // Small - end mode, small - end mode on X86_64 macOS
#define __CF_LITTLE_ENDIAN__ 1
#define __CF_BIG_ENDIAN__ 0
#endif

CF_INLINE void __CFSetValid(void *cf) {
    // x86_64 macOS 下 CF_INFO_BITS 的值是 0
    // __CFBitfieldSetValue(((CFRuntimeBase *)cf)->_cfinfo[0], 3, 3, 1);
    __CFBitfieldSetValue(((CFRuntimeBase *)cf)->_cfinfo[CF_INFO_BITS], 3.3.1);
}
Copy the code

The internal call to __CFSetValid is __CFBitfieldSetValue(((CFRuntimeBase *)cf)->_cfinfo[0], 3, 3, 1); , so let’s move on to the __CFBitfieldSetValue macro definition.

// __CFBitfieldSetValue(((CFRuntimeBase *)cf)->_cfinfo[0], 3, 3, 1)
#define __CFBitfieldSetValue(V, N1, N2, X)   ((V) = ((V) & ~__CFBitfieldMask(N1, N2)) | (((X) << (N2)) & __CFBitfieldMask(N1, N2)))

// Macro conversion is as follows:
// V = ((CFRuntimeBase *)cf)->_cfinfo[0]
// N1 = 3
// N2 = 3
// X = 1

// To make it easier to see, we need to convert V to the left of the equals sign
((V) = ((V) & ~__CFBitfieldMask(3.3)) | (((1) < < (3)) & __CFBitfieldMask(3.3))) / / 1 ⃣ ️

#define __CFBitfieldMask(N1, N2)   ((((UInt32)~0UL) << (31UL - (N1) + (N2))) >> (31UL - N1))
// __CFBitfieldMask(3, 3) 
// ((((UInt32)~0UL) << (31UL - (3) + (3))) >> (31UL - 3))
// ((((UInt32)~0UL) << 31UL) >> (31UL - 3))
0b1000 0000 0000 0000 0000 0000 0000 0000
// 0b0000 0000 0000 0000 0000 0000 0001 0000
// 0x00000010UL

// the value of __CFBitfieldMask(3, 3) is UInt32 0x00000010UL

((V) = ((V) & ~0x00000010UL) | (((1) < < (3)) & 0x00000010UL)) / / 2 ⃣ ️
((V) = ((V) & 0xffffffefUL) | (0x00000008UL & 0x00000010UL)) / / 3 ⃣ ️
((V) = ((V) & 0xffffffefUL) | 0x00000000UL) / / 4 ⃣ ️
// Because the most right side is and 0 to do or can be ignored
((CFRuntimeBase *)cf)->_cfinfo[0] = ((CFRuntimeBase *)cf)->_cfinfo[0] & 0xffffffefUL / / 5 ⃣ ️
_CFRuntimeCreateInstance (z); _cfinfo (z);
// Then the preceding and following bytes are 0, then the lower 8 bits of _cfinfo[0] are 1110 1111
Copy the code

__CFRunLoopSourceUnsetSignaled (memory); __CFBitfieldSetValue(RLS ->_bits, 1, 1, 0); I’m not going to expand it here.

CFRunLoopAddSource

The CFRunLoopAddSource function adds source to mode, and the mode argument is, unsurprisingly, a string.

void CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef rls, CFStringRef modeName) {    /* DOES CALLOUT */
    // Used to check whether a given process is forked
    CHECK_FOR_FORK(a);// If rL is marked as being released, it returns directly.
    if (__CFRunLoopIsDeallocating(rl)) return;
    
    // If RLS is invalid, return.
    if(! __CFIsValid(rls))return;
    
    / / when the context. Version0. The schedule is not NULL, will be set to true, used to mark in the final implementation schedule function function,
    // schedule is the callback function after source0 is added to mode.
    Boolean doVer0Callout = false;
    
    CFRunLoopRef (CFRunLoopRef); // CFRunLoopRef (CFRunLoopRef);
    __CFRunLoopLock(rl);
    
    // If modeName is kcFRunLoopCommonMode, add source to common Mode
    if (modeName == kCFRunLoopCommonModes) {
        // If the _commonModes of rL are not NULL, create a copy.
        CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
        
        // If rL's _commonModeItems is NULL, there are no Run loop mode items in common mode
        if (NULL == rl->_commonModeItems) {
            // Apply space for _commonModeItems
            rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
        }
        
        // Add RLS to rL's common Mode item collection
        CFSetAddValue(rl->_commonModeItems, rls);
        
        // If the common modes of RL are not empty, it means that the current RL has modes marked common. In this case, the items in common Mode items need to be added synchronously to the modes marked common.
        // Because the run loop mode items can be called normally as long as rL runs in these modes. (For example, timer can only perform callbacks in default mode and slide mode if it is added to Common Mode)
        if (NULL! = set) { CFTypeRef context[2] = {rl, rls};
            /* add new item to all common-modes */
            / / call __CFRunLoopAddItemToCommonModes function add RLS to the common mode of rl
            CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
            
            // Release the temporary variable set above
            CFRelease(set); }}else {
    // modeName is currently a normal mode other than common mode
    
        // Find the run loop mode in rL (and lock the mode)
        CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true);
        
        // If mode is not empty and _sources0 is NULL, space is allocated for them
        if (NULL! = rlm &&NULL == rlm->_sources0) {
            // Request space
            rlm->_sources0 = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
            rlm->_sources1 = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
            rlm->_portToV1SourceMap = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0.NULL.NULL);
        }
        
        // If RLM is not NULL, then neither _sources0 nor _sources1 contains RLM
        if (NULL! = rlm && !CFSetContainsValue(rlm->_sources0, rls) && !CFSetContainsValue(rlm->_sources1, rls)) {
            // Determine whether RLS context is source0 or source1
            if (0 == rls->_context.version0.version) {
                // If RLS is source0, add it directly to _sources0.
                CFSetAddValue(rlm->_sources0, rls);
            } else if (1 == rls->_context.version0.version) {
                // Add RLS to _sources1 if RLS is source1 (_sources1 is more special than _sources0)
                CFSetAddValue(rlm->_sources1, rls);
                
                // Process the port
                
                // Fetch the port in source1
                __CFPort src_port = rls->_context.version1.getPort(rls->_context.version1.info);
                if(CFPORT_NULL ! = src_port) {// if src_port is not empty, save the port to RLM.
                    // _portToV1SourceMap and _portSet are two member variables of run loop mode:
                    // CFMutableDictionaryRef _portToV1SourceMap; Key = mach_port_t; value = CFRunLoopSourceRef
                    // __CFPortSet _portSet; _wakeUpPort, _timerPort, queuePort are stored in this collection
                    
                    CFDictionarySetValue(rlm->_portToV1SourceMap, (const void(*)uintptr_t)src_port, rls); __CFPortSetInsert(src_port, rlm->_portSet); }}// ⬆️⬆️ you can see that currently only source0 and source1 are available
        
        // (__CFRunLoopSource) lock source
        __CFRunLoopSourceLock(rls);
        
        // If RLS _runLoops is NULL, apply space for _runLoops
        if (NULL == rls->_runLoops) {
            rls->_runLoops = CFBagCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeBagCallBacks); // sources retain run loops!
        }
        // Add rL to _runLoops in RLS
        CFBagAddValue(rls->_runLoops, rl);
        // (__CFRunLoopSource) source unlocks
        __CFRunLoopSourceUnlock(rls);
        
        // If RLS is source0, if schedule exists, it will be executed after rL is unlocked below. Schedule is the callback function that source was added to mode
        if (0 == rls->_context.version0.version) {
            if (NULL! = rls->_context.version0.schedule) {// mark this as true to mark the schedule callback after the following RL is unlocked
                doVer0Callout = true; }}}// The corresponding lock is added before the __CFRunLoopFindMode(rl, modeName, true) function finds RLM and returns it.
        if (NULL! = rlm) {// (CFRunLoopModeRef) run loop mode Unlock__CFRunLoopModeUnlock(rlm); }}// (CFRunLoopRef) unlocks the outermost run loop (CFRunLoopRef).
    __CFRunLoopUnlock(rl);
    
    // Call the schedule function
    if (doVer0Callout) {
        // although it looses some protection for the source, we have no choice but to do this after unlocking
        // the run loop and mode locks, to avoid deadlocks where the source wants to take a lock which is already
        // held in another thread which is itself waiting for a run loop/mode lock
        // Although it loses some protection for source code, we have no choice but to do this after unlocking the run loop and mode lock,
        // To avoid deadlocks because the source code acquires a lock already held in another thread that is itself waiting to run a loop/mode lock.
        
        rls->_context.version0.schedule(rls->_context.version0.info, rl, modeName);    /* CALLOUT */}}Copy the code

The CFRunLoopAddSource function is longer but clearer, and can be consistent with our previous conclusion, especially if the mode added to source is common mode, souce is automatically synchronized to each common mode. (The comments are extremely clear, so I won’t summarize them here.)

CFRunLoopSource = CFRunLoopObserverRef CFRunLoopObserverRef (The difference between source0 and Souce1 in CFRunLoopSource and port in source1 will be discussed in the next article.)

CFRunLoopObserverRef (struct __CFRunLoopObserver *)

CFRunLoopObserverRef is the observer, and each Observer contains a callback (function pointer) that the observer receives when the state of the Run loop changes. It is used to report changes to the current state of the Run loop to the outside world.

typedef struct __CFRunLoopObserver * CFRunLoopObserverRef;

struct __CFRunLoopObserver {
    CFRuntimeBase _base; // All CF "instances" start from this structure
    pthread_mutex_t _lock; / / the mutex
    CFRunLoopRef _runLoop; // Observer of run loop
    CFIndex _rlCount; // The observer observed how many run loops there were
    CFOptionFlags _activities; /* immutable */ _activities specifies which state of the runloop to observe. Once specified, it is immutable.
    CFIndex _order; /* immutable */ // Observer priority
    
    // typedef void (*CFRunLoopObserverCallBack)(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info);
    CFRunLoopObserverCallBack _callout; /* immutable */ // observer callback function to observe the run loop state change callback
    CFRunLoopObserverContext _context; /* immutable, except invalidation */ // Observer context
};
Copy the code

The Observer also contains a callback function that is fired when the listening run loop state occurs. The logic used by run loop for observer is basically the same as that used by timer, specifying callback function and passing parameters through context.

CFRunLoopActivity is a set of enumerated values used to represent the run loop activity.

/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0), // Enter the RunLoop.
    kCFRunLoopBeforeTimers = (1UL << 1), // Run Loop will handle timer
    kCFRunLoopBeforeSources = (1UL << 2), // Run Loop will handle source
    kCFRunLoopBeforeWaiting = (1UL << 5), // Run Loop will go to sleep
    kCFRunLoopAfterWaiting = (1UL << 6), // Run Loop wakes up
    kCFRunLoopExit = (1UL << 7), // RunLoop Exit (Corresponding to kCFRunLoopEntry, Entry and Exit are called only once in each RunLoop to indicate that the Loop is about to enter and Exit.)
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};
Copy the code

Definition of CFRunLoopObserverContext.

typedef struct {
    CFIndex version;
    void * info;
    
    const void *(*retain)(const void *info); // retain
    void (*release)(const void *info); // release 
    
    CFStringRef (*copyDescription)(const void *info); // Describe the function
} CFRunLoopObserverContext;
Copy the code

Next comes the Run Loop Observer class object and the class registration.

CFTypeID CFRunLoopObserverGetTypeID(void) {
    static dispatch_once_t initOnce;
    
    dispatch_once(&initOnce, ^{ 
        / / class registration
        __kCFRunLoopObserverTypeID = _CFRuntimeRegisterClass(&__CFRunLoopObserverClass); 
    });
    
    return __kCFRunLoopObserverTypeID;
}
Copy the code

__CFRunLoopObserverClass is a RunLoopObserver class object.

static const CFRuntimeClass __CFRunLoopObserverClass = {
    0."CFRunLoopObserver"./ / name
    NULL.// init
    NULL.// copy
    __CFRunLoopObserverDeallocate, // Destruct function
    NULL.NULL.NULL,
    __CFRunLoopObserverCopyDescription // Describe the function
};
Copy the code

Ok, let’s look at the creation function of the Run Loop Observer.

CFRunLoopObserverCreate

The CFRunLoopObserverCreate function is used to create a CFRunLoopObserver object with a function callback. The CFRunLoopObserverCreate function takes six parameters.

Allocator: Allocator used to allocate memory for new objects. Pass NULL or kCFAllocatorDefault to use the current default allocator.

Activities: A set of flags that identifies the activity phase of the running cycle, during which the observer should be invoked. The CFRunLoopActivity above lists the different running states. To invoke an observer at multiple stages of the running loop, use the bitbit or operator to combine the values of CFRunLoopActivity.

Repeats: a marker that identifies whether the observer is called only once or every time in the run loop. If repeats is false, the observer will become invalid after one invocation even if scheduled to be invoked at multiple stages within the running loop.

Order: Priority index indicating the order in which the observers running the loop are processed. In a given run cycle mode, when multiple run cycle observers are scheduled in the same activity phase, the observers are processed in ascending order of this parameter. Pass 0 unless there is some other reason.

Callout: Callback function called by the observer runtime.

Context: Structure that holds context information for the running loop observer. This function copies the information out of the structure, so the memory pointed to by the context does not need to continue to exist after the function call. NULL if the observer does not need the context’s information pointer to track state.

Return value: new CFRunLoopObserver object. Ownership follows the creation rules described in the ownership policy.

CF_EXPORT CFRunLoopObserverRef CFRunLoopObserverCreate(CFAllocatorRef allocator, CFOptionFlags activities, Boolean repeats, CFIndex order, CFRunLoopObserverCallBack callout, CFRunLoopObserverContext *context);
Copy the code

Ok, let’s look at the implementation of this function.

CFRunLoopObserverRef CFRunLoopObserverCreate(CFAllocatorRef allocator, CFOptionFlags activities, Boolean repeats, CFIndex order, CFRunLoopObserverCallBack callout, CFRunLoopObserverContext *context) {
    // Used to check whether a given process is forked
    CHECK_FOR_FORK(a);// struct __CFRunLoopObserver structure pointer
    CFRunLoopObserverRef memory;
    UInt32 size;
    // Calculate the memory size of the __CFRunLoopObserver structure except for the CFRuntimeBase field
    size = sizeof(struct __CFRunLoopObserver) - sizeof(CFRuntimeBase);
    
    // Create an instance of __CFRunLoopObserver and return its pointer
    memory = (CFRunLoopObserverRef)_CFRuntimeCreateInstance(allocator, CFRunLoopObserverGetTypeID(), size, NULL);
    if (NULL == memory) {
        return NULL;
    }
    
    // Set the _cfinfo field in memory
    __CFSetValid(memory);
    
    // Set the value of memory's _cfinfo field to indicate that the run loop has not yet been observed.
    __CFRunLoopObserverUnsetFiring(memory);
    
    if (repeats) {
        // Set the _cfinfo field in memory to observe the run loop state repeatedly
        // __CFBitfieldSetValue(((CFRuntimeBase *)rlo)->_cfinfo[CF_INFO_BITS], 1, 1, 1);
        
        __CFRunLoopObserverSetRepeats(memory);
    } else {
        // Set memory's _cfinfo field to observe only one run loop change
        // __CFBitfieldSetValue(((CFRuntimeBase *)rlo)->_cfinfo[CF_INFO_BITS], 1, 1, 0);
        
        __CFRunLoopObserverUnsetRepeats(memory);
    }
    
    // initialize _lock to a mutex recursive lock
    __CFRunLoopLockInit(&memory->_lock);
    
    // Assign 5 consecutive fields
    memory->_runLoop = NULL;
    memory->_rlCount = 0;
    memory->_activities = activities;
    memory->_order = order;
    memory->_callout = callout;
    
    if (context) {
        // Decide whether to hold info depending on whether the context has a retain function
        if (context->retain) {
            memory->_context.info = (void *)context->retain(context->info);
        } else {
            memory->_context.info = context->info;
        }
        
        // Configure the contents of the _context field
        memory->_context.retain = context->retain;
        memory->_context.release = context->release;
        memory->_context.copyDescription = context->copyDescription;
    } else {
        // If context is NULL, the contents of the _context field are set to 0
        memory->_context.info = 0;
        memory->_context.retain = 0;
        memory->_context.release = 0;
        memory->_context.copyDescription = 0;
    }
    
    // Returns the struct __CFRunLoopObserver structure pointer
    return memory;
}
Copy the code

The run loop viewer is not automatically added to the run loop. To add an observer to the run loop, use CFRunLoopAddObserver. An observer can only be registered to one run cycle, although it can be added to multiple run cycle modes within that run cycle.

CFRunLoopAddObserver

void CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef rlo, CFStringRef modeName) {
    // Used to check whether a given process is forked
    CHECK_FOR_FORK(a);Struct __CFRunLoopMode pointer
    CFRunLoopModeRef rlm;
    
    Return (rl->_cfinfo[CF_INFO_BITS])
    if (__CFRunLoopIsDeallocating(rl)) return;
    
    // if rlo is invalid, return,
    Rlo _runLoop is not NULL and _runLoop is not equal to rL
    if(! __CFIsValid(rlo) || (NULL! = rlo->_runLoop && rlo->_runLoop ! = rl))return;
    
    // run loop lock
    __CFRunLoopLock(rl);
    
    // If modeName is kCFRunLoopCommonModes, add RLO to all modes marked common.
    // This will be observed whenever rL is in common mode.
    if (modeName == kCFRunLoopCommonModes) {
        // Get _commonModes of rL
        CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
        
        // If the _commonModeItems of rL is NULL, space is allocated for it
        if (NULL == rl->_commonModeItems) {
            rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
        }
        
        // Add RLO to _commonModeItems of RL
        CFSetAddValue(rl->_commonModeItems, rlo);
        
        // If current RLS have modes labeled common, add RLO to _observers of their run loop mode
        if (NULL! = set) { CFTypeRef context[2] = {rl, rlo};
            /* add new item to all common-modes */

            / / call the rlo __CFRunLoopAddItemToCommonModes function added to the common mode of rl
            CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
            
            // Release the temporary variable set above
            CFRelease(set); }}else {
    // modeName is currently a normal mode other than common mode
    
        // Find the run loop mode in rL (and lock the mode)
        rlm = __CFRunLoopFindMode(rl, modeName, true);
        
        // If mode is not NULL and _observers is NULL, apply space for them
        if (NULL! = rlm &&NULL == rlm->_observers) {
            // Apply for space for _observers of RLM
            rlm->_observers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks);
        }
        
        // If RLM is not null, and _observers of the current RLM does not include RLO
        if (NULL! = rlm && !CFArrayContainsValue(rlm->_observers, CFRangeMake(0.CFArrayGetCount(rlm->_observers)), rlo)) {
            // INSERTED Flag RLO whether the insert was successful
            Boolean inserted = false;
            
            // The for loop is convenient from back to forward, finding the first Run loop observer with _order less than or equal to RLO in the _observers array of RLM
            for (CFIndex idx = CFArrayGetCount(rlm->_observers); idx--; ) {
                // Find the CFRunLoopObserverRef at the specified location
                CFRunLoopObserverRef obs = (CFRunLoopObserverRef)CFArrayGetValueAtIndex(rlm->_observers, idx);
                
                / / _order
                if (obs->_order <= rlo->_order) {
                    / / insert
                    CFArrayInsertValueAtIndex(rlm->_observers, idx + 1, rlo);
                    
                    // Insert successfully out of the for loop
                    inserted = true;
                    break; }}// If a run loop observer smaller than _order is not found at the end of the loop, insert RLO at 0 subscript of _observers of RLM
            if(! inserted) {CFArrayInsertValueAtIndex(rlm->_observers, 0, rlo);
            }
            
            RLM ->_observerMask (1 in RLM ->_observerMask)
            rlm->_observerMask |= rlo->_activities;
            
            / / __CFRunLoopObserverSchedule function below ⬇ ️
            __CFRunLoopObserverSchedule(rlo, rl, rlm);
        }
        
        // If RLM is not empty, RLM = __CFRunLoopFindMode(rl, modeName, true); To unlock the RLM
        if (NULL != rlm) {
            __CFRunLoopModeUnlock(rlm);
        }
    }
    
    // run loop Unlock
    __CFRunLoopUnlock(rl);
}

static void __CFRunLoopObserverSchedule(CFRunLoopObserverRef rlo, CFRunLoopRef rl, CFRunLoopModeRef rlm) {
    / / CFRunLoopObserver lock
    __CFRunLoopObserverLock(rlo);
    // If _rlCount of rLO is 0, then _runLoop of rlo is assigned to rl
    if (0 == rlo->_rlCount) {
        rlo->_runLoop = rl;
    }
    / / _rlCount since
    rlo->_rlCount++;
    
    / / CFRunLoopObserver unlocked
    __CFRunLoopObserverUnlock(rlo);
}
Copy the code

CFRunLoopAddObserver = CFRunLoopSource = CFRunLoopSource = CFRunLoopSource CFRunLoopAddObserver also relies on a particular Run loop mode, so once the run loop is running in another mode, the desired state will not be heard.

Let’s start by looking at CFRunLoopTimerRef.

Struct __CFRunLoopTimer *

NSTimer is closely related to run loop, CFRunLoopTimerRef and NSTimer can be toll-free bridged. When the timer is added to the Run loop, the Run loop will register the corresponding trigger time. When the time is up, the Run loop will wake up if it is in sleep and execute the corresponding callback function of the timer.

typedef struct CF_BRIDGED_MUTABLE_TYPE(NSTimer) __CFRunLoopTimer * CFRunLoopTimerRef;

struct __CFRunLoopTimer {
    CFRuntimeBase _base; // All CF "instances" start from this structure
    uint16_t _bits; // Mark the state of the timer
    pthread_mutex_t _lock; / / the mutex
    CFRunLoopRef _runLoop; // The timer is registered in the corresponding run loop
    CFMutableSetRef _rlModes; // The name of the run loop modes corresponding to the timer is also saved internally, which verifies that the timer can be used in multiple Run loop modes
    CFAbsoluteTime _nextFireDate; // timer Indicates the next triggering time. The value is set again after each triggering
    CFTimeInterval _interval; /* immutable */ // The interval of the timer
    
        / / _tolerance default value is 0.0, and then CFRunLoopTimerSetTolerance function When the parameter is less than zero, set how = 0.0,
    // If greater than 0, set RLT ->_tolerance = MIN(tolerance, RLT ->_interval / 2), i.e.
    CFTimeInterval _tolerance; /* mutable */ // The allowed time deviation of the timer
    
    uint64_t _fireTSR; /* TSR units */ // timer Indicates the time when the timer is triggered
    CFIndex _order; /* immutable */ // Timer priority
    
    // typedef void (*CFRunLoopTimerCallBack)(CFRunLoopTimerRef timer, void *info);
    CFRunLoopTimerCallBack _callout; /* immutable */ / / the timer callback
    CFRunLoopTimerContext _context; /* immutable, except invalidation */ // Timer context, which can be used to pass parameters to the timer object's callback function.
};
Copy the code

CFRunLoopTimerContext definition.

typedef struct {
    CFIndex version;
    void * info;
    const void *(*retain)(const void *info); // retain
    void (*release)(const void *info); // release
     
    CFStringRef (*copyDescription)(const void *info); // Describe the function
} CFRunLoopTimerContext;
Copy the code

Then there’s the Run Loop Timer class object, and the class registration.

CFTypeID CFRunLoopTimerGetTypeID(void) {
    static dispatch_once_t initOnce;
    dispatch_once(&initOnce, ^{ 
        / / class registration
        __kCFRunLoopTimerTypeID = _CFRuntimeRegisterClass(&__CFRunLoopTimerClass); 
    });
    return __kCFRunLoopTimerTypeID;
}
Copy the code

__CFRunLoopTimerClass is a run loop Timer class object.

static const CFRuntimeClass __CFRunLoopTimerClass = {
    0."CFRunLoopTimer"./ / name
    NULL.// init
    NULL.// copy
    __CFRunLoopTimerDeallocate, // Destruct function
    NULL.// equal
    NULL.NULL,
    __CFRunLoopTimerCopyDescription // Describe the function
};
Copy the code

Ok, let’s look at the run Loop Timer creation function.

CFRunLoopTimerCreate

CFRunLoopTimerCreate Creates a new CFRunLoopTimer object with a function callback.

Allocator: Allocator used to allocate memory for new objects. Pass NULL or kCFAllocatorDefault to use the current default allocator.

FireDate: The time when the timer should fire first. If there are implementation reasons, the precision of the trigger date can be adjusted by a timer (submillisecond at most).

Interval: indicates the interval for triggering a timer. If it is 0 or negative, the timer will trigger once and then automatically expire. If there are enforcement reasons, the precision of the interval can be adjusted by a timer (submillisecond at most).

Flags: currently ignored. Pass 0 for future compatibility.

Order: Priority index indicating the processing order in which the cycle timer runs. The running cycle timer currently ignores this parameter. Passing zero.

Callout: callback function called when the timer is triggered.

Context: Structure that holds the context information for running the timer cycle. This function copies the information out of the structure, so the memory pointed to by the context does not need to continue to exist after the function call. NULL if the callback function does not need the context’s information pointer to track state.

CFRunLoopTimerRef CFRunLoopTimerCreate(CFAllocatorRef allocator, CFAbsoluteTime fireDate, CFTimeInterval interval, CFOptionFlags flags, CFIndex order, CFRunLoopTimerCallBack callout, CFRunLoopTimerContext *context) {
    // Used to check whether a given process is forked
    CHECK_FOR_FORK(a);// 如果 interval 为 NaN,则 crash
    if (isnan(interval)) {
        CRSetCrashLogMessage("NaN was used as an interval for a CFRunLoopTimer");
        HALT;
    }
    
    Struct __CFRunLoopTimer struct __CFRunLoopTimer
    CFRunLoopTimerRef memory;
    UInt32 size;
    // Calculate the size of the __CFRunLoopTimer structure except for the CFRuntimeBase field
    size = sizeof(struct __CFRunLoopTimer) - sizeof(CFRuntimeBase);
    
    // Create an instance of __CFRunLoopTimer and return its pointer
    memory = (CFRunLoopTimerRef)_CFRuntimeCreateInstance(allocator, CFRunLoopTimerGetTypeID(), size, NULL);
    if (NULL == memory) {
        return NULL;
    }
    
    // Set the _cfinfo field in memory
    __CFSetValid(memory);
    
    // Set the value of memory's _cfinfo field to indicate that the current timer has not been executed
    __CFRunLoopTimerUnsetFiring(memory);
    
    // initialize _lock to a mutex recursive lock
    __CFRunLoopLockInit(&memory->_lock);
    
    // _runLoop 置为 NULL
    memory->_runLoop = NULL;
    // Apply space for _rlModes of the timer
    memory->_rlModes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
    // Priority, which will be used when adding timer to mode
    memory->_order = order;
    
    // If interval is less than 0.0, set to 0
    if (interval < 0.0) interval = 0.0;
    
    // Interval assignment
    memory->_interval = interval;
    memory->_tolerance = 0.0;
    
    / / # define TIMER_DATE_LIMIT 4039289856.0
    if (TIMER_DATE_LIMIT < fireDate) fireDate = TIMER_DATE_LIMIT;
    
    // Next trigger time
    memory->_nextFireDate = fireDate;
    memory->_fireTSR = 0ULL;
    
    uint64_t now2 = mach_absolute_time(a); CFAbsoluteTime now1 =CFAbsoluteTimeGetCurrent(a);if (fireDate < now1) {
        memory->_fireTSR = now2;
    } else if (TIMER_INTERVAL_LIMIT < fireDate - now1) {
        memory->_fireTSR = now2 + __CFTimeIntervalToTSR(TIMER_INTERVAL_LIMIT);
    } else {
        memory->_fireTSR = now2 + __CFTimeIntervalToTSR(fireDate - now1);
    }
    
    // The callback function
    memory->_callout = callout;
    
    // context
    if (NULL! = context) {// If context is not NULL
        
        // Whether to retain info
        if (context->retain) {
            memory->_context.info = (void *)context->retain(context->info);
        } else {
            memory->_context.info = context->info;
        }
        
        // Perform the assignment
        memory->_context.retain = context->retain;
        memory->_context.release = context->release;
        memory->_context.copyDescription = context->copyDescription;
    } else {
        / / s
        memory->_context.info = 0;
        memory->_context.retain = 0;
        memory->_context.release = 0;
        memory->_context.copyDescription = 0;
    }
    return memory;
}
Copy the code

The timer needs to be added to run loop mode before it can start. To add a timer to the run loop, use the CFRunLoopAddTimer function. A timer can only be registered to one run cycle at a time, although it can be in multiple modes within that run cycle.

CFRunLoopAddTimer

CFRunLoopAddTimer Adds the CFRunLoopTimer object to the RunLoop mode. ModeName is the run cycle mode of the RL to which the timer is to be added. Add timers to all common Mode monitored object sets using the constant kCFRunLoopCommonModes.

Although a run cycle timer can be added to multiple run cycle modes within a run cycle at once, it can only be registered in one run cycle at a time. If the RL already includes a timer in mode, this feature does nothing.

void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) {    
    // Used to check whether a given process is forked
    CHECK_FOR_FORK(a);// If rL is marked as being released, it returns directly.
    if (__CFRunLoopIsDeallocating(rl)) return;
    
    // if RLT is invalid, return,
    RLT _runLoop is not NULL and _runLoop is not equal to rL
    if(! __CFIsValid(rlt) || (NULL! = rlt->_runLoop && rlt->_runLoop ! = rl))return;
    
    // run loop lock
    __CFRunLoopLock(rl);
    
    // If RLT is added to common Mode
    if (modeName == kCFRunLoopCommonModes) {
        // Get a copy of rL's _commonModes
        CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
        
        // If the _commonModeItems of rL is NULL, it allocates memory for it
        if (NULL == rl->_commonModeItems) {
            rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
        }
        
        // Add RLT to _commonModeItems of RL
        CFSetAddValue(rl->_commonModeItems, rlt);
        
        // If rL has common mode
        if (NULL! = set) { CFTypeRef context[2] = {rl, rlt};
            /* add new item to all common-modes */
            / / call __CFRunLoopAddItemToCommonModes function add RLT to rl is marked as common mode
            CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
            
            // Release the temporary variable set above
            CFRelease(set); }}else {
    // modeName is currently a normal mode other than common mode
    
        // Find the run loop mode in rL (and lock the mode)
        CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true);
        
        // If RLM is found
        if (NULL! = rlm) {// If the _timers of RLM are NULL, memory is allocated for the RLM
            if (NULL == rlm->_timers) {
                CFArrayCallBacks cb = kCFTypeArrayCallBacks;
                cb.equal = NULL;
                
                // Apply memory for _timers
                rlm->_timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &cb); }}// If RLM is not NULL and the _rlModes of RLT do not contain RLM
        if (NULL! = rlm && !CFSetContainsValue(rlt->_rlModes, rlm->_name)) {
            / / RLT lock
            __CFRunLoopTimerLock(rlt);

            if (NULL == rlt->_runLoop) {
                // If RLT ->_runLoop is NULL, the _runLoop of RLT is assigned
                rlt->_runLoop = rl;
            } else if(rl ! = rlt->_runLoop) {// If RLT's _runLoop is not rl, then RLT's _runLoop has a value.
                If a timer has been added to a run loop, it cannot be added to another run loop.
                
                __CFRunLoopTimerUnlock(rlt);
                __CFRunLoopModeUnlock(rlm);
                __CFRunLoopUnlock(rl);
                
                / / return directly
                return;
            }
            
            // Add RLM _name to _rlModes of RLT.
            CFMutableSetRef _rlModes; The name of run loop modes corresponding to timer is also saved internally, which verifies that timer can be used in multiple Run loop modes.)
            CFSetAddValue(rlt->_rlModes, rlm->_name);
            
            / / timer unlocked
            __CFRunLoopTimerUnlock(rlt);
            
            // static CFLock_t __CFRLTFireTSRLock = CFLockInit;
            / / lock
            __CFRunLoopTimerFireTSRLock();
            // Add RLT to _timers of RLM
            __CFRepositionTimerInMode(rlm, rlt, false);
            / / unlock
            __CFRunLoopTimerFireTSRUnlock();
            
            // CFSystemVersionLion = 7, /* 10.7 */
            if(! _CFExecutableLinkedOnOrAfter(CFSystemVersionLion)) {// Normally we don't do this on behalf of clients, but for backwards compatibility due to the change in timer handling...
                // Usually we don't do this on behalf of our customers, but backwards compatible due to changes in the way timers are handled...
                if(rl ! =CFRunLoopGetCurrent()) CFRunLoopWakeUp(rl); }}// The corresponding lock is added before the __CFRunLoopFindMode(rl, modeName, true) function finds RLM and returns it.
        if (NULL != rlm) {
            __CFRunLoopModeUnlock(rlm);
        }
    }
    
    // run loop Unlock
    __CFRunLoopUnlock(rl);
}
Copy the code

CFRunLoopSourceRef CFRunLoopObserverRef CFRunLoopTimerRef CFRunLoopSourceRef CFRunLoopObserverRef CFRunLoopTimerRef ⛽ ️ ⛽ ️

Refer to the link

Reference link :🔗

  • Runloop source
  • Run Loops official documentation
  • Complete guide to iOS RunLoop
  • IOS source code parsing: Runloop underlying data structure
  • IOS source code: Runloop operating principle
  • Understand RunLoop in depth
  • IOS Basics – Dive into RunLoop
  • A core runloop source code analysis
  • NSRunLoop
  • Get to the bottom of iOS – Understand RunLoop in depth
  • RunLoop summary and interview
  • Runloop- Actually develop the application scenario you want to use
  • RunLoop source code read
  • do {… } The role of while (0) in the macro definition
  • CFRunLoop (cF-1151.16)
  • Operating system big-endian mode and small-endian mode
  • CFBag