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