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:
RunLoop
There is a one-to-one correspondence between threads- When the thread needs to get the corresponding
RunLoop
Is createdRunLoop
object - The thread is destroyed when it is destroyed
RunLoop
object
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 slide 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
; andMode
Switch between passLLDB
Debugging mode:b CFRunLoopRunSpecific
和 b __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:
- Notify the observer,
RunLoop
Is about to enter - Notify the observer,
RunLoop
The triggerTimer
- Inform observer
The RunLoop
To deal withSource0
(notmach_port
) - Trigger any non-port-based that is ready to trigger
Source0
The input source - If based on
mach_port
The input sourceSource1
If it is ready to be triggered, go to Step 9Source1
. - Notify the observer,
RunLoop
About to go to sleep - Put the thread to sleep until one of the following events occurs:
- Based on the
mach_port
The input sourceSource1
Happen; - Timer trigger;
RunLoop
Set up thetimeout
Take effect, the operation is about to end;RunLoop
To be explicitly awakened, calledCFRunLoopWakeUp
;
- Based on the
- Notify the observer,
RunLoop
About to be awakened - 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.
RunLoop
Awakened explicitly but not timed out, skip step 2 and restart the loop (2 ~ 9
)
- A user-defined timer starts, jumps to step 2, processes the timer event, and restarts the loop (
- Notify the observer,
RunLoop
exit
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
Source
The processing is complete and needs to stop immediatelyrunloop
When to return tokCFRunLoopRunHandledSource
To quitRunLoop
;RunLoop
Set up thetimeout
In effect, returnkCFRunLoopRunTimedOut
From the ` RunLoop;- Explicitly call
CFRunLoopStop
Function, setRunLoop
The status ofSTOPPED
To return tokCFRunLoopRunStopped
To quitRunLoop
; RunLoop
runningMode
If it is stopped, returnkCFRunLoopRunStopped
To quitRunLoop
;RunLoop
runningMode
Null, nonetimer
orsource0
orsource1
Or,runloop
There is nothing to executeblock
returnkCFRunLoopRunFinished
To 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;// 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…