2021-08-12
preface
After a brief overview of RunLoop’s data structures, this section introduces the object management of RunLoop. The main managed objects of RunLoop include Modes, monitoring objects (Sources, Observers, Timers), and the more specific Common Modes.
Modes management
RunLoop provides simple Modes management logic. CFRunLoop does not expose an explicit interface for adding Mode, nor does it provide an interface for removing Mode.
Adding Mode is implemented in the __CFRunLoopFindMode method. As described in the previous section, FindMode will be found in the _modes member of CFRunLoop (type CFMutableSetRef, Element is of type CFRunLoopModeRef) finds the target Mode by name, and if not, creates a new Mode.
There is no internal method for removing Mode in RunLoop, because when Mode is removed, the RunLoop is destroyed. In other words, Mode is removed in RunLoop destructor. In short, RunLoop either removes no Modes or removes all Modes when necessary.
2. Monitoring Object management
The monitoring object management logic is interwoven with a lot of CommonModes management code, which will be bolded in this section. This will be summarized in chapter 3 as we learn about CommonModes management.
2.1 Sources
To add the destination Source to the specified Mode:
- The target Mode is CommonModes:
- Add the target Source to the commonModeItems collection;
- Add the target Source to all Modes contained in CommonModes;
- Target Mode is not CommonModes:
- If the target Source is Source0: add the target Source to Mode
_sources0
Collection, if the target Sourcecontext.version0.schedule
If yes, it is marked as “The operation needs to be triggered when the addition is successful”. - If the target Source is Source1: add the target Source to Mode
_sources1
Set, and update Mode_portToV1SourceMap
A dictionary; - Update the target Source
_runLoops
Collection (backreferences to all runloops with this Source added);
- If the target Source is Source0: add the target Source to Mode
- If the Source is Source0 and is marked “Trigger action when add succeeds” : triggers
context.version0.schedule
;
NOTE: the _modes data type is CFMutableSetRef and the element is CFRunLoopModeRef; The _commonModes data type is CFMutableSetRef and the element is CFStringRef.
NOTE: CFSetApplyFunction (set, (__CFRunLoopAddItemToCommonModes), (void *) context) is the function of: Traverse the set of all elements to elements, and context to preach, call __CFRunLoopAddItemToCommonModes function.
// Omit some simple lazy loading code
void CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef rls, CFStringRef modeName) { /* DOES CALLOUT */
CHECK_FOR_FORK(a);if (__CFRunLoopIsDeallocating(rl)) return;
if(! __CFIsValid(rls))return;
Boolean doVer0Callout = false;
__CFRunLoopLock(rl);
if (modeName == kCFRunLoopCommonModes) {
CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL; .// Core Operation 1: add the target Source to the _commonModeItems collection
CFSetAddValue(rl->_commonModeItems, rls);
if (NULL! = set) { CFTypeRef context[2] = {rl, rls};
// Core Operation 2: add the target Source to all Modes included in CommonModes
CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
CFRelease(set); }}else {
CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true); .if (NULL! = rlm && !CFSetContainsValue(rlm->_sources0, rls) && !CFSetContainsValue(rlm->_sources1, rls)) {
// Core operation 3: add the target Source to _source0 or _source1
if (0 == rls->_context.version0.version) {
CFSetAddValue(rlm->_sources0, rls);
} else if (1 == rls->_context.version0.version) {
CFSetAddValue(rlm->_sources1, rls);
__CFPort src_port = rls->_context.version1.getPort(rls->_context.version1.info);
if(CFPORT_NULL ! = src_port) {CFDictionarySetValue(rlm->_portToV1SourceMap, (const void(*)uintptr_t)src_port, rls); __CFPortSetInsert(src_port, rlm->_portSet); } } __CFRunLoopSourceLock(rls); .// Core Operation 4: Updates the collection of backreferences from the target Source to "all runloops that added this Source.
CFBagAddValue(rls->_runLoops, rl);
__CFRunLoopSourceUnlock(rls);
if (0 == rls->_context.version0.version) {
if (NULL! = rls->_context.version0.schedule) {// Core Operation 5: marked "Need to trigger action when add succeeds"
doVer0Callout = true; }}}if (NULL! = rlm) { __CFRunLoopModeUnlock(rlm); } } __CFRunLoopUnlock(rl);/ / core operating 6: if marked as "add success need triggering action", the trigger _context. Version0. The schedule
if (doVer0Callout) {
rls->_context.version0.schedule(rls->_context.version0.info, rl, modeName); /* CALLOUT */}}Copy the code
To remove the target Source from the specified Mode:
- The target Mode is CommonModes:
- Removes the target Source from the commonModeItems collection
- Removes the target Source from all Modes contained in CommonModes;
- Target Mode is not CommonModes:
- If the target Source is Source0: set the target Source from Mode
_sources0
Collection if the target Source’scontext.version0.cancel
If yes, it is marked as “Operation needs to be triggered when the removal is successful”. - If the target Source is Source1: set the target Source from Mode
_sources1
Set, and update Mode_portToV1SourceMap
A dictionary; - Update the target Source
_runLoops
The collection;
- If the target Source is Source0: set the target Source from Mode
- If the Source is Source0 and marked “Operation required when removal is successful” : triggers
context.version0.cancel
;
NOTE: It is not difficult to see that the logic of CFRunLoopRemoveSource and CFRunLoopAddSource are highly symmetric.
void CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef rls, CFStringRef modeName) { /* DOES CALLOUT */
CHECK_FOR_FORK(a); Boolean doVer0Callout =false, doRLSRelease = false;
__CFRunLoopLock(rl);
if (modeName == kCFRunLoopCommonModes) {
if (NULL! = rl->_commonModeItems &&CFSetContainsValue(rl->_commonModeItems, rls)) {
CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
// Core operation 1: Remove the Source from commonModeItems
CFSetRemoveValue(rl->_commonModeItems, rls);
if (NULL! = set) { CFTypeRef context[2] = {rl, rls};
// Core operation 2: traverses all Modes of commonModes and removes the target Source
CFSetApplyFunction(set, (__CFRunLoopRemoveItemFromCommonModes), (void *)context);
CFRelease(set); }}}else {
CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, false);
if (NULL! = rlm && ((NULL! = rlm->_sources0 &&CFSetContainsValue(rlm->_sources0, rls)) || (NULL! = rlm->_sources1 &&CFSetContainsValue(rlm->_sources1, rls)))) {
CFRetain(rls);
if (1 == rls->_context.version0.version) {
__CFPort src_port = rls->_context.version1.getPort(rls->_context.version1.info);
if(CFPORT_NULL ! = src_port) {CFDictionaryRemoveValue(rlm->_portToV1SourceMap, (const void(*)uintptr_t)src_port); __CFPortSetRemove(src_port, rlm->_portSet); }}// Core operation 3: remove target Source from _source0 or _source1
CFSetRemoveValue(rlm->_sources0, rls);
CFSetRemoveValue(rlm->_sources1, rls);
__CFRunLoopSourceLock(rls);
// Core Operation 4: Updates the collection of backreferences from the target Source to "all runloops that added this Source.
if (NULL! = rls->_runLoops) {CFBagRemoveValue(rls->_runLoops, rl);
}
__CFRunLoopSourceUnlock(rls);
if (0 == rls->_context.version0.version) {
if (NULL! = rls->_context.version0.cancel) {// Core Operation 5: marked as "Need to trigger operation when removal is successful"
doVer0Callout = true;
}
}
doRLSRelease = true;
}
if (NULL! = rlm) { __CFRunLoopModeUnlock(rlm); } } __CFRunLoopUnlock(rl);if (doVer0Callout) {
// Core Operation 6: _context.version0.cancel is fired if it is marked as "operation needs to be triggered when removal is successful"
rls->_context.version0.cancel(rls->_context.version0.info, rl, modeName); /* CALLOUT */
}
if (doRLSRelease) CFRelease(rls);
}
Copy the code
2.2 Observers
To add a target Observer to a specified Mode:
- If the target Observer
_runLoop
Non-empty and not equal to the target RunLoop, returns directly to ensure that the Observer can only be added to one RunLoop (consistent with Documentation on the Observer); - The target Mode is CommonModes:
- Add the target Source to the commonModeItems collection;
- Add the target Source to all Modes contained in CommonModes;
- Target Mode is not CommonModes:
- In target Mode
_observers
According to the array_order
In descending order, insert the target Observer_observers
; - Update target Mode
_observerMask
; - Update the target Observer
_runLoop
and_rlCount
;
- In target Mode
NOTE: the _observers array is always in _order descending order, so the larger the _order Observer in Mode, the higher the priority. But in most cases, _order will be set to 0.
// Omit some simple lazy loading code
void CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef rlo, CFStringRef modeName) {
CHECK_FOR_FORK(a); CFRunLoopModeRef rlm;if (__CFRunLoopIsDeallocating(rl)) return;
// Core Operation 0: ensure that an Observer can only be added to one RunLoop
if(! __CFIsValid(rlo) || (NULL! = rlo->_runLoop && rlo->_runLoop ! = rl))return;
__CFRunLoopLock(rl);
if (modeName == kCFRunLoopCommonModes) {
CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL; .CFSetAddValue(rl->_commonModeItems, rlo);
if (NULL! = set) { CFTypeRef context[2] = {rl, rlo};
CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
CFRelease(set); }}else {
rlm = __CFRunLoopFindMode(rl, modeName, true); .if (NULL! = rlm && !CFArrayContainsValue(rlm->_observers, CFRangeMake(0.CFArrayGetCount(rlm->_observers)), rlo)) {
Boolean inserted = false;
// Core operation 1: Insert '_observers' in' _order 'descending order from the' _observers' array of target Mode
for (CFIndex idx = CFArrayGetCount(rlm->_observers); idx--; ) {
CFRunLoopObserverRef obs = (CFRunLoopObserverRef)CFArrayGetValueAtIndex(rlm->_observers, idx);
if (obs->_order <= rlo->_order) {
CFArrayInsertValueAtIndex(rlm->_observers, idx + 1, rlo);
inserted = true;
break; }}if(! inserted) {CFArrayInsertValueAtIndex(rlm->_observers, 0, rlo);
}
// Core operation 2: update '_observerMask' for the target Mode
rlm->_observerMask |= rlo->_activities;
// Core operation 3: update '_runLoop' and '_rlCount' of the target Observer
__CFRunLoopObserverSchedule(rlo, rl, rlm);
}
if (NULL! = rlm) { __CFRunLoopModeUnlock(rlm); } } __CFRunLoopUnlock(rl); }static void __CFRunLoopObserverSchedule(CFRunLoopObserverRef rlo, CFRunLoopRef rl, CFRunLoopModeRef rlm) {
__CFRunLoopObserverLock(rlo);
if (0 == rlo->_rlCount) {
rlo->_runLoop = rl;
}
rlo->_rlCount++;
__CFRunLoopObserverUnlock(rlo);
}
Copy the code
To remove the target Observer from the specified Mode:
- The target Mode is CommonModes:
- Remove the target Observer from the commonModeItems collection
- Remove the target Observer from all Modes included in CommonModes
- Target Mode is not CommonModes:
- Set the target Observer from
_observers
Remove; - Update the target Observer
_runLoop
and_rlCount
;
- Set the target Observer from
void CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef rlo, CFStringRef modeName) {
CHECK_FOR_FORK(a); CFRunLoopModeRef rlm; __CFRunLoopLock(rl);if (modeName == kCFRunLoopCommonModes) {
if (NULL! = rl->_commonModeItems &&CFSetContainsValue(rl->_commonModeItems, rlo)) {
CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
CFSetRemoveValue(rl->_commonModeItems, rlo);
if (NULL! = set) { CFTypeRef context[2] = {rl, rlo};
CFSetApplyFunction(set, (__CFRunLoopRemoveItemFromCommonModes), (void *)context);
CFRelease(set); }}else{}}else {
rlm = __CFRunLoopFindMode(rl, modeName, false);
if (NULL! = rlm &&NULL! = rlm->_observers) {CFRetain(rlo);
CFIndex idx = CFArrayGetFirstIndexOfValue(rlm->_observers, CFRangeMake(0.CFArrayGetCount(rlm->_observers)), rlo);
if(kCFNotFound ! = idx) {// Core Action 1: Remove target observers from _Observers
CFArrayRemoveValueAtIndex(rlm->_observers, idx);
// Core operation 2: update '_runLoop' and '_rlCount' of the target Observer
__CFRunLoopObserverCancel(rlo, rl, rlm);
}
CFRelease(rlo);
}
if (NULL! = rlm) { __CFRunLoopModeUnlock(rlm); } } __CFRunLoopUnlock(rl); }static void __CFRunLoopObserverCancel(CFRunLoopObserverRef rlo, CFRunLoopRef rl, CFRunLoopModeRef rlm) {
__CFRunLoopObserverLock(rlo);
rlo->_rlCount--;
if (0 == rlo->_rlCount) {
rlo->_runLoop = NULL;
}
__CFRunLoopObserverUnlock(rlo);
}
Copy the code
2.3 Timers
Of the three types of monitoring object management, Timers are the most complex. So some of the following actions are slightly more obscure until we fully understand the mechanisms for managing and responding to Timers. Let’s set aside some of the internal details and look at what it does.
To add the target Timer to the specified Mode:
- If the target Timer
_runLoop
Non-empty and not equal to the target RunLoop, return directly to ensure that the Timer can only be added to one RunLoop (consistent with Documentation of the Timer); - The target Mode is CommonModes:
- Add the target Timer to the commonModeItems collection;
- Add the target Timer to all Modes contained by CommonModes;
- Target Mode is not CommonModes:
- Update Timer
_runLoop
and_rlModes
; - Update target Mode
_timers
(including the update target Mode next timing event trigger operation);
- Update Timer
NOTE: __CFRepositionTimerInMode and __CFArmNextTimerInMode are the core logic of RunLoop. You’ll see why these two methods are needed in the next article when you learn how RunLoop handles timers.
// Omit some simple lazy loading code
void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) {
CHECK_FOR_FORK(a);if (__CFRunLoopIsDeallocating(rl)) return;
if(! __CFIsValid(rlt) || (NULL! = rlt->_runLoop && rlt->_runLoop ! = rl))return;
__CFRunLoopLock(rl);
if (modeName == kCFRunLoopCommonModes) {
CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL; .CFSetAddValue(rl->_commonModeItems, rlt);
if (NULL! = set) { CFTypeRef context[2] = {rl, rlt};
CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
CFRelease(set); }}else {
CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true); .if (NULL! = rlm && !CFSetContainsValue(rlt->_rlModes, rlm->_name)) {
__CFRunLoopTimerLock(rlt);
// Core operation 1: update _runLoop and _rlModes of the Timer
if (NULL == rlt->_runLoop) {
rlt->_runLoop = rl;
} else if(rl ! = rlt->_runLoop) { __CFRunLoopTimerUnlock(rlt); __CFRunLoopModeUnlock(rlm); __CFRunLoopUnlock(rl);return;
}
CFSetAddValue(rlt->_rlModes, rlm->_name);
__CFRunLoopTimerUnlock(rlt);
__CFRunLoopTimerFireTSRLock();
// Core operation 2: update the _timers sequence for the target Mode
__CFRepositionTimerInMode(rlm, rlt, false);
__CFRunLoopTimerFireTSRUnlock();
//NOTE:Operating system version compatible code, not important can be ignored
if(! _CFExecutableLinkedOnOrAfter(CFSystemVersionLion)) {if(rl ! =CFRunLoopGetCurrent()) CFRunLoopWakeUp(rl); }}if (NULL! = rlm) { __CFRunLoopModeUnlock(rlm); } } __CFRunLoopUnlock(rl); }Copy the code
To remove the target Timer from the specified Mode:
- The target Mode is CommonModes:
- Remove the target Timer from the commonModeItems collection
- Removes the target Timer from all Modes contained in CommonModes;
- Target Mode is not CommonModes:
- Update Timer
_runLoop
and_rlModes
; - “Update the next timing event trigger for target Mode”;
- Update Timer
void CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) {
CHECK_FOR_FORK(a); __CFRunLoopLock(rl);if (modeName == kCFRunLoopCommonModes) {
if (NULL! = rl->_commonModeItems &&CFSetContainsValue(rl->_commonModeItems, rlt)) {
CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
CFSetRemoveValue(rl->_commonModeItems, rlt);
if (NULL! = set) { CFTypeRef context[2] = {rl, rlt};
CFSetApplyFunction(set, (__CFRunLoopRemoveItemFromCommonModes), (void *)context);
CFRelease(set); }}else{}}else {
CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, false);
CFIndex idx = kCFNotFound;
CFMutableArrayRef timerList = NULL;
if (NULL! = rlm) { timerList = rlm->_timers;if (NULL! = timerList) { idx =CFArrayGetFirstIndexOfValue(timerList, CFRangeMake(0.CFArrayGetCount(timerList)), rlt); }}if(kCFNotFound ! = idx) { __CFRunLoopTimerLock(rlt);// Core operation 1: update _runLoop and _rlModes of the Timer
CFSetRemoveValue(rlt->_rlModes, rlm->_name);
if (0= =CFSetGetCount(rlt->_rlModes)) {
rlt->_runLoop = NULL;
}
__CFRunLoopTimerUnlock(rlt);
CFArrayRemoveValueAtIndex(timerList, idx);
// Core Operation 2: updates the next timing event trigger for the target Mode
__CFArmNextTimerInMode(rlm, rl);
}
if (NULL! = rlm) { __CFRunLoopModeUnlock(rlm); } } __CFRunLoopUnlock(rl); }Copy the code
3. CommonModes management
The CommonModes management logic contained in chapter 2 is summarized as follows:
- Add operation target Mode to CommonModes:
- Add the target object to the commonModeItems collection;
- Add the target object to all Modes contained by CommonModes;
- Remove target Mode from CommonModes:
- Removes the target object from the commonModeItems collection
- Removes the target object from all Modes contained in CommonModes;
The method called is implemented as follows:
static void __CFRunLoopAddItemToCommonModes(const void *value, void *ctx) {
CFStringRef modeName = (CFStringRef)value;
CFRunLoopRef rl = (CFRunLoopRef)(((CFTypeRef *)ctx)[0]);
CFTypeRef item = (CFTypeRef)(((CFTypeRef *)ctx)[1]);
if (CFGetTypeID(item) == CFRunLoopSourceGetTypeID()) {
CFRunLoopAddSource(rl, (CFRunLoopSourceRef)item, modeName);
} else if (CFGetTypeID(item) == CFRunLoopObserverGetTypeID()) {
CFRunLoopAddObserver(rl, (CFRunLoopObserverRef)item, modeName);
} else if (CFGetTypeID(item) == CFRunLoopTimerGetTypeID()) {
CFRunLoopAddTimer(rl, (CFRunLoopTimerRef)item, modeName); }}static void __CFRunLoopRemoveItemFromCommonModes(const void *value, void *ctx) {
CFStringRef modeName = (CFStringRef)value;
CFRunLoopRef rl = (CFRunLoopRef)(((CFTypeRef *)ctx)[0]);
CFTypeRef item = (CFTypeRef)(((CFTypeRef *)ctx)[1]);
if (CFGetTypeID(item) == CFRunLoopSourceGetTypeID()) {
CFRunLoopRemoveSource(rl, (CFRunLoopSourceRef)item, modeName);
} else if (CFGetTypeID(item) == CFRunLoopObserverGetTypeID()) {
CFRunLoopRemoveObserver(rl, (CFRunLoopObserverRef)item, modeName);
} else if (CFGetTypeID(item) == CFRunLoopTimerGetTypeID()) {
CFRunLoopRemoveTimer(rl, (CFRunLoopTimerRef)item, modeName); }}Copy the code
The code to add Mode to CommonModes is as follows. Adding the target Mode to CommonModes means adding all monitored objects from the RunLoop’s _commonModeItems to the target Mode.
void CFRunLoopAddCommonMode(CFRunLoopRef rl, CFStringRef modeName) {
CHECK_FOR_FORK(a);if (__CFRunLoopIsDeallocating(rl)) return;
__CFRunLoopLock(rl);
if (!CFSetContainsValue(rl->_commonModes, modeName)) {
CFSetRef set = rl->_commonModeItems ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModeItems) : NULL;
CFSetAddValue(rl->_commonModes, modeName);
if (NULL! = set) { CFTypeRef context[2] = {rl, modeName};
// Core operation 1: add all objects from _commonModeItems to the target Mode
CFSetApplyFunction(set, (__CFRunLoopAddItemsToCommonMode), (void *)context);
CFRelease(set); }}else {
}
__CFRunLoopUnlock(rl);
}
static void __CFRunLoopAddItemsToCommonMode(const void *value, void *ctx) {
CFTypeRef item = (CFTypeRef)value;
CFRunLoopRef rl = (CFRunLoopRef)(((CFTypeRef *)ctx)[0]);
CFStringRef modeName = (CFStringRef)(((CFTypeRef *)ctx)[1]);
if (CFGetTypeID(item) == CFRunLoopSourceGetTypeID()) {
CFRunLoopAddSource(rl, (CFRunLoopSourceRef)item, modeName);
} else if (CFGetTypeID(item) == CFRunLoopObserverGetTypeID()) {
CFRunLoopAddObserver(rl, (CFRunLoopObserverRef)item, modeName);
} else if (CFGetTypeID(item) == CFRunLoopTimerGetTypeID()) {
CFRunLoopAddTimer(rl, (CFRunLoopTimerRef)item, modeName); }}Copy the code
In summary, adding a monitoring object to CommonModes means that the RunLoop running in any Mode of _commonModes can trigger the monitoring object. This effect is possible because of three factors:
- When a target object is added to CommonModes, the target object is added to all Modes of the CommonModes.
- When adding a target object to CommonModes, the target object is added to the RunLoop’s commonModeItems;
- When the target Mode is added to CommonModes, all objects in commonModeItems are added to the target Mode.
conclusion
This article has learned how to add Modes, add and delete monitoring objects, and add CommonModes. The details of Timer management and the core logic of RunLoop (RunLoopRun) will be studied in the next section. This may be a little confusing, but the final drawing will help you understand (colored boxes and lines indicate the objects or relationships introduced during the operation) :