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_sources0Collection, if the target Sourcecontext.version0.scheduleIf 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_sources1Set, and update Mode_portToV1SourceMapA dictionary;
    • Update the target Source_runLoopsCollection (backreferences to all runloops with this Source added);
  • If the Source is Source0 and is marked “Trigger action when add succeeds” : triggerscontext.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_sources0Collection if the target Source’scontext.version0.cancelIf 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_sources1Set, and update Mode_portToV1SourceMapA dictionary;
    • Update the target Source_runLoopsThe collection;
  • If the Source is Source0 and marked “Operation required when removal is successful” : triggerscontext.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_runLoopNon-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_observersAccording to the array_orderIn descending order, insert the target Observer_observers;
    • Update target Mode_observerMask;
    • Update the target Observer_runLoopand_rlCount;

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_observersRemove;
    • Update the target Observer_runLoopand_rlCount;
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_runLoopNon-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_runLoopand_rlModes;
    • Update target Mode_timers(including the update target Mode next timing event trigger operation);

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_runLoopand_rlModes;
    • “Update the next timing event trigger for target Mode”;
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) :