What is RunLoop?

RunLoop is an important knowledge in iOS/Mac OS development. It runs through the entire process of running an application. It is part of the threading infrastructure and is a mechanism to ensure that threads loop through events without exiting. It also manages the events that threads need to handle, keeping them busy when they’re busy and dormant when they’re not.

Each thread has an associated RunLoop object. Runloops for child threads need to be opened manually, and runloops for the main thread are automatically opened by the system as part of application startup.

IOS /Mac OS provides two objects, NSRunLoop and CFRunLoopRef, to help us configure and manage runloops for threads. CFRunLoopRef provides a pure C implementation and thread-safe API; NSRunLoop is an object-oriented API wrapped around CFRunLoopRef that is not thread-safe.

RunLoop in relation to threads

Apple does not recommend creating RunLoop objects ourselves, but we can get RunLoop objects for a particular thread in the following ways:

[NSRunLoop currentRunLoop];
[NSRunLoop mainRunLoop];
//CoreFoundation
CFRunLoopGetMain(a);CFRunLoopGetCurrent(a);Copy the code

CoreFoundation is open source, and we can look at CFRunLoopRef’s implementation of CFRunLoopGetMain and CFRunLoopGetCurrent:

CFRunLoopRef CFRunLoopGetMain(void) {
    CHECK_FOR_FORK();
    static CFRunLoopRef __main = NULL; // no retain needed
    // pthread_main_thread_np() gets the main thread
    if(! __main) __main = _CFRunLoopGet0(pthread_main_thread_np());// no CAS needed
    return __main;
}

CFRunLoopRef CFRunLoopGetCurrent(void) {
    CHECK_FOR_FORK();
    CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
    if (rl) return rl;
    // pthread_self() gets the current thread
    return _CFRunLoopGet0(pthread_self());
}
/ / / global ` Dictionary `
static CFMutableDictionaryRef __CFRunLoops = NULL;
/// The lock needed to access 'Dictionary'
static CFLock_t loopsLock = CFLockInit;
// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    if (pthread_equal(t, kNilPthreadT)) {
	t = pthread_main_thread_np();
    }
    __CFLock(&loopsLock);
    /// 'Dictionary' does not exist
    if(! __CFRunLoops) { __CFUnlock(&loopsLock);/// create the local variable 'Dictionary'
	CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0.NULL, &kCFTypeDictionaryValueCallBacks);
        // create the main thread 'RunLoop' object.
	CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
        /// The actual address of the main thread object, with the address 'Key' and the address 'mainLoop' as' Value 'stored in the local variable' Dictionary '
	CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
        /// Write the value of a dict variable to a global dictionary '__CFRunLoops'
	if(! OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
	    CFRelease(dict);
	}
	CFRelease(mainLoop);
        __CFLock(&loopsLock);
    }
    // get the thread 'RunLoop' from the global dictionary '__CFRunLoops'
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFUnlock(&loopsLock);
    /// If the thread 'RunLoop' does not exist
    if(! loop) { CFRunLoopRef newLoop = __CFRunLoopCreate(t); __CFLock(&loopsLock); loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));if(! loop) {// create 'RunLoop', get the actual address of the thread object,
        /// store this address as' Key 'and newLoop as' Value' in the global variable '__CFRunLoops'
	    CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
	    loop = newLoop;
	}
        // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
        __CFUnlock(&loopsLock);
	CFRelease(newLoop);
    }
    /// is the current thread
    if (pthread_equal(t, pthread_self())) {
        // Store the 'RunLoop' object with '__CFTSDKeyRunLoop' as' key 'to the thread's local (private) data space,
        /// Destructor is NULL
        _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
        /// 'cfinternal. h' sets enumeration '__CFTSDKeyRunLoop' = 10 and '__CFTSDKeyRunLoopCntr' = 11
        /// If thread TSD, enumerate '__CFTSDKeyRunLoopCntr' corresponding to 'slot' has no value
        if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
        // save 'PTHREAD_DESTRUCTOR_ITERATIONS-1' and set the destructor '__CFFinalizeRunLoop',
        /// The purpose of this is to implement the destruction of 'RunLoop' when the thread is destroyed
            _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS- 1), (void(*) (void *))__CFFinalizeRunLoop);
       /// How to do this? Keep reading to find out!}}return loop;
}

// the code '_CFGetTSD' and '_CFSetTSD' are implemented as follows:
///TSD: Thread Specific Data
typedef struct __CFTSDTable {
    uint32_t destructorCount;
    /// 'uintptr_t' store pointer, unsigned integer type,
    // The number of arrays is' CF_TSD_MAX_SLOTS '
    uintptr_t data[CF_TSD_MAX_SLOTS];
    tsdDestructor destructors[CF_TSD_MAX_SLOTS];
} __CFTSDTable;

// For the use of CF and Foundation only
CF_EXPORT void *_CFGetTSD(uint32_t slot) {
    // Get or initialize a thread local storage,It is created on demand
    __CFTSDTable *table = __CFTSDGetTable();
    / /...
    uintptr_t *slots = (uintptr_t *)(table->data);
    return (void *)slots[slot];
}

// For the use of CF and Foundation only
CF_EXPORT void *_CFSetTSD(uint32_t slot, void *newVal, tsdDestructor destructor) {
    /// Get or initialize a thread local storage,It is created on demand
    __CFTSDTable *table = __CFTSDGetTable();
    / / /...
    void *oldVal = (void *)table->data[slot];
    / / /...
    table->data[slot] = (uintptr_t)newVal;
    /// destructor correlation
    table->destructors[slot] = destructor;
    return oldVal;
}
// Get or initialize a thread local storage. It is created on demand.
static __CFTSDTable *__CFTSDGetTable() {
   // get thread data by 'CF_TSD_KEY'
    __CFTSDTable *table = (__CFTSDTable *)__CFTSDGetSpecific();
    // Make sure we're not setting data again after destruction.
    if (table == CF_TSD_BAD_PTR) {
        return NULL;
    }
    // Create table on demand
    if(! table) {// This memory is freed in the finalize function
        table = (__CFTSDTable *)calloc(1.sizeof(__CFTSDTable));
        // Windows and Linux have created the table already, we need to initialize it here for other platforms. On Windows, the cleanup function is called by DllMain when a thread exits. On Linux the destructor is set at init time.
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
        // initialize a key 'CF_TSD_KEY' and associate the destructor
        /// 'CF_TSD_KEY' = 55, the value of this Key can be shared between different threads, but the corresponding value of this Key is different.
        /// each thread has its own TSD, and they share the key CF_TSD_KEY
        // The thread is destroyed when the Apple system calls:
        /// `_pthread_exit` -> `_pthread_tsd_cleanup` -> 
        ///`_pthread_tsd_cleanup_new`->`_pthread_tsd_cleanup_key`
        When a thread is destroyed, the associated destructor '__CFTSDFinalize' will be called to ensure that the private data corresponding to the thread can also be destroyed
        _pthread_tsd_cleanup_key
        / / / function [details] (https://github.com/apple/darwin-libpthread/blob/main/src/pthread_tsd.c)
        pthread_key_init_np(CF_TSD_KEY, __CFTSDFinalize);
#endif
        // Specify the data to be stored for 'CF_TSD_KEY'
        __CFTSDSetSpecific(table);
    } 
    return table;
}

/// Destroy the TSD corresponding to the thread
static void __CFTSDFinalize(void *arg) {
    / / /...
    __CFTSDTable *table = (__CFTSDTable *)arg;
    // run through all slots such as' __CFTSDKeyRunLoop 'and' __CFTSDKeyRunLoopCntr '
    for (int32_t i = 0; i < CF_TSD_MAX_SLOTS; i++) {
        if (table->data[i] && table->destructors[i]) {
            uintptr_t old = table->data[i];
            table->data[i] = (uintptr_t)NULL;
           // When I = 11 = '__CFTSDKeyRunLoopCntr' is traversed, call '__CFFinalizeRunLoop' to release 'RunLoop'
            table->destructors[i]((void*)(old)); }}if (table->destructorCount == PTHREAD_DESTRUCTOR_ITERATIONS - 1) {    // On PTHREAD_DESTRUCTOR_ITERATIONS-1 call, destroy our data
        free(table);
        / / /...
        __CFTSDSetSpecific(CF_TSD_BAD_PTR);
        return; }}Copy the code

The _CFGetTSD and _CFSetTSD source codes can be viewed here.

Conclusion:

  1. RunLoopThere is a one-to-one correspondence between threads
  2. When the thread needs to get the correspondingRunLoopIs createdRunLoopobject
  3. The thread is destroyed when it is destroyedRunLoopobject

Related classes of RunLoop

Five runloop-related constructs in CoreFoundation:

CFRunLoopRef / / runLoop object
CFRunLoopModeRef // The mode in which runLoop runs
CFRunLoopTimerRef// Time based trigger
CFRunLoopSourceRef// Event source, source0: custom event input source and source1: Event source based on Mach kernel port
CFRunLoopObserverRef // The observer that listens to the runLoop status
Copy the code

The relationship between them is as follows:

You can print [NSRunLoop currentRunLoop] or view the structure definitions of CFRunLoopRef and CFRunLoopModeRef. The structure of both is defined as follows:

///CFRunLoop.c
typedef struct __CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoopMode {
    / /...
    CFStringRef _name;
    / /...
    CFMutableSetRef _sources0; // <Set>
    CFMutableSetRef _sources1;// <Set>
    CFMutableArrayRef _observers; // <Array>
    CFMutableArrayRef _timers; // <Array>
    / /...
};
/// cfrunloop. h type renaming
typedef struct __CFRunLoop * CFRunLoopRef;
/ / / CFRunLoop. C structure
struct __CFRunLoop {
    / /..
    CFMutableSetRef _commonModes; // <Set> String UITrackingRunLoopMode/kCFRunLoopDefaultMode
    CFMutableSetRef _commonModeItems;// <Set> observer/source/timer
    CFRunLoopModeRef _currentMode; // The current running mode
    CFMutableSetRef _modes; // Built-in modes;
    / /...
};
Copy the code

The RunLoop mode

This Mode will be set to _currentMode. Only source0 and source1 associated with this Mode will be processed. Only observers associated with this Mode receive notifications.

/// 'RunLoop' specifies' Mode 'to run
CFRunLoopRunResult CFRunLoopRunInMode(CFRunLoopMode mode, CFTimeInterval seconds, Boolean returnAfterSourceHandled);
Copy the code

During the running of the program, events based on time, system and user are processed. These events have different priorities during the running of the program. In order to meet the application layer’s management of these events according to their priorities, the system uses RunLoopMode to group these events and then sends them to RunLoop for management. In addition to the default and common schemas defined by the system, we can also customize the schema, but the custom schema must have associated events, otherwise the custom schema is meaningless.

_commonModeItems and _commonModes are the implementation logic behind kCFRunLoopCommonModes (NSRunLoopCommonModes). Once again, we expect some events to be handled by multiple modes simultaneously, so we place these events (sources/timers/observers) in _commonModeItems, and mark multiple modes that need to be handled simultaneously in the _commonModes set. When events specify kCFRunLoopCommonModes to be added, they are first added to _commonModeItems, and then all events in _commonModeItems are appended to the modes already marked in _commonModes.

// add a 'Mode' to RunLoop's 'commonMode' set. Once added, it cannot be removed. Just add
void CFRunLoopAddCommonMode(CFRunLoopRef rl, CFRunLoopMode mode);
Copy the code

Example: The main thread runs in kCFRunLoopDefaultMode by default. When we swipe ScrollView, we switch to UITrackingRunLoopMode. The _commonModes of the main thread RunLoop include both modes by default. In the development of the main thread will encounter a timer, will be affected by the view sliding problem, solution:

[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
Copy the code

Adding timers to the NSRunLoopCommonModes of the NSRunLoop object will eventually add timers to kCFRunLoopDefaultMode and UITrackingRunLoopMode. Of course, you can also add them to both modes.

To understand how _commonModeItems and _commonModes work, see CoreFoundation CFRunLoopAddTimer.

void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) {    
    / / /...
    if (modeName == kCFRunLoopCommonModes) { // whether 'kCFRunLoopCommonModes' is available
        /// take the _commonModes of Runloop
	CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
	if (NULL == rl->_commonModeItems) {
            / / / to create ` _commonModeItems ` < Set >
	    rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
	}
        /// Add timer to '_commonModeItems'
	CFSetAddValue(rl->_commonModeItems, rlt);
	if (NULL! =set) { / / ` _commonModes ` has value
	    CFTypeRef context[2] = {rl, rlt};
	    /* add new item to all common-modes */
            /// call this method for each element in the Set collection
	    CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
	    CFRelease(set); }}else {
        /// not 'kCFRunLoopCommonModes', check whether' runloop 'modes have any
	CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true);
	if (NULL! = rlm) {if (NULL == rlm->_timers) { // create an array for 'timer'
                CFArrayCallBacks cb = kCFTypeArrayCallBacks;
                cb.equal = NULL;
                rlm->_timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &cb); }}/// Mode is available, and the timer object modes do not have this mode
	if (NULL! = rlm && ! CFSetContainsValue(rlt->_rlModes, rlm->_name)) {/ / /...
            if (NULL == rlt->_runLoop) {
		rlt->_runLoop = rl;
  	    } else if(rl ! = rlt->_runLoop) {/ /...
                // The runloop associated with the timer is inconsistent with the current runloop
		return;
	    }
            /// Timer modes add the mode name
  	    CFSetAddValue(rlt->_rlModes, rlm->_name);
            Machport and MachMSG are used to trigger timer events
            __CFRepositionTimerInMode(rlm, rlt, false);
            / / /...
	}
        / / /...
    }
    / / /..
}

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);//add source
    } else if (CFGetTypeID(item) == CFRunLoopObserverGetTypeID()) {
	CFRunLoopAddObserver(rl, (CFRunLoopObserverRef)item, modeName); // add observer
    } else if (CFGetTypeID(item) == CFRunLoopTimerGetTypeID()) {
	CFRunLoopAddTimer(rl, (CFRunLoopTimerRef)item, modeName); // add timer}}Copy the code

The CoreFoundation functions that add and remove events to and from the specified RunLoopMode are:

//Source
void CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFRunLoopMode mode);
void CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFRunLoopMode mode);
//Timer
void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFRunLoopMode mode);
void CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFRunLoopMode mode);
//Observer
void CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFRunLoopMode mode);
void CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFRunLoopMode mode);
Copy the code

The operation of the RunLoop

When the application starts, the main thread RunLoop is started using the UIApplicationMain function.

When the program is started, the function’s call stack is found to be calledCFRunLoopRunSpecific; andModeSwitch between passLLDBDebugging mode:b CFRunLoopRunSpecificb __CFRunLoopRun, found will also call the method, source analysis is as follows:

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {
    /// process check
    CHECK_FOR_FORK();
    /// Whether to destroy
    if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
    / / / the mutex
    __CFRunLoopLock(rl);
    // Find the modes of 'modeName' from 'runloop'
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
    /// If 'currentMode' is empty, return 'kCFRunLoopRunFinished'
    if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
        /// is there a problem with the code? Hiding something?
	Boolean did = false;
	if (currentMode) __CFRunLoopModeUnlock(currentMode);
	__CFRunLoopUnlock(rl);
	return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
    }
    /// Volatile tells the compiler to read from the address of the variable each time
    /// '_per_run_data' stores the current RL status
    volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
    CFRunLoopModeRef previousMode = rl->_currentMode;
    rl->_currentMode = currentMode;
    int32_t result = kCFRunLoopRunFinished;
    // when 'CurrentMode' is in 'kCFRunLoopEntry',
    /// Notify the observer of the current 'Mode' via '__CFRunLoopDoObservers'
    /// _observerMask sets the state that RLO needs to listen to
    ///1. Runloop is in 'kCFRunLoopEntry', notifying runloopmode->observers that runloop is about to enter
	if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
        /// 'RunLoop' actually runs the logic
	result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
         /// when 'CurrentMode' is in 'kCFRunLoopExit',
    /// Notify the observer of the current 'Mode' via '__CFRunLoopDoObservers'
	if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
        __CFRunLoopModeUnlock(currentMode);
        __CFRunLoopPopPerRunData(rl, previousPerRun);
	rl->_currentMode = previousMode;
    __CFRunLoopUnlock(rl);
    return result;
}

/// The core principle of RunLoop
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    /// TSR: time since repair
    uint64_t startTSR = mach_absolute_time();
    // Check whether 'RunLoop' or 'RLM ->_stopped' has been stopped, then return kCFRunLoopRunStopped '.
    / / /...
    // declare mach_port, when (rL is rl&rlm->name in rL -> commonModes), to hold 'mach_port' associated with the runLoop of the main queue. Handle the 'runloop' kernel event
    mach_port_name_t dispatchPort = MACH_PORT_NULL;
    Boolean libdispatchQSafe = pthread_main_np() && ((HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL== previousMode) || (! HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY &&0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ)));
    if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name)) 
        dispatchPort = _dispatch_get_main_queue_port_4CF();
    // Set the port number corresponding to the queue associated with Mode (timer queue)
#if USE_DISPATCH_SOURCE_FOR_TIMERS
    mach_port_name_t modeQueuePort = MACH_PORT_NULL;
    if (rlm->_queue) {
        modeQueuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue);
        / / /...
    }
#endif
    If (seconds>0&&seconds<=TIMER_INTERVAL_LIMIT), start the GCD timer. In other cases, the GCD timer will timeout immediately or not
    dispatch_source_t timeout_timer = NULL;
    / / /...
	timeout_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0.0, queue);
       / / /...
	dispatch_source_set_event_handler_f(timeout_timer, __CFRunLoopTimeout);
       / / /...
        dispatch_resume(timeout_timer);
        
     Boolean didDispatchPortLastTime = true;
    /// start the do-while loop when retVal! The loop stops when = 0
    int32_t retVal = 0;
    do {
/ / /...
        //// declare the MSg_buffer array
        uint8_t msg_buffer[3 * 1024];
/ / /...
        RLM waits for a collection of mach_ports to receive messages from Mach
	__CFPortSet waitSet = rlm->_portSet;
        /// Cancel rL ignore wake up to receive wake up messages
        __CFRunLoopUnsetIgnoreWakeUps(rl);
        ///2.runloop is in 'kCFRunLoopBeforeTimers', notifying runloopmode->observers that runloop is about to trigger a timer callback
        if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        ///3.runloop is in 'kCFRunLoopBeforeSources', notifying runloopmode->observers that runloop is about to trigger the Source0(non-mach_port) callback
        if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
        // Execute runloop to add blocks to runloop by 'struct _block_item *_blocks_head, _blocks_tail';
        // finally call '__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__'
	__CFRunLoopDoBlocks(rl, rlm);
        /// 4. Execute the custom 'source0' event and finally call '__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__'
        // `stopAfterHandle`A flag indicating whether the run loop should exit after processing one source 
        // If rL times out immediately or source0 has been processed (RL exits)
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        if (sourceHandledThisLoop) {/// the source0 process is complete and the added block is executed again
            __CFRunLoopDoBlocks(rl, rlm);
	}
        
        Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
        // mach_port of main thread 'runloop' is valid and not first allocated
        if(MACH_PORT_NULL ! = dispatchPort && ! didDispatchPortLastTime) {/ / /...
            msg = (mach_msg_header_t *)msg_buffer;
            / / / 5. ` thread ` open ` for (;;) 'Loop, wait,
            /// wait for 'mach_msg' to fetch information from rL's 'dispatchPort', if it succeeds, go to source1.
            if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
                / / / MSG processing
                goto handle_msg;
            }
            / / /...
        }
        didDispatchPortLastTime = false;
        /// If rL does not exit && is in 'kCFRunLoopBeforeWaiting' state
        ///6. Runloop is in 'kCFRunLoopBeforeWaiting', notifying runloopmode->observers that runloop is about to enter sleep
	if(! poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);/// Set the state of runloop to sleep, where set to 1
        ///Bit 0 of the base reserved bits is used for stopped state
        ///Bit 1 of the base reserved bits is used for sleeping state
        ///Bit 2 of the base reserved bits is used for deallocating state
	__CFRunLoopSetSleeping(rl);
        /// join RLM's WaitSet
        __CFPortSetInsert(dispatchPort, waitSet);
        / / /...
        /// Set the start time of RL sleep. Rl exits to 0 or the current absolute time
        CFAbsoluteTime sleepStart = poll ? 0.0 : CFAbsoluteTimeGetCurrent(a);/ / /...
        If (TIMEOUT_INFINITY == timeout) {CFRUNLOOP_SLEEP(); ///7. }
        /// let the thread go to sleep and wait to be awakened by the mach_msg function
        msg = (mach_msg_header_t *)msg_buffer;
        Poll = false Indicates that the RL has not stopped, waitSet has not timed out, and waitSet has timer port
        __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
        MacOS will loop through RLM ->queue until all done
    / / /...
        /// rL is awakened, and the sleep time of RL is calculated
        rl->_sleepTime += (poll ? 0.0 : (CFAbsoluteTimeGetCurrent() - sleepStart));
        // remove from RLM's waitSet (portSet)
        __CFPortSetRemove(dispatchPort, waitSet);
        /// RL has been woken up, so rL is set to ignore wake up messages
        __CFRunLoopSetIgnoreWakeUps(rl);
        // user callouts now OK again
        // Remove sleeping from RL
	__CFRunLoopUnsetSleeping(rl);
        // if rL does not exit && is in 'kCFRunLoopAfterWaiting' state
        ///8. Runloop is in 'kCFRunLoopAfterWaiting', notifying runloopmode->observers that runloop is about to wake up
	if(! poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);/// a message from mach_port will jump to this place
        handle_msg:;
        /// RL has been woken up, so rL is set to ignore wake up messages
        __CFRunLoopSetIgnoreWakeUps(rl);
        / / /...
        ///9. Wake up to process the event
        /// '__CFRunLoopServiceMachPort' succeeds in calling mach_msg, setting the value of livePort to the port from which the message was sent
        if (MACH_PORT_NULL == livePort) {
            CFRUNLOOP_WAKEUP_FOR_NOTHING(a);///// livePort is empty, do nothing
            // handle nothing
        } else if (livePort == rl->_wakeUpPort) {// wake up rL by calling 'CFRunLoopWakeUp'
            CFRUNLOOP_WAKEUP_FOR_WAKEUP(a);//
            // do nothing on Mac OS
        }
        9.1 Being woken up by the timer, processing the timer event
        /// the GCD Timer wakes up
#if USE_DISPATCH_SOURCE_FOR_TIMERS
        else if(modeQueuePort ! = MACH_PORT_NULL && livePort == modeQueuePort) {CFRUNLOOP_WAKEUP_FOR_TIMER(a);// if not, reset the next trigger time
            if(! __CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {// Re-arm the next timer, because we apparently fired early__CFArmNextTimerInMode(rlm, rl); }}#endif
        /// is awakened by MK Timer
#if USE_MK_TIMER_TOO
        else if(rlm->_timerPort ! = MACH_PORT_NULL && livePort == rlm->_timerPort) {CFRUNLOOP_WAKEUP_FOR_TIMER(a);if(! __CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {// Re-arm the next timer__CFArmNextTimerInMode(rlm, rl); }}#endif
       9.2 Processing block events from Dispatch to mainQueue
        else if (livePort == dispatchPort) {
           /// DISPATCH wakes up the runloop
            CFRUNLOOP_WAKEUP_FOR_DISPATCH(a);/// thread Data uses' __CFTSDKeyIsInGCDMainQ 'as key to store 6
            _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6.NULL);
            /// '_dispatch_main_queue_callback_4CF', processing MSG
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
            /// thread Data stores 0 with '__CFTSDKeyIsInGCDMainQ' as key to determine the value of 'libdispatchQSafe' at the start of the function.
            _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0.NULL);
            / / /..
            sourceHandledThisLoop = true;
            didDispatchPortLastTime = true;
        } else {
            ///9.3 is awakened by source1 based on mach_port to handle this event
            CFRUNLOOP_WAKEUP_FOR_SOURCE(a);/ / /...
            RLM ->_portToV1SourceMap
            CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
            if (rls) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
		mach_msg_header_t *reply = NULL;
                /// Handle Source1 by calling '__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__'
		sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
		if (NULL! = reply) {/// after source1 is processed, a message reply is performed if a message needs to be replied
		    (void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
		    CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);
		}
                / / /...
#endif
	    }
            / / /...
        } 
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
        if(msg && msg ! = (mach_msg_header_t *)msg_buffer) free(msg);#endif
        //// add runloop blocks
	__CFRunLoopDoBlocks(rl, rlm);
        
	if (sourceHandledThisLoop && stopAfterHandle) {/// the source process is complete & the runloop needs to be stopped
	    retVal = kCFRunLoopRunHandledSource;
        } else if (timeout_context->termTSR < mach_absolute_time()) {/// Time out
            retVal = kCFRunLoopRunTimedOut;
	} else if (__CFRunLoopIsStopped(rl)) {/// Set the RL status to STOPPED using the 'CFRunLoopStop' function
            __CFRunLoopUnsetStopped(rl);
	    retVal = kCFRunLoopRunStopped;
	} else if (rlm->_stopped) {///runLoopMode has been stopped
	    rlm->_stopped = false;
	    retVal = kCFRunLoopRunStopped;
	} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) { / / / RKM is empty,
            /// There is no timer or source0 or source1 or RL that has no blocks to execute and is not the main queue
	    retVal = kCFRunLoopRunFinished;
	}
        / /...
    } while (0 == retVal);
     / / /...
    return retVal;
}

Copy the code

Inside the RunLoop is a do-while loop that keeps the thread running, receiving events, and processing them; RunLoop defines states that, when run under a particular RunLoopMode, can send messages to observers registered under that Mode;

typedef CF_OPTIONS(CFOptionFlags.CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),/ / / into the RunLoop
    kCFRunLoopBeforeTimers = (1UL << 1),///RunLoop is about to trigger timer events
    kCFRunLoopBeforeSources = (1UL << 2),///RunLoop will handle the Source event
    kCFRunLoopBeforeWaiting = (1UL << 5),///RunLoop is about to go to sleep
    kCFRunLoopAfterWaiting = (1UL << 6),///RunLoop is about to wake up, but has not yet started processing the events that wake it up
    kCFRunLoopExit = (1UL << 7),/ / / from the RunLoop
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};
Copy the code

The apple documentation summarizes the execution sequence of RunLoop events as follows:

  1. Notify the observer,RunLoopIs about to enter
  2. Notify the observer,RunLoopThe triggerTimer
  3. Inform observerThe RunLoopTo deal withSource0(notmach_port)
  4. Trigger any non-port-based that is ready to triggerSource0The input source
  5. If based onmach_portThe input sourceSource1If it is ready to be triggered, go to Step 9Source1.
  6. Notify the observer,RunLoopAbout to go to sleep
  7. Let the thread go to sleep until one of the following events occurs:
    • Based on themach_portThe input sourceSource1Happen;
    • Timer trigger;
    • RunLoopSet up thetimeoutTake effect, the operation is about to end;
    • RunLoopTo be explicitly awakened, calledCFRunLoopWakeUp;
  8. Notify the observer,RunLoopAbout to be awakened
  9. After waking up, processing the event to be processed:
    • A user-defined timer starts, jumps to step 2, processes the timer event, and restarts the loop (2 ~ 9)
    • Handles port-based input sources and delivers received messages.
    • RunLoopAwakened explicitly but not timed out, skip step 2 and restart the loop (2 ~ 9)
  10. Notify the observer,RunLoopexit

When RunLoop exits, the running function returns the following enumerated values:

typedef CF_ENUM(SInt32, CFRunLoopRunResult) {
    kCFRunLoopRunFinished = 1,
    kCFRunLoopRunStopped = 2,
    kCFRunLoopRunTimedOut = 3,
    kCFRunLoopRunHandledSource = 4 
};
Copy the code
  1. SourceThe processing is complete and needs to stop immediatelyrunloopWhen to return tokCFRunLoopRunHandledSourceTo quitRunLoop;
  2. RunLoopSet up thetimeoutIn effect, returnkCFRunLoopRunTimedOutFrom the ` RunLoop;
  3. Explicitly callCFRunLoopStopFunction, setRunLoopThe status ofSTOPPEDTo return tokCFRunLoopRunStoppedTo quitRunLoop;
  4. RunLooprunningModeIf it is stopped, returnkCFRunLoopRunStoppedTo quitRunLoop;
  5. RunLooprunningModeNull, nonetimerorsource0orsource1Or,runloopThere is nothing to executeblockreturnkCFRunLoopRunFinishedTo quitRunLoop;

The final diagram summarizes the internal running logic of RunLoop as follows:

The application of the RunLoop

Oberserver

Structure of the Observer in CoreFoundation:

struct __CFRunLoopObserver {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;
    CFRunLoopRef _runLoop;
    CFIndex _rlCount; /// is added to Rl several times
    CFOptionFlags _activities;// The state of the RL needs to be observed
    CFIndex _order;When a status event is triggered, the observer is notified in turn. The smaller the value, the higher the priority
    CFRunLoopObserverCallBack _callout;	// A callback when a status event is triggered
    CFRunLoopObserverContext _context;/ / context
};
typedef struct __CFRunLoopObserver * CFRunLoopObserverRef;
Copy the code

Observers can be added to multiple modes of the RunLoop, and the same Mode can have multiple OberServers. When an event is triggered, observers are called back in the order of _order.

Example: Create a sliding view and add an Observer to the kCFRunLoopCommonModes of the primary RunLoop (NSRunLoopCommonModes) to watch the RunLoop switch.

/// Set the observer
- (void)addObserverForMainRunLoop {
    /* UITrackingRunLoopMode,GSEventReceiveRunLoopMode, kCFRunLoopDefaultMode,kCFRunLoopCommonModes */
    void *info = (__bridge_retained void *)self;
    CFRunLoopObserverContext context = {0,info,NULL.NULL.NULL};
   // A priority index indicating the order in which running loop observers 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 the increasing order of this parameter. Pass 0 unless there's a reason not to
    CFRunLoopObserverRef changeObserver = CFRunLoopObserverCreate(kCFAllocatorDefault ,
                                                                  kCFRunLoopAllActivities,
                                                                  YES.0,
                                                                  &_runLoopObserverCallBack,
                                                                  &context);
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), changeObserver, kCFRunLoopCommonModes);
    CFRelease(changeObserver);
}
/// callback function
void _runLoopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
    /// get the mode name
    NSString* mode = (__bridge NSString*)CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
    if (info) {// Bridge info to the OC object
    }
    switch (activity) {
        //do somthing...}}Copy the code

Source0

Source structure in CoreFoundation:

struct __CFRunLoopSource {
    CFRuntimeBase _base;
    uint32_t _bits;
    pthread_mutex_t _lock;
    CFIndex _order;	/ / / with the observer
    CFMutableBagRef _runLoops;
    union {
	CFRunLoopSourceContext version0; //source0
        CFRunLoopSourceContext1 version1; //source1
    } _context;
};
typedef struct {
    CFIndex	version;
    void *	info;
    const void* (*retain) (const void *info);
    void	(*release)(const void *info);
    CFStringRef	(*copyDescription)(const void *info);
    Boolean	(*equal)(const void *info1, const void *info2);
    CFHashCode	(*hash)(const void *info);
    void	(*schedule)(void *info, CFRunLoopRef rl, CFRunLoopMode mode);
    void	(*cancel)(void *info, CFRunLoopRef rl, CFRunLoopMode mode);
    void	(*perform)(void *info);
} CFRunLoopSourceContext;

typedef struct {
    / / /... Same as the first 7 attributes of 'CFRunLoopSourceContext'
#if TARGET_OS_OSX || TARGET_OS_IPHONE
    mach_port_t	(*getPort)(void *info);
    void *	(*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info);
#else
    / / /...
#endif
} CFRunLoopSourceContext1;
typedef struct __CFRunLoopSource * CFRunLoopSourceRef;
Copy the code

The Apple system defines a number of apis, the underlying implementation is based on Source0:

// subthread -> main thread
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;
- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;
// main thread -> child thread,
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array 
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait 
- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg 
Copy the code

However, when calling the main thread -> subroutine series methods, make sure that the child thread RunLoop is enabled, otherwise it will not take effect.

Example: Let’s simply emulate a performSelector from the main thread to child threads based on Souce0.

///1. Start the child thread
 _subthread = [[NSThread alloc]initWithTarget:self selector:@selector(subthreadOperation) object:nil];
///2. Add a 'Souce0' event to the child thread's method and start 'RL'
- (void)subthreadOperation {
    ///3. Save the runloop for the child thread
    _subRunLoop = CFRunLoopGetCurrent(a);///4. Create & add source0
    void *info = (__bridge_retained void*)self;
    CFRunLoopSourceContext context = {0,info,NULL.NULL.NULL.NULL.NULL,&schedule,&cancel,&perform};
    _source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
    CFRunLoopAddSource(_subRunLoop, _source, kCFRunLoopDefaultMode);
    RL / / / run
    [[NSRunLoop currentRunLoop] run];
}
/// callback related to Source0
void schedule(void *info, CFRunLoopRef rl, CFRunLoopMode mode) {
    ///source0 is added to the runloop of the child thread
}
void cancel(void *info, CFRunLoopRef rl, CFRunLoopMode mode) {
    ///source0 has been removed from the runloop of the child thread
}
void perform(void *info) {
   /// bridge info to get messages from the master thread
}
///5. The main thread raises the Source0 event
- (void)buttonAction:(id)sender {
    / / 5.1 trigger source
    CFRunLoopSourceSignal(_source);
    / / / 5.2 wake runLoop
    CFRunLoopWakeUp(_subRunLoop);
}
///6. Remove Source0 and exit child thread RL
Boolean contain = CFRunLoopContainsSource(_subRunLoop, _source, kCFRunLoopDefaultMode);
if (contain) {
    6.1 remove the source / /
    CFRunLoopRemoveSource(_subRunLoop, _source, kCFRunLoopDefaultMode);
    / / / 6.2 stop runLoop
    CFRunLoopStop(_subRunLoop);
}
Copy the code

Conclusion: Souce0 is nothing more than a wrapped function with context that needs to be actively triggered before it can be executed.

Source1

Sorce1 is an event based on mach_port, which is a kernel event. The kernel of Apple system is XNU hybrid kernel, including Mach kernel and BSD kernel. BSD mainly provides API standardized on Mach, while Mach is the core. Responsible for thread and process management, virtual memory management, process communication and messaging, task scheduling, etc.

Source1-based event passing, which relies on the kernel interface:

//System Trap/Function -- Sends and receives a message using the same mes-sage buffer
mach_msg_return_t mach_msg(mach_msg_header_t *msg, mach_msg_option_t option, mach_msg_size_t send_size, mach_msg_size_t rcv_size, mach_port_name_t rcv_name, mach_msg_timeout_t timeout, mach_port_name_t notify)
Copy the code

This method will implement event passing based on hardware exception: trap. The ultimate purpose of a trap is to provide a process-like interface, called a system call, between the user program and the kernel

In macOS, you can use Source1 based on mach_port to implement process communication.

Example: create a child thread, through Source1 to establish the main thread and child channel, achieve two-way communication.

///1. Start the child thread
 _subthread = [[NSThread alloc]initWithTarget:self selector:@selector(launchThreadWithPort:) object:nil];
///2. Add a 'Souce1' event to the child thread's method and start 'RL'
- (void)launchThreadWithPort:(NSPort*)port {
    @autoreleasepool {
        ///3. Create & add Source1
        void *info = (__bridge_retained void*)self;
        CFMessagePortContext portcontext = {0,info,NULL.NULL.NULL};
        Boolean shouldFreeInfo;
        CFMessagePortRef mach_port = CFMessagePortCreateLocal(kCFAllocatorDefault,  CFStringCreateWithFormat(kCFAllocatorDefault, NULL.CFSTR("com.qishare.sub_mach_port")), &_messagePortCallBack, &portcontext, &shouldFreeInfo);
        /// save the port and set up a two-way channel
        _subPort = mach_port;
        if(mach_port ! =NULL) {
            CFRunLoopSourceRef source1 = CFMessagePortCreateRunLoopSource(kCFAllocatorDefault, mach_port, 0);
            if(source1 ! =NULL) {
                CFRunLoopAddSource(CFRunLoopGetCurrent(), source1, kCFRunLoopDefaultMode);
                CFRunLoopRun(a);CFRelease(source1);
                CFRelease(mach_port); }}}}///3.1 'Source1' callback function
CFDataRef _messagePortCallBack(CFMessagePortRef local, SInt32 msgid, CFDataRef data, void *info) {
    // bridge info to get oc object...
    const UInt8 *buffer = CFDataGetBytePtr(data);
    CFIndex index = CFDataGetLength(data);
    CFStringRef messageref = CFStringCreateWithBytes(kCFAllocatorDefault, buffer, index, kCFStringEncodingUTF8, false);
    NSString *message = (__bridge_transfer NSString*)messageref;
    NSString *tip =  msgid == 1002 ? @" Main thread" : @" child thread";
    NSLog(@"%@ : %@, received data: %@", tip,[NSThread currentThread],message);
    return NULL;
}
5. Message sending: child thread -> main thread
[self performSelector:@selector(sendMsgToMainThread) onThread:_subthread withObject:nil waitUntilDone:NO];
//5.1 Build message 10002 represents the main thread -> child threads
NSData *data = ["Hello, I'm from the child thread ✈️" dataUsingEncoding:NSUTF8StringEncoding];
CFDataRef msgData = (__bridge_retained CFDataRef)data;
5.2 Sending a Message
CFMessagePortSendRequest(_mainPort, 1002, msgData, 0.1.0.0.NULL.NULL);
CFRelease(msgData);
6. Message sending: main thread -> child thread
CFStringRef message = CFSTR("Hello, I'm here for autonomic thread 🏡");
CFDataRef outData = CFStringCreateExternalRepresentation(kCFAllocatorDefault, message, kCFStringEncodingUTF8, 0);
/// send the message, 10001 represents the main thread -> child thread
CFMessagePortSendRequest(_subPort, 1001, outData, 0.1.0.0.NULL.NULL);
/// Release resources
CFRelease(outData);
CFRelease(message);
Copy the code

Timer

CoreFoundation timer structure:

struct __CFRunLoopTimer {
    CFRuntimeBase _base;
    uint16_t _bits;
    pthread_mutex_t _lock;
    CFRunLoopRef _runLoop;
    CFMutableSetRef _rlModes;
    CFAbsoluteTime _nextFireDate;
    CFTimeInterval _interval;		/* immutable */
    CFTimeInterval _tolerance;          /* mutable */
    uint64_t _fireTSR;			/* TSR units */
    CFIndex _order;			/* immutable */
    CFRunLoopTimerCallBack _callout;	/* immutable */
    CFRunLoopTimerContext _context;	/* immutable, except invalidation */
};
typedef struct CF_BRIDGED_MUTABLE_TYPE(NSTimer) __CFRunLoopTimer * CFRunLoopTimerRef;
Copy the code

The latency call API defined in The Apple system is based on Timer at the bottom:

- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray<NSRunLoopMode> *)modes;
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;
Copy the code

Example 1: Create a child thread to start a CoreFoundation timer:

///1. Start the child thread
  _subthread = [[NSThread alloc]initWithTarget:self selector:@selector(subthreadOperation) object:nil];
//2. Create & add timer under child thread
- (void)subthreadOperation {
   /// save the child thread 'RL'
    _subRunLoop = CFRunLoopGetCurrent(a);@autoreleasepool{__weak typeof(self)weakSelf = self;
        CFRunLoopTimerRef timer = CFRunLoopTimerCreateWithHandler(kCFAllocatorDefault, 0.1.0.0And ^ (CFRunLoopTimerRef timer) {
          /// Timer event
        });
        _cftimer = timer;
        CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopDefaultMode);
        CFRunLoopRun(a); }/// called when stopped
    NSLog(@runloop: I'm going to ruin, don't stop me! 😠");
}
///3. Stop timer & child thread RL
- (void)stopCFTimerLoop {
    / / / 3.1 remove ` timer `
    CFRunLoopRemoveTimer(_subRunLoop, _cftimer, kCFRunLoopDefaultMode);
    / / / stop RL 3.2
    CFRunLoopStop(_subRunLoop);
    CFRelease(_cftimer);
}
Copy the code

Example 2: Create a child thread to start an NSFoundation timer:

///1. Start the child thread
  _subthread = [[NSThread alloc]initWithTarget:self selector:@selector(subthreadOperation) object:nil];
//2. Create & add timer under child thread
- (void)subthreadOperation {
    if (_timer) {
        [_timer invalidate];
        _timer = nil;
    } else {
        _timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
           /// Timer event
        }];
        [[NSRunLoop currentRunLoop] addTimer:_timer forMode:NSDefaultRunLoopMode];
        ///MARK: The child thread Runloop exits when the timer stops.
        ///[[NSRunLoop currentRunLoop]run];
        /// This approach is recommended
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        NSLog(@runloop: I'm going to ruin, don't you stop me! 😠"); }}///3. Stop timer & child thread RL
- (void)stopTimerLoop {
    ///This method is the only way to remove a timer from an NSRunLoop object.
    [_timer invalidate];
    / / / stop
    CFRunLoopStop(_subRunLoop);    
}
Copy the code

Example 2 Enable RunLoop for child threads using [[NSRunLoop currentRunLoop]run]. RunLoop cannot exit without calling [_timer invalidate], while runMode:beforeDate: is possible. This ensures that we can exit the RunLoop if there are other event sources in the RunLoop and they are not removed.

RunMode :beforeDate: exits explicitly without removing events. [[NSRunLoop currentRunLoop]run] does not exit explicitly without removing events.

The child thread is alive

Child thread survival is essentially enabling RunLoop for child threads. However, before starting the RunLoop of the child thread, ensure that the RunLoop has at least one Timer, Souce0, or Source1.

The simplest way to stay alive:

///1. Start the child thread
  _subthread = [[NSThread alloc]initWithTarget:self selector:@selector(subthreadOperation) object:nil];
//2. Create & add timer under child thread
- (void)subthreadOperation {
    [[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
    _shouldKeepRunning = YES;
    do{[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
    } while (_shouldKeepRunning);   
}
/ / / 3. Stop
- (void)stopLoop {
    CFRunLoopStop(_subRunLoop);  
    _shouldKeepRunning = NO;
}
Copy the code

The resources

Developer.apple.com/library/arc…

Blog.ibireme.com/2015/05/18/…

Github.com/apple/darwi…

Opensource.apple.com/source/CF/C…