Runloop is a basic concept in iOS. Other systems have similar Event loops and Event loops, and this key foundation is usually based on messaging. The key of realization is how to sleep the system to avoid resource occupation when the message has not arrived. When the message arrives, the system is awakened to perform the corresponding task.

Run loops are part of the fundamental infrastructure associated with threads. A run loop is an event processing loop that you use to schedule work and coordinate the receipt of incoming events. The purpose of a run loop is to keep your thread busy when there is work to do and put your thread to sleep when there is none.

This article gives an in-depth analysis of runloop in iOS, analyzes it in detail from the source point of view, and summarizes the common use posture of Runloop combined with the usage scenarios.

Basic concepts of Runloop

Runloop is an object that manages events and messages through an internally maintained event loop.

This is mainly the switch between the two operating system states: user state <===> kernel state. When there is no event/message, sleep to avoid resource occupation. When there is an event/message, immediately wake up to process the event.

The CFRunLoopRef is the C API in CoreFouncation that provides a fairly rich interface for Runloops, all thread-safe. NSRunLoop encapsulates the CFRunLoopRef and provides an object-oriented API that is not thread-safe.

thread

Runloop objects are stored in a global container, where key is the thread object and value is the Runloop object. A runloop corresponds to a thread. The runloop of each thread is automatically created when required. The currentRunloop interface ([NSRunLoop currentRunloop]) is invoked to create and enable a runloop. The main thread has runloop enabled in the main entry of the App, and the child thread does not create runloop by default. When the thread terminates, its corresponding runloop (if any) is destroyed.

Pthreads (the low-level C-based threading interface) are the counterpart of NSThreads (which provide object-oriented apis) and are the upper wrapper for Mach Threads.

Thread-local Storage: Each Thread maintains a key-value pair field that records information throughout the Thread’s execution, such as the Runloop object.

Mode

A run loop mode is a collection of input sources and timers to be monitored and a collection of run loop observers to be notified. You must be sure to add one or more input sources, timers, or run-loop observers to any modes you create for them to be useful. You use modes to filter out events from unwanted sources during a particular pass through your run loop.

A thread has a runloop, a runloop contains multiple modes, and a Mode contains multiple sources, timers, and observers. Runloop cannot switch Mode by itself. It must exit and re-specify Mode.

A Mode can mark itself as the Common attribute (by adding its ModeName to the runloop’s _commonModes collection). Whenever the contents of the Runloop change, the Runloop synchronizes the Source/Timer/Observer from the _commonModeItems collection to all modes with the Common property.

The common modes are DefaultMode and UITrackingMode. In addition, there are

  1. UIInitializationRunLoopMode just start the App when the first Mode, after the start don’t need to
  2. GSEventReceiveRunLoopMode for iOS system inside, the receiving system Mode of events, by GraphicsServices call, in the front CFRunLoopRunSpecific is usually not used.
  3. _kCFStreamBlockingOpenMode is CFStream scheduling network task using private mode, see CFStream.

There are two interfaces related to Mode: CFRunLoopAddCommonMode Is used to add Mode to Common, and CFRunLoopRunInMode makes runloop run in the specified Mode.

Mode Item

The Mode of a runloop contains multiple Mode items, including Source, Timer, and Observer. A Mode Item can also be added to multiple runloopmodes at the same time, that is, they are many-to-many. Therefore, a Mode Item (such as NSTimer) can be added to CommonModes (which is a collection that stores the names of multiple modes, DefaultMode and UITrackingMode in the main thread).

A runloop must exist with a Mode Item. If there is no Mode Item in the Mode, the runloop exits automatically. Create an NSTimer, NSPort, Source, that is, create a runloopMode item.

Source0

Source0 is used to receive user events such as UIEvent. Source0 has a public API that developers can use to create their own Source0 using the CFRunLoopSourceCreate function. UIEvent is a Source0 event, which can also be seen from the UI event breakpoint stack information:

This article will also analyze the runloop source code.

In combination with the previous RunLoop core running process, we can see that Source0(responsible for intra-app events, and the App is responsible for managing the trigger, such as the UITouch event) and Timer (also known as Timer Source, time-based trigger, NSTimer) are two different Runloop event sources (of course, Source0 is a class of the Input Source, which also includes a Custom Input Source that is issued manually by other threads). When the RunLoop is woken up by these events, it processes and calls the event handling methods (CFRunLoopTimerRef’s callback pointer and CFRunLoopSourceRef both contain the corresponding callback pointer).

Source1

Source1 is used by the operating system kernel and cannot be used by developers. Source1 is implemented based on the mach_msg principle, and the corresponding callback task must be triggered by reading messages in the Mach kernel’s message queue (Mach MSG) on a Mach port. It is usually used to listen on system ports, communicate messages with other threads, and actively wake up the runloop of the current thread.

When working on tasks, Source1 usually works with Source0, that is, assigning tasks to Source0 to execute. The UITouch event, for example, is initially handled by Source1, which then wraps the event and distributes it to Source0 for processing. This is crucial.

Timer

NSTimer is actually implemented based on Runloop, and its characteristics are greatly influenced by Runloop. For example, the holding relationship in NSTimer correlation is as follows:

Current RunLoop -> CFRunLoopMode -> sources array -> __NSCFTimer -> _NSTimerBlockTarget -> targetCopy the code

Observer

The Runloop object has six states (points in time) that can be monitored. Developers can add observers to these states to trigger callback tasks when they arrive, and this is where most of the use of Runloop actually occurs.

Some apis for Runloop

Runloop’s run method

  1. RunMode :beforeDate: runs a runloop on the specified Mode until the specified point in time. If no source or timer is added ahead of time, the method ends immediately and the thread’s runloop status is not affected (it will not start if the runloop is not started). If there is a source or timer, the function returns once the first input source has been processed or the specified time has elapsed. The runloop may not exit at this point because the system itself may have added some other source. Because this function runs runloop only once, it is often paired with a conditional loop of while (which controls whether the runloop continues) to implement certain functions, such as resident child threads. Timer is not part of the input source, so the timer callback method may fire multiple times before the function returns.
  2. RunUntilDate: causes the runloop to run on DefaultMode for a specified time, during which time the runloop processes all added input sources. In fact, this method implements its functionality by repeatedly calling runMode:beforeDate:, so some of the prerequisites of the runMode:beforeDate: method are also in effect here.
  3. Run: causes the runloop to run forever on DefaultMode, running an infinite loop to process input source or timer events and never exiting. This method also repeatedly calls runMode:beforeDate: to implement its functionality, so some of the prerequisites of the runMode:beforeDate: method are also in effect here. The AFN 2.x source code uses this method to keep the backend subthreads alive. This method is usually used with caution.

The most commonly used interface is runMode:beforeDate:, while it is recommended to use a global variable to control the running state of the runloop.

BOOL shouldKeepRunning = YES; // global
NSRunLoop *theRL = [NSRunLoop currentRunLoop];
while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
Copy the code

It is best to add another @Autoreleasepool for more efficient memory management.

The underlying data structure of Runloop

The underlying data structures associated with runloop include CFRunLoopRef, CFRunLoopModeRef, CFRunLoopSourceRef, and CFRunLoopObserverRef.

CFRunLoop

__CFRunLoop

struct __CFRunLoop {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;			/* locked for accessing mode list */
    __CFPort _wakeUpPort;			// used for CFRunLoopWakeUp
    Boolean _unused;
    volatile _per_run_data *_perRunData;              // reset for runs of the run loop
    pthread_t _pthread;
    uint32_t _winthread;
    CFMutableSetRef _commonModes;
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode;
    CFMutableSetRef _modes;
    struct _block_item* _blocks_head;
    struct _block_item* _blocks_tail;
    CFAbsoluteTime _runTime;
    CFAbsoluteTime _sleepTime;
    CFTypeRef _counterpart;
};
Copy the code

Here are some key fields that developers use on a daily basis:

  1. Pthread_t_pthread: indicates the thread corresponding to the runloop.
  2. CFMutableSetRef _commonModes: Container for storing strings (not runloopMode objects) corresponding to all modes marked common.
  3. CFMutableSetRef _commonModeItems: container for storing modeItem objects, corresponding to all mode items labeled common (source, timer, observer).
  4. CFRunLoopModeRef _currentMode: runloopMode object currently running.
  5. CFMutableSetRef _modes: Container for storing runloopMode objects.

The mode-related fields of runloop are pretty straightforward here. _modes, _commonModes, and _commonModeItems are all containers. This verifies that a runloop corresponds to multiple modes, and that a mode can have multiple items. If you want to switch mode while the runloop is running, you can only exit the runloop and specify another mode to enter. In this way, items in different modes are isolated from each other without affecting each other.

In addition, there are a few fields that are useful for understanding the runloop principle:

  1. CFRuntimeBase _base: Runloop can have some runtime features
  2. __CFPort _wakeUpPort: The port to wake up the runloop. This is the key to the runloop principle. CFRunLoopWakeUp can be triggered through port. More on that later.
  3. Volatile _per_run_data *_perRunData: This is the data stored by the runloop itself
  4. _blocks_head and _blocks_tail: a linked list that stores block objects. This is used by subsequent runloop execution tasks (__CFRunLoopDoBlocks())
  5. _runTime, _sleepTime: indicates the time associated with runloop

How do I create a __CFRunLoop object

We usually use [NSRunLoop currentRunLoop] or CFRunLoopGetCurrent() to get the runloop of the current thread, otherwise it will be created automatically.

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

/// Get the runloop of the current thread
CFRunLoopRef CFRunLoopGetCurrent(void) {
    CHECK_FOR_FORK();
    /// it is also similar to the global store thread corresponding runloop object.
    CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
    if (rl) return rl;
    return _CFRunLoopGet0(pthread_self());
}
Copy the code

For the main thread runloop object, use the static variable __main to store it; There is also a global store for runloop objects of child threads (note TSD here).

_CFRunLoopGet0

The _CFRunLoopGet0 function obtains its runloop object from the current thread pthread_t object, or creates a new runloop object if it does not have one. Once created, the runloop object is stored in __CFRunLoops, as well as in the TSD of the current thread.

/// global Dictionary, key is pthread_t, value is CFRunLoopRef.
static CFMutableDictionaryRef __CFRunLoops = NULL;
/// Access the lock for __CFRunLoops.
static CFLock_t loopsLock = CFLockInit;

// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
// Get the thread runloop based on pthread
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
    if (pthread_equal(t, kNilPthreadT)) {
	    t = pthread_main_thread_np();
    }
    __CFLock(&loopsLock);
    if(! __CFRunLoops) { __CFUnlock(&loopsLock); CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault,0.NULL, &kCFTypeDictionaryValueCallBacks);
        // Create a dictionary __CFRunLoops for the first time.
	    CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
	    CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
	    if(! OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
	        CFRelease(dict);
	    }
        /// this local variable mainLoop should be released. At this point, the CFDictionary has retained the mainLoop object.
	    CFRelease(mainLoop);
        __CFLock(&loopsLock);
    }

    // Get runloops for pthread_t from the dictionary __CFRunLoops.
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    __CFUnlock(&loopsLock);
    if(! loop) {// If not, create a runloop for the pthread.
	    CFRunLoopRef newLoop = __CFRunLoopCreate(t);
        __CFLock(&loopsLock);
        // If there is a target runloop in the dictionary, it is unlocked after the first time
	    loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
	    if(! loop) { CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop); loop = newLoop; }// don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
        __CFUnlock(&loopsLock);
        /// At this point, the Dictionary has retained the newLoop object
	    CFRelease(newLoop);
    }
    if (pthread_equal(t, pthread_self())) {
        /// This _CFSetTSD also stores runloop objects
        _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
        if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
            // register a callback __CFFinalizeRunLoop to destroy the runloop object when the thread is destroyed.
            _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS- 1), (void(*) (void*))__CFFinalizeRunLoop); }}return loop;
}
Copy the code

The __CFRunLoops static variable is a dictionary for storing runloop objects with key as thread object (pthread_t) and value as runloop object (CFRunLoopRef).

The double if judgment is worth noting

* OSAtomicCompareAndSwapPtrBarrier (NULL, dict, volatile (void *) & __CFRunLoops) will create the new temp dict assignment to __CFRunLoops, Is a Barrier operation and is used to ensure security.

Let’s start with a note:

if(! loop) { CFRunLoopRef newLoop = __CFRunLoopCreate(t); __CFLock(&loopsLock); loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));if(! loop) { 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);
}
Copy the code

Note the double if(! Loop), also for thread safety. Because in the multithreaded case, maybe both threads are going into the outer if condition. ***__CFLock(&loopsLock)*** allows only one thread to execute at a time, but does not cancel its execution. If there is no inner if(! Loop) may cause multiple threads to execute to loop = newLoop; This assignment operation. Therefore, this double-layer if judgment is essential, and is also strictly considered in many languages for scenarios such as singleton objects.

CFRunLoopRef newLoop = __CFRunLoopCreate(t); Create multiple Runloop objects. If the __CFLock (& loopsLock); If the runloop object is created only once in a multithreaded situation, what is the problem?

if(! loop) { __CFLock(&loopsLock); loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));if(! loop) { CFRunLoopRef newLoop = __CFRunLoopCreate(t); CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop); loop = newLoop; CFRelease(newLoop); }// don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
  __CFUnlock(&loopsLock);
}
Copy the code

I don’t think that’s possible. // Don’t release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it. Of course, the overhead of creating newLoop objects doesn’t really matter.

__CFRunLoopCreate

Most of the code in the _CFRunLoopGet0 function is easy to understand, except for the _CFGetTSD and _CFSetTSD, which are used to store the private data of the Thread object, as described below. The Runloop object of the Thread object is stored in this data. Moving on to the key __CFRunLoopCreate function, which takes only one argument, the pthread_t object.

static CFRunLoopRef __CFRunLoopCreate(pthread_t t) {
    CFRunLoopRef loop = NULL;
    CFRunLoopModeRef rlm;
    /// How to calculate the memory usage of runloop objects
    uint32_t size = sizeof(struct __CFRunLoop) - sizeof(CFRuntimeBase);
    /// Note: Runloop objects are also created during Runtime.
    loop = (CFRunLoopRef)_CFRuntimeCreateInstance(kCFAllocatorSystemDefault, CFRunLoopGetTypeID(), size, NULL);
    if (NULL == loop) {
	    return NULL;
    }
    // initialize _per_run_data
    (void)__CFRunLoopPushPerRunData(loop);
    __CFRunLoopLockInit(&loop->_lock);
    loop->_wakeUpPort = __CFPortAllocate();
    if (CFPORT_NULL == loop->_wakeUpPort) HALT;
    __CFRunLoopSetIgnoreWakeUps(loop);

    /// commonModes are indeed cfSets
    loop->_commonModes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
    // commonModes include DefaultMode by default
    CFSetAddValue(loop->_commonModes, kCFRunLoopDefaultMode);
    
    // all that remains is the initialization of the other fields
    loop->_commonModeItems = NULL;
    loop->_currentMode = NULL;
    loop->_modes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
    loop->_blocks_head = NULL;
    loop->_blocks_tail = NULL;
    loop->_counterpart = NULL;
    loop->_pthread = t;
#if DEPLOYMENT_TARGET_WINDOWS
    loop->_winthread = GetCurrentThreadId();
#else
    loop->_winthread = 0;
#endif
    rlm = __CFRunLoopFindMode(loop, kCFRunLoopDefaultMode, true);
    if (NULL! = rlm) __CFRunLoopModeUnlock(rlm);return loop;
}
Copy the code

uint32_t size = sizeof(struct __CFRunLoop) – sizeof(CFRuntimeBase); This one is mysterious…

Once the loop object is created, we populate its properties: _perRunData, _wakeUpPort, _commonModes, _modes, and so on.

CFSetAddValue(loop->_commonModes, kCFRunLoopDefaultMode); Causes DefaultMode to append the common flag as soon as the Runloop object is created.

rlm = __CFRunLoopFindMode(loop, kCFRunLoopDefaultMode, true); The DefaultMode of runloop is confirmed.

CFRuntimeBase and _CFRuntimeCreateInstance

Here are two lines of interesting code:

/// How to calculate the memory usage of runloop objects
uint32_t size = sizeof(struct __CFRunLoop) - sizeof(CFRuntimeBase);
/// Note: Runloop objects are also created during Runtime.
loop = (CFRunLoopRef)_CFRuntimeCreateInstance(kCFAllocatorSystemDefault, CFRunLoopGetTypeID(), size, NULL);
Copy the code

__CFRuntimeBase is the most basic structure of CoreFoundation, __CFBoolean, __CFString, __CFDate, __CFURL, __CFMachPort, __CFBundle, etc. all have this CFRuntimeBase _base; Field. The information stored in __CFRuntimeBase includes: Is it system default allocated memory, Fireing or repeats associated with runloop Observer, CFTypeID, is deallocing being destroyed, is DealLoced already destroyed, is it a custom reference count object, reference count, These are shown in the Runtime source code. For the moment, all data structures associated with runloop will have this CFRuntimeBase _base; Field, and the _base field will be used to store some information.

/* All CF "instances" start with this structure. Never refer to * these fields directly -- they are for CF's use and may  be added * to or removed or change format without warning. Binary * compatibility for uses of this struct is not guaranteed from * release to release. */
#if DEPLOYMENT_RUNTIME_SWIFT

typedef struct __attribute__(((aligned__(8))) __CFRuntimeBase {
    // This matches the isa and retain count storage in Swift
    uintptr_t _cfisa;
    uintptr_t _swift_rc;
    // This is for CF's use, and must match __NSCFType/_CFInfo layout
    _Atomic(uint64_t) _cfinfoa;
} CFRuntimeBase;

#define INIT_CFRUNTIME_BASE(...) {0, _CF_CONSTANT_OBJECT_STRONG_RC, 0x0000000000000080ULL}

#else

typedef struct __CFRuntimeBase {
    uintptr_t _cfisa;
#if TARGET_RT_64_BIT
    _Atomic(uint64_t) _cfinfoa;
#else
    _Atomic(uint32_t) _cfinfoa;
#endif
} CFRuntimeBase;

#if TARGET_RT_64_BIT
#define INIT_CFRUNTIME_BASE(...) {0, 0x0000000000000080ULL}
#else
#define INIT_CFRUNTIME_BASE(...) {0, 0x00000080UL}
#endif

#endif
Copy the code

The _CFRuntimeCreateInstance function is described as follows. It uses the CFAllocatorRef passed in by parameter 1 to create a CF instance. The creation of this field is complex and involves the various types of information stored in this field, so you can explore the Runtime source code.

CF_EXPORT CFTypeRef _CFRuntimeCreateInstance(CFAllocatorRef allocator, CFTypeID typeID, CFIndex extraBytes, unsigned char *category);
	/* Creates a new CF instance of the class specified by the * given CFTypeID, using the given allocator, and returns it. * If the allocator returns NULL, this function returns NULL. * A CFRuntimeBase structure is initialized at the beginning * of the returned instance. extraBytes is the additional * number of bytes to allocate for the instance (BEYOND that * needed for the CFRuntimeBase). If the specified CFTypeID * is unknown to the CF runtime, this function returns NULL. * No part of the new memory other than base header is * initialized (the extra bytes are not  zeroed, for example). * All instances created with this function must be destroyed * only through use of the CFRelease() function -- instances * must not be destroyed by using CFAllocatorDeallocate() * directly, even in the initialization or creation functions * of a class. Pass NULL for the category parameter.Copy the code

The state data of the runloop object _perRunData

_perRunData is separate data for each iteration of the runloop (run). Volatile prevents the compiler from optimizing itself. Refer to Volatile variables.

volatile _per_run_data *_perRunData;              // reset for runs of the run loop

typedef struct _per_run_data {
    uint32_t a;
    uint32_t b;
    uint32_t stopped;
    uint32_t ignoreWakeUps;
} _per_run_data;
Copy the code

The _per_run_data structure itself is very simple, which is used for a series of state markers:

  1. A flag indicating whether runloop is stopped corresponds to the _perRunData->stopped property (set to 0x53544F50 when stopped and 0x0 when running).
  2. Runloop whether to ignore the wakeup flag, corresponding to _perRunData->ignoreWakeUps (0x57414B45 if ignored, 0x0 if not ignored).
/* 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 */
// Create the _perRunData property of the runloop object, which returns the previous.
CF_INLINE volatile _per_run_data *__CFRunLoopPushPerRunData(CFRunLoopRef rl) {
    volatile _per_run_data *previous = rl->_perRunData;
    rl->_perRunData = (volatile _per_run_data *)CFAllocatorAllocate(kCFAllocatorSystemDefault, sizeof(_per_run_data), 0);
    rl->_perRunData->a = 0x4346524C;
    rl->_perRunData->b = 0x4346524C; // 'CFRL'
    rl->_perRunData->stopped = 0x00000000;
    rl->_perRunData->ignoreWakeUps = 0x00000000;
    return previous;
}

// Reassign the previous to it.
CF_INLINE void __CFRunLoopPopPerRunData(CFRunLoopRef rl, volatile _per_run_data *previous) {
    if (rl->_perRunData) CFAllocatorDeallocate(kCFAllocatorSystemDefault, (void *)rl->_perRunData);
    rl->_perRunData = previous;
}

CF_INLINE Boolean __CFRunLoopIsStopped(CFRunLoopRef rl) {
    return (rl->_perRunData->stopped) ? true : false;
}

CF_INLINE void __CFRunLoopSetStopped(CFRunLoopRef rl) {
    rl->_perRunData->stopped = 0x53544F50;	// 'STOP'
}

CF_INLINE void __CFRunLoopUnsetStopped(CFRunLoopRef rl) {
    rl->_perRunData->stopped = 0x0;
}

CF_INLINE Boolean __CFRunLoopIsIgnoringWakeUps(CFRunLoopRef rl) {
    return (rl->_perRunData->ignoreWakeUps) ? true : false;
}

CF_INLINE void __CFRunLoopSetIgnoreWakeUps(CFRunLoopRef rl) {
    rl->_perRunData->ignoreWakeUps = 0x57414B45; // 'WAKE'
}

CF_INLINE void __CFRunLoopUnsetIgnoreWakeUps(CFRunLoopRef rl) {
    rl->_perRunData->ignoreWakeUps = 0x0;
}
Copy the code

This C-style Push/Pop operation is used a lot. The runloop object is created using (void)__CFRunLoopPushPerRunData(loop); _per_RUN_data is initialized.

CFRunLoopMode

RunloopMode is a very important concept. Many data structures related to runloop are distinguished by runloopMode, that is, data in different Runloopmodes are isolated from each other.

NSMutableSet

  1. Default Mode: generally processes events such as timer, network, database, and I/O
  2. UITracking Mode: Specialized for handling UI events

Is the Mode switch also the name of push and pop functions?

__CFRunLoopMode

Struct CFRunLoopModeRef, CFRuntimeBase _base; This is also used when creating runloop objects.

A CFRunLoopModeRef object contains sources0, sources1, observers, timers, and portSet. Each Mode has its own unique set of fields, so the isolation of different modes in the same runloop is supported by the underlying data structure.

typedef struct __CFRunLoopMode *CFRunLoopModeRef;

struct __CFRunLoopMode {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;	/* must have the run loop locked before locking this */
    CFStringRef _name;
    Boolean _stopped;
    char _padding[3];
    CFMutableSetRef _sources0;
    CFMutableSetRef _sources1;
    CFMutableArrayRef _observers;
    CFMutableArrayRef _timers;
    CFMutableDictionaryRef _portToV1SourceMap;
    __CFPortSet _portSet;
    CFIndex _observerMask;
#if USE_DISPATCH_SOURCE_FOR_TIMERS
    dispatch_source_t _timerSource;
    dispatch_queue_t _queue;
    Boolean _timerFired; // set to true by the source when a timer has fired
    Boolean _dispatchTimerArmed;
#endif
#if USE_MK_TIMER_TOO
    mach_port_t _timerPort;
    Boolean _mkTimerArmed;
#endif
#if DEPLOYMENT_TARGET_WINDOWS
    DWORD _msgQMask;
    void (*_msgPump)(void);
#endif
    uint64_t _timerSoftDeadline; /* TSR */
    uint64_t _timerHardDeadline; /* TSR */
};
Copy the code

In addition to CFRuntimeBase _base; In addition, there are the following fields:

  1. CFStringRef _name: Each mode specifies a string name. The external mode is usually accessed by name. If the specified runloopMode cannot be found by name, it will not be affected, avoiding errors caused by abnormal operation of mode.
  2. Boolean _STOPPED: Very simple field that marks the running status of runloop. And then there’s _per_run_data.
  3. Source0, source1, observers, and timers are all collection types, which contain Mode items. That is, a Mode contains multiple Mode items.
  4. CFMutableDictionaryRef _portToV1SourceMap: Stores the mapping between port and source of Source1. The key is mach_port_t and the value is CFRunLoopSourceRef.
  5. __CFPortSet _portSet: port to listen on, such as _wakeUpPort and _timerPort
  6. CFIndex _observerMask: indicates the status of runloopMode.

If dispatch source timers are correlated (for MacOS X only), there are corresponding GCD timerSource and GCD Queue fields.

#if DEPLOYMENT_TARGET_MACOSX
#define USE_DISPATCH_SOURCE_FOR_TIMERS 1
#define USE_MK_TIMER_TOO 1
#else
#define USE_DISPATCH_SOURCE_FOR_TIMERS 0
#define USE_MK_TIMER_TOO 1
#endif
Copy the code

The only runloopmodes we are usually exposed to are DefaultMode and UITrackingRunLoopMode. In fact, there are a lot of other modes that developers don’t normally touch. See Run Loop Modes.

How do I create a __CFRunLoopMode object

The __CFRunLoopFindMode() function finds the corresponding runloopMode object from the passed modeName string, or creates a new one if none exists.

rlm = (CFRunLoopModeRef)_CFRuntimeCreateInstance(kCFAllocatorSystemDefault, __kCFRunLoopModeTypeID, sizeof(struct __CFRunLoopMode) - sizeof(CFRuntimeBase), NULL);
rlm->_name = CFStringCreateCopy(kCFAllocatorSystemDefault, modeName);
// Notice the _portSet.
rlm->_portSet = __CFPortSetAllocate();
// Do you have any questions? The _wakeUpPort used to wake up runloop. Place rL ->_wakeUpPort in RLM ->_portSet.
ret = __CFPortSetInsert(rl->_wakeUpPort, rlm->_portSet);
if(KERN_SUCCESS ! = ret) CRASH("*** Unable to insert wake up port into port set. (%d) ***", ret);

#if USE_MK_TIMER_TOO
    rlm->_timerPort = mk_timer_create();
    ret = __CFPortSetInsert(rlm->_timerPort, rlm->_portSet);
    if(KERN_SUCCESS ! = ret) CRASH("*** Unable to insert timer port into port set. (%d) ***", ret);
#endif
Copy the code

rlm = (CFRunLoopModeRef)_CFRuntimeCreateInstance(kCFAllocatorSystemDefault, __kCFRunLoopModeTypeID, sizeof(struct __CFRunLoopMode) – sizeof(CFRuntimeBase), NULL); It’s the same routine.

After _portSet is initialized, __CFPortSetInsert(rl->_wakeUpPort, RLM ->_portSet) is called; Insert _wakeUpPort of runloop into _portSet of runloopMode. As we know, a runloop can have multiple runloopmodes, so the _wakeUpPort of the runloop must be synchronized to each runloopMode.

In addition, there is a code wrapped in USE_DISPATCH_SOURCE_FOR_TIMERS that is only available on macOS X. It’s only on macOS X, but it seems to help, right? In this case, the runloopMode will have a timer. The timer’s callback is to set the _timerFired field of the runloopMode to true, which is marked as being fired by the timer.

#if USE_DISPATCH_SOURCE_FOR_TIMERS
    rlm->_timerFired = false;
    // This queue will be useful later?
    rlm->_queue = _dispatch_runloop_root_queue_create_4CF("Run Loop Mode Queue".0);
    mach_port_t queuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue);
    if (queuePort == MACH_PORT_NULL) CRASH("*** Unable to create run loop mode queue port. (%d) ***".- 1);
    rlm->_timerSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0.0, rlm->_queue);

    __block Boolean *timerFiredPointer = &(rlm->_timerFired);
    dispatch_source_set_event_handler(rlm->_timerSource, ^{
        *timerFiredPointer = true;
    });

    // Set timer to far out there. The unique leeway makes this timer easy to spot in debug output.
    _dispatch_source_set_runloop_timer_4CF(rlm->_timerSource, DISPATCH_TIME_FOREVER, DISPATCH_TIME_FOREVER, 321);
    dispatch_resume(rlm->_timerSource);

    ret = __CFPortSetInsert(queuePort, rlm->_portSet);
    if(KERN_SUCCESS ! = ret) CRASH("*** Unable to insert timer port into port set. (%d) ***", ret);

#endif
Copy the code

RunloopMode Item

A RunloopMode Item is usually referred to as a runloop source, timer, or observer.

NSRunLoopCommonModes

NSRunLoopCommonModes are not a real RunLoopMode, but a set of RunLoopMode names.

CommonModes store the name of the Mode. A Mode can add its own name to commonModes, that is, mark itself as common. After that, Runloop synchronizes the Source/Timer/Observer from commonModeItems to all modes marked with the Common flag. Equivalent to: Add the corresponding Source in commonModeItems for each runloopMode corresponding to commonModes.

The main thread has two commonModes: Default Mode and UITracking Mode. You can mark the mode with the specified name as common by CFRunLoopAddCommonMode(rL, modeName).

Each runloop object has two fields associated with commonMode:

CFMutableSetRef _commonModes;
CFMutableSetRef _commonModeItems;
Copy the code

commonModes

CommonModes are a collection of *NSMutableSet

. The name of the runloopMode is stored.

commonModeItems

It is also a Set containing an Observer, Timer, and Source.

The runloop will exit if there is no modeItem in it, so the *** resident thread must have mode item ***.

Items are synchronized to all modes marked with the Common tag.

Items of different modes are isolated from each other and can only be synchronized using commonMode. For example, NSTimer can be added to DefaultMode and UITrackingMode.

CFRunLoopSourceRef

Runloop Source distinguishes Source0 from Source1.

  1. Source0 does not actively trigger events, only a callback function. Source0 cannot actively wake up runloop. Such as performSelector. For example, when hibernating, clicking on the screen triggers source0 to wake up runloop. How does iOS itself wrap the UITouch event?
  2. Source1 actively wakes up runloops for communication between iOS kernel threads and other threads in the form of Mach MSG, with a callback function and a Mach port.
struct __CFRunLoopSource {
    CFRuntimeBase _base;
    uint32_t _bits;
    pthread_mutex_t _lock;
    CFIndex _order;			/* immutable */
    CFMutableBagRef _runLoops;
    union {
	    CFRunLoopSourceContext version0;	/* immutable, except invalidation */
        CFRunLoopSourceContext1 version1;	/* immutable, except invalidation */
    } _context;
};
Copy the code

Version0 and version1 correspond to source0 and source1 respectively.

The source object has a special field ***CFMutableBagRef _runLoops; ***, temporarily not clear is used for what purpose?

CFRunLoopSourceContext (Source0)

Source0 contains only one callback (function pointer) and cannot actively wake up the runloop. CFRunLoopWakeUp(runloop) needs to be called manually to wake up the runloop. Source0 must be marked as Signal (calling the CFRunLoopSourceSignal function), that is, Source0 must be marked as pending (_bits field), The CFRunLoopWakeUp(runloop) function is then called to wake up the runloop to handle the event.

How to manually call…

The underlying structure of Source0 is as follows:

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;
Copy the code

There are three function Pointers:

  1. Schedule: callback function triggered when source is added to runloop
  2. Cancel: The callback function that fires when source is removed from the runloop
  3. Perform: Callback when the source event is fired, using the CFRunLoopSourceSignal function.

Info is the information about the source.

CFRunLoopSourceContext1 (Source1)

Source1 contains a Mach port and a callback function (function pointer) that can communicate with other threads via messages (Mach MSG). Mach MSG, on the other hand, has the ability to switch between user and kernel mode, that is, to actively wake up runloop in this way. It is generally used in very low-level usage scenarios and is widely used within iOS systems.

The underlying structure of Source1 is as follows:

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);
#if(TARGET_OS_MAC && ! (TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) || (TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)
    mach_port_t	(*getPort)(void *info);
    void *	(*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info);
#else
    void *	(*getPort)(void *info);
    void	(*perform)(void *info);
#endif
} CFRunLoopSourceContext1;
Copy the code

The Perform pointer points to what runloop will do when it is woken up.

Why does Source1 not have Pointers to schedule and cancel functions?

GetPort function pointer to get the mach_port_t object from the function when source is added to the runloop.

CFRunLoopTimerRef

NSTimer is closely related to runloop, CFRunLoopTimerRef and NSTimer can be toll-free bridged. When a timer is added to a runloop, the runloop will register the corresponding trigger time. When the time is up, the runloop will wake up if it is asleep and execute the timer’s corresponding callback function.

struct __CFRunLoopTimer {
    CFRuntimeBase _base;
    uint16_t _bits; // Mark the state of the timer
    pthread_mutex_t _lock;
    CFRunLoopRef _runLoop;
    CFMutableSetRef _rlModes; // Store the name of runloopMode
    CFAbsoluteTime _nextFireDate;
    CFTimeInterval _interval;		/* immutable */
    CFTimeInterval _tolerance;          /* mutable */
    uint64_t _fireTSR;			/* TSR units */
    CFIndex _order;			/* immutable */
    CFRunLoopTimerCallBack _callout;	/* immutable */
    CFRunLoopTimerContext _context;	/* immutable, except invalidation */
};
Copy the code

The structure in __CFRunLoopTimer is as follows:

  1. Uint16_t _bits: Mark the state of timer.
  2. CFRunLoopRef _runLoop: In which runloop is the timer registered?
  3. CFMutableSetRef _rlModes: In which runloopMode does the timer run? This stores the mode name, and there can be more than one mode. It is also verified that a timer can be used in multiple Runloopmodes.
  4. CFAbsoluteTime _nextFireDate: Indicates the next trigger time of the timer. The value will be set again after each trigger. namelyrlt->_nextFireDate = CFAbsoluteTimeGetCurrent() + __CFTimeIntervalUntilTSR(nextFireTSR);
  5. CFTimeInterval _tolerance: Allowable time deviation of timer?? The question is: since runloop will interfere with timer accuracy, what is the significance of the existence of _tolerance?
  6. CFRunLoopTimerCallBack _callout: Callback function of timer
  7. CFRunLoopTimerContext _context: context

CFRunLoopTimerCallBack _callout; The function prototype of is:

typedef void (*CFRunLoopTimerCallBack)(CFRunLoopTimerRef timer, void *info);
Copy the code

CFRunLoopTimerContext _context is a field that can be used to pass parameters to the timer object’s callback function.

typedef struct {
    CFIndex	version;
    void *	info;
    const void *(*retain)(const void *info);
    void	(*release)(const void *info);
    CFStringRef	(*copyDescription)(const void *info);
} CFRunLoopTimerContext;
Copy the code

*void info is the same in both places.

Runloop also provides an interface to use CFRunLoopTimer directly:

CF_EXPORT CFRunLoopTimerRef CFRunLoopTimerCreate(CFAllocatorRef allocator, CFAbsoluteTime fireDate, CFTimeInterval interval, CFOptionFlags flags, CFIndex order, CFRunLoopTimerCallBack callout, CFRunLoopTimerContext *context); #if __BLOCKS__ CF_EXPORT CFRunLoopTimerRef CFRunLoopTimerCreateWithHandler(CFAllocatorRef allocator, CFAbsoluteTime fireDate, CFTimeInterval interval, CFOptionFlags flags, CFIndex order, Void (^block) (CFRunLoopTimerRef timer) API_AVAILABLE(MacOS (10.7), ios(5.0), Watchos (2.0), tVOs (9.0)); void (^block) (CFRunLoopTimerRef timer)) API_AVAILABLE(MacOS (10.7), ios(5.0), Watchos (2.0), TVOs (9.0));Copy the code

The CFRunLoopTimerCreate() function returns a CFRunLoopTimerRef object that can be used in the following scenarios:

- (void)testRunloopTimer { CFRunLoopTimerContext context = { 0, (__bridge void *)self, &CFRetain, &CFRelease, NULL }; CFRunLoopTimerRef timer = CFRunLoopTimerCreate(NULL, CFAbsoluteTimeGetCurrent(), 1, 0, 0, &RunloopTimerCallBack, &context); CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopCommonModes); } void RunloopTimerCallBack(CFRunLoopTimerRef timer, void *info) { NSLog(@"RunloopTimerCallBack"); Method 1, use the static variable to store the self object. ViewController *self1 = (__bridge ViewController *)ViewControllerSelf; NSLog(@"%@", self1); // method 2, using CFRunLoopTimerContext to pass the self object. ViewController *self2 = (__bridge ViewController *)info; NSLog(@"%@", self2); }Copy the code

For more precise timing scenarios, drop NSTimer and use GCD timer instead. For GCD Timer usage, check out the blog to compare the three iOS timers.

CFRunLoopObserverRef

During the life cycle of a runloop, there are six operating states:

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry         = (1UL << 0), // About to enter Loop
    kCFRunLoopBeforeTimers  = (1UL << 1), // Timer is about to be processed
    kCFRunLoopBeforeSources = (1UL << 2), // We will handle Sources, here Source0
    kCFRunLoopBeforeWaiting = (1UL << 5), // About to go to sleep
    kCFRunLoopAfterWaiting  = (1UL << 6), // Just woke up from hibernation
    kCFRunLoopExit          = (1UL << 7), // Exit runloop
};
Copy the code

You can use an Observer to listen for these states of the Runloop to perform a specified task. In particular, kCFRunLoopBeforeWaiting is not only widely used in iOS, but also favored by developers, because once it is in kCFRunLoopBeforeWaiting state, iOS system will switch from user state to kernel state and go to sleep. This means that the user task is complete and the CPU is free.

struct __CFRunLoopObserver {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;
    CFRunLoopRef _runLoop;
    CFIndex _rlCount;
    CFOptionFlags _activities;		/* immutable */
    CFIndex _order;			/* immutable */
    CFRunLoopObserverCallBack _callout;	/* immutable */
    CFRunLoopObserverContext _context;	/* immutable, except invalidation */
};
Copy the code

Observer fields are also intuitive, _callout and _context not to mention, similar to timer.

  1. CFIndex _rlCount: indicates the number of runloop runs.
  2. CFOptionFlags _activities: can you listen for multiple states?
  3. CFIndex _order: What order is this?

The Observer also contains a callback function that is fired when the listening runloop status occurs. The runloop uses the same logic for the observer as for the timer, specifying a callback function and passing parameters through the context. This type of use of runloop should be familiar once you’ve mastered the routine.

Listening for the BeforeWaiting state of runloop:

- (void)addRunloopObserver {
    CFRunLoopObserverContext context = { 0, (__bridge void *)self, &CFRetain, &CFRelease, NULL };
    CFRunLoopObserverRef observer = CFRunLoopObserverCreate(NULL, kCFRunLoopBeforeWaiting, YES, 0, &RunloopObserverCallBack, &context);
    
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopCommonModes);
}

void RunloopObserverCallBack(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
    NSLog(@"RunloopObserverCallBack");
}
Copy the code

Note that the Observer relies on a particular runloopMode, so once the Runloop is running in another mode, the desired state will not be heard.

mach_msg

Mach is the core of Darwin for the iOS kernel, providing basic services such as processor scheduling and interprocess communication (IPC). Interprocess communication in Mach is carried out in the form of Mach MSG, which is passed between two Mach ports. Both sending and receiving messages are done through the mach_msg function.

The runloop Source1 is also called a port-based Source because it relies on the Mach MSG to be sent to the specified Mach Port to wake up the Runloop’s. The mach_msg() function has the following prototype:

/* * Routine: mach_msg * Purpose: * Send and/or receive a message. If the message operation * is interrupted, and the user did not request an indication * of that fact, then restart the appropriate parts of the * operation silently (trap version does not restart). */
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

Inside mach_msg(), the mach_MSg_trap () system call is actually called. Trap trap is a basic concept at the operating system level. It is used to change the operating system status, for example, to trigger the switch between the kernel mode and user mode. Traps are usually triggered by exception conditions, such as breakpoints, division by zero operations, invalid memory access, and so on. If the program does not have a Source, the runloop will go to sleep. This is done by calling __CFRunLoopServiceMachPort in the runloop Source code, which calls the mach_msg function to change the kernel state: Switch from user mode to kernel mode.

For example, when the App is stationary and in runloop sleep, click debug pause, then the main thread call stack is stuck at mach_MSg_trap ().

The mach_msg() function sets the timeout parameter. If the MSG is not read before timeout, the runloop of the current thread will sleep.

mach_msg_header_t

Mach_msg_header_t stores basic MSG information, including port information, etc. For example, local port msGH_local_port and remote port MSgh_remote_port, the direction of MSG delivery is specified in the header.

typedef struct {
    mach_msg_header_t header;
    mach_msg_body_t body;
} mach_msg_base_t;

typedef struct {
    mach_msg_bits_t msgh_bits;
    mach_msg_size_t msgh_size;
    mach_port_t msgh_remote_port;
    mach_port_t msgh_local_port;
    mach_port_name_t msgh_voucher_port;
    mach_msg_id_t msgh_id;
} mach_msg_header_t;
Copy the code
typedef struct{
	mach_msg_size_t msgh_descriptor_count;
} mach_msg_body_t;
Copy the code

mach_port_t

Mach ports are actually integers used to mark ports.

typedef __darwin_mach_port_t mach_port_t;
typedef __darwin_natural_t __darwin_mach_port_name_t; /* Used by mach */
typedef __darwin_mach_port_name_t __darwin_mach_port_t; /* Used by mach */
typedef unsigned int            __darwin_natural_t;
Copy the code

Note about mach_port_t:

/* * mach_port_t - a named port right * * In user-space, "rights" are represented by the name of the * right in the Mach port namespace. Even so, this type is * presented as a unique one to more clearly denote the presence * of a right coming along with the name. * * Often, various rights for a port held in a single name space * will coalesce and are, therefore, be identified by a single name * [this is the case for send and receive rights]. But not * always [send-once rights currently get a unique name for * each right]. * * This definition of mach_port_t is only for user-space. * */
Copy the code

Other fields of Mach MSG

  1. Mach_msg_option_t option: marks the direction of message delivery.
  2. Send_size and RCv_size: Some data sizes for message delivery
  3. mach_port_name_t notify

Thread-specific data

Thread-specific Data (TSD) is thread-specific data that contains TSD functions for storing and retrieving data from Thread objects. For example, the CFRunLoopGetMain() function calls _CFRunLoopGet0(), where the TSD interface is used to get runloop objects from thread. So the Runloop object exists not only in the global dictionary, but also in the TSD of the thread.

See cfplatform.c for the TSD source code.

// __CFTSDTable
typedef struct __CFTSDTable {
    uint32_t destructorCount;
    uintptr_t data[CF_TSD_MAX_SLOTS];
    tsdDestructor destructors[CF_TSD_MAX_SLOTS];
} __CFTSDTable;

// _CFGetTSD
CF_EXPORT void *_CFGetTSD(uint32_t slot) {
    __CFTSDTable *table = __CFTSDGetTable();
    if(! table) {return NULL; }
    uintptr_t *slots = (uintptr_t *)(table->data);
    return (void *)slots[slot];
}

// _CFSetTSD
CF_EXPORT void *_CFSetTSD(uint32_t slot, void *newVal, tsdDestructor destructor) {
    __CFTSDTable *table = __CFTSDGetTable();
    if(! table) {return NULL; }

    void *oldVal = (void *)table->data[slot];
    table->data[slot] = (uintptr_t)newVal;
    table->destructors[slot] = destructor;
    
    return oldVal;
}
Copy the code

__CFTSDTable is a data structure that stores private data, data arrays are private functions, and destructors are used to hold the corresponding destructors of data elements. The _CFGetTSD() function and _CFSetTSD() provide an interface to __CFTSDTable. As in the previous code for creating a runloop object:

CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS- 1), (void(*) (void *))__CFFinalizeRunLoop);
Copy the code

*_CFSetTSD(__CFTSDKeyRunLoop, (void )loop, NULL); Set a runloop object to the first __CFTSDKeyRunLoop slot for the current thread’s private data. This is how thread stores runloop objects.

The third argument to the _CFSetTSD() function is a destructor for the thread’s destruction. For example, *_CFSetTSD(__CFTSDKeyRunLoopCntr, (void)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void ()(void))__CFFinalizeRunLoop; Void ()(void)__CFFinalizeRunLoop is invoked when the thread is destroyed. The __CFFinalizeRunLoop function is the destructor of the runloop object. PTHREAD_DESTRUCTOR_ITERATIONS is the maximum number of times that a thread destroys its private data TSD as it exits. So, when the thread exits, the Runloop object is destroyed in this way.

In fact, the runloop source code has a set of operations like this:

_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS- 1), (void(*) (void *))__CFFinalizeRunLoop);
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(data - 1), (void(*) (void *))__CFFinalizeRunLoop);
_CFSetTSD(__CFTSDKeyRunLoopCntr, 0, (void(*) (void *))__CFFinalizeRunLoop);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6.NULL);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0.NULL);
// If we received a voucher from this mach_msg, then put a copy of the new voucher into TSD. CFMachPortBoost will look in the TSD for the voucher. By using the value in  the TSD we tie the CFMachPortBoost to this received mach_msg explicitly without a chance for anything in between the two pieces of code to set the voucher again.
voucher_t previousVoucher = _CFSetTSD(__CFTSDKeyMachMessageHasVoucher, (void *)voucherCopy, os_release);
// Restore the previous voucher
_CFSetTSD(__CFTSDKeyMachMessageHasVoucher, previousVoucher, os_release);
Copy the code

The runloop object is stored in slot __CFTSDKeyRunLoop, and the destructor __CFFinalizeRunLoop is stored in slot __CFTSDKeyRunLoopCntr. In the source code, the Runloop object is stored separately from its reference count.

Here’s an interesting point:

_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6.NULL);
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0.NULL);
Copy the code

What do void star 6 and void star 0 do here? __CFRunLoopModeIsEmpty() and __CFRunLoopRun() use 0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ). To get the value of the libdispatchQSafe variable. Therefore, __CFTSDKeyIsInGCDMainQ is simply used to add a judgment before and after the CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE(MSG) function to perform a lock-like operation.

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)) return false; // represents the libdispatch main queue
Copy the code
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();
Copy the code

In addition, how does TSD ensure that these destructors are properly called when a thread is destroyed? This is in the __CFTSDGetTable function.

pthread_key_init_np(CF_TSD_KEY, __CFTSDFinalize);
Copy the code

The __CFTSDFinalize() function is the destructor corresponding to CF_TSD_KEY, which is the actual destruction function of the thread itself. When called, it iterates over all slots in the TSD, sets data to NULL, and calls the slot destructor, which takes data as an argument.

static void __CFTSDFinalize(void *arg) {
    __CFTSDSetSpecific(arg);

    if(! arg || arg == CF_TSD_BAD_PTR) {return;
    }
    
    __CFTSDTable *table = (__CFTSDTable *)arg;
    table->destructorCount++;
        
    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;
            table->destructors[i]((void*)(old)); }}// On PTHREAD_DESTRUCTOR_ITERATIONS-1 call, destroy our data
    if (table->destructorCount == PTHREAD_DESTRUCTOR_ITERATIONS - 1) {
        free(table);
        
        __CFTSDSetSpecific(CF_TSD_BAD_PTR);
        return; }}Copy the code

Therefore, this is how the runloop object’s corresponding destructor __CFFinalizeRunLoop is called during thread destruction.

summary

We can get an idea of the underlying implementation of Runloop by looking at some of the underlying data structures above:

  1. The mapping between runloop objects and Thread-to-thread objects exists in the global dictionary __CFRunLoops and thread TSD. Thread does not have a runloop object by default and is automatically created when it is first acquired. When a thread exits, the runloop object is destroyed, using the THREAD’s TSD.
  2. A runloop object can only run in one mode at a time, and one mode can correspond to multiple Mode items (source, timer).
  3. CommonMode is just a flag. You can give runloopMode the common flag.
  4. Source0 cannot actively wake up runloop, only by setting callback functions (such as timer); Source1 is based on Mach ports and MSG and can actively wake up runloops.
  5. You can add an Observer to listen for the running status of a runloop to perform a specified task.
  6. Thread sleep and wake up are implemented using the mach_msg function, based on Mach port.
  7. On MACOSX, the runloop uses GCD Timer and MK Timer as timers. In other cases, MK Timer is used for timers.

The resources

  1. Event loop
  2. Run Loops
  3. NSRunLoop
  4. CFRunLoop
  5. CFRunLoopPerformBlock
  6. CFRunLoopPerformBlock vs dispatch_async
  7. RunLoop.subproj
  8. Kernel Programming Guide: Mach Overview
  9. mach_msg
  10. CFPlatform.c
  11. AsyncDisplayKit has been renamed to Texture
  12. Understand RunLoop in depth
  13. Decryption Runloop
  14. Get to the bottom of iOS – Understand RunLoop in depth
  15. Optimizing the UITableViewCell height calculation stuff
  16. Revisit the RunLoop principle
  17. IOS RunLoop,