RunLoop isn’t a new topic in 20202, and there are plenty of articles on the subject that have been brilliantly summarized online.
As part of the iOS series, this article is a summary of some of the basics I explored in RunLoop, as well as some of the best RunLoop technology articles on the web.
If there are any errors in this article, please correct them.
RunLoop leading knowledge
IOS /OS X architecture
IOS evolution
Mac OS Classic had a great GUI, but the system design was terrible, especially the collaborative multitasking system and inefficient memory management, which was primitive by today’s standards.
The NeXTSTEP operating system was incorporated into Mac OS Classic along with Mach and OC from the architecture as part of Jobs’ return to Apple. It wasn’t so much the Mac OS Classic merging with NeXTSTEP as the Mac OS Classic replacing NeXTSTEP, and the transition didn’t happen all of a sudden.
Mac OS Classic had a good GUI but a bad system design, and NeXTSTEP had a great design but a very bland GUI. The result was a new, much more successful operating system that inherited the best of both — Mac OS X. The new Mac OS X was very similar in design and implementation to NeXTSTEP, Core components such as Cocoa, Mach, and Interface Builder are derived from NeXTSTEP
Originally called iPhone OS, iOS is Mac OS X’s answer to mobile platforms. IOS has the same operating system hierarchy as Mac OS X and the same operating system core, Dawin.
IOS Architecture
Apple officially divides the entire system into four levels:
The application layer includes graphics apps that users can access, such as Spotlight, Aqua(macOS), SpringBoard(iOS), and so on.
The application framework layer is the Cocoa and other frameworks that developers come into contact with.
The core framework layer includes various core frameworks, OpenGL and other contents.
The Darwin layer is the core of the operating system, including the XNU kernel (open source in 2017), drivers, and UNIX shell
Darwin architecture
The kernel of Darwin is XNU, which is Not Unix. XNU is a hybrid of two technologies, Mach and BSD. The BSD layer ensures the UNIX nature of Darwin systems, and the true kernel is Mach, but hidden from the outside. BSD above is user mode, all content can be accessed by the application, but the application cannot access kernel mode. When you need to switch from user mode to kernel mode, you need to use Mach trap to switch.
— Mac OS X & iOS
The three components above the hardware layer, Mach, BSD, and IOKit (along with a few others not noted above), together make up the XNU kernel.
The inner ring of the XNU kernel is called Mach, and as a microkernel, it provides only a very small number of basic services such as processor scheduling and IPC (inter-process communication).
The BSD layer can be viewed as an outer ring around the Mach layer, providing functions such as process management, file systems, and networking.
The IOKit layer is a framework that provides an object-oriented (C++) framework for device drivers.
Mach itself provides very limited apis, and Apple discourages the use of Mach’s apis, but they are so basic that nothing else can be done without them. In Mach, everything was implemented through its own objects, with processes, threads, and virtual memory all referred to as “objects.”
Unlike other architectures, Mach objects cannot be called directly, but can communicate with each other only through messaging.
Messages are the most basic concept in Mach. Messages are transmitted between two ports. This is the core of IPC (inter-process communication) in Mach.
— Ibireme, Understanding RunLoop
Mach_msg function
The mach_msg function is in the Message.h header file of the Mach kernel
This is the function that the kernel uses to send and receive messages on a port, and understanding this function is important to understand how Runloop works. Detailed instructions can be found here.
// Mach message infrastructure
typedef struct{
// Message header
mach_msg_header_t header;
/ / the message body
mach_msg_body_t body;
} mach_msg_base_t;
// Mach header structure
typedef struct{
// The bits of the header
mach_msg_bits_t msgh_bits;
// The size of the header
mach_msg_size_t msgh_size;
// Remote port number
mach_port_t msgh_remote_port;
// Local port number
mach_port_t msgh_local_port;
// Credential port number
mach_port_name_t msgh_voucher_port;
/ / message ID
mach_msg_id_t msgh_id;
} mach_msg_header_t;
/ / mach_msg function
mach_msg_return_t mach_msg(
/ / the message header
mach_msg_header_t *msg,
// Message option, which can be used to specify whether to send the message (MACH_SEND_MSG) or receive the message (MACH_RCV_MSG)
mach_msg_option_t option,
// Size of cache set when sending messages, unsigned integer
mach_msg_size_t send_size,
// The size of the cache set when receiving messages, an unsigned integer
mach_msg_size_t rcv_size,
// Port to receive messages, an unsigned integer
mach_port_name_t rcv_name,
// Message expiration time
// When the option parameter contains MACH_SEND_TIMEOUT or MACH_RCV_TIMEOUT, an expiration time is passed in milliseconds
mach_msg_timeout_t timeout,
// The port to receive notifications
// If the option parameter contains MACH_SEND_CANCEL and MACH_RCV_NOTIFY, set the port for receiving notifications. Otherwise, pass MACH_PORT_NULL.
mach_port_name_t notify);
Copy the code
Mach_msg can be simply understood as a communication mechanism between multiple processes. Different processes can use the same message queue to communicate data. When mach_msg is used to read MSG from the message queue, timeout value can be set in the parameter. If MSG is not read before timeout, the current thread will remain dormant. This is why runloop can go to sleep when there are no more tasks to execute. — Decrypt Runloop MrPeak
To send and receive messages, the mach_msg() function actually calls a Mach trap, the function mach_MSg_trap (), which is the equivalent of a system call in Mach. When you call mach_MSg_trap () in user mode, the trap mechanism is triggered, switching to kernel mode; The mach_msg() function implemented by the kernel in the kernel state does the actual work. — Ibireme, Understanding RunLoop
CoreFoundation basis
CoreFoundation is a SET of C-based apis that provide basic data management and service capabilities for iOS. Because CoreFoundation objects are implemented based on C, there is also the concept of reference counting, while Foundation objects are implemented based on OC. “Bridge”, “bridge_retained”, and “bridge_transfer” are used for translation of two types of objects.
__bridge
id obj = [[NSObject alloc] init];
void *p = (__bridge void *)obj;
id o = (__bridge id)p;
Copy the code
Converting an Objective-C object type to void using a *bridge is the same as using the **unsafe_unretained keyword.
__bridge: Does only type conversion, does not modify the related object reference count, the original Core Foundation object when not in use, need to call CFRelease method
__bridge_retained
d obj = [[NSObject alloc] init];
void *p = (__bridge_retained void *)obj;
Copy the code
The name should make sense: when a type is converted, ownership of its object is also held by the converted variable. In MRC:
id obj = [[NSObject alloc] init];
void *p = obj;
[(id)p retain];
Copy the code
The following example verifies that outside the braces, p still refers to a valid entity. Indicates that she owns the object and that the object was not destroyed because it was outside the scope of its definition.
void *p = 0;
{
id obj = [[NSObject alloc] init];
p = (__bridge_retained void *)obj;
}
NSLog(@"class=%@", [(__bridge id)p class]);
Copy the code
__bridge_retained: After type conversion, the reference count of the relevant object is increased by 1. If the original Core Foundation object is not used, the CFRelease method needs to be called
__bridge_transfer
MRC:
// the p variable originally holds the ownership of the object
id obj = (id)p;
[obj retain];
[(id)p release];
Copy the code
The ARC:
// the p variable originally holds the ownership of the object
id obj = (__bridge_transfer id)p;
Copy the code
Retained by **bridge_transfer ** retained by the compiler, and released by **bridge_transfer.
__bridge_transfer: After a type conversion, the object’s reference count is managed by ARC. Core Foundation objects do not need to call the CFRelease method when they are not in use
Toll-Free bridged
There’s a toll-free bridging mechanism between CoreFoundation and Foundation. In the ERA of MRC, it is possible to convert two frame objects directly through type force. In the ARC era, however, you would need to use the toll-free Bridging mechanism. There are three different conversion mechanisms described above, but in Foundation, there is an easier way to do it. As follows:
// After using a CFBridgingRetain on an NSObject, the caller must take responsibility for calling CFRelease at an appropriate time.
NS_INLINE CF_RETURNS_RETAINED CFTypeRef _Nullable CFBridgingRetain(id _Nullable X) {
return (__bridge_retained CFTypeRef)X;
}
NS_INLINE id _Nullable CFBridgingRelease(CFTypeRef CF_CONSUMED _Nullable X) {
return (__bridge_transfer id)X;
}
Copy the code
CFBridgingRetain can replace **bridge_retained, and CFBridgingRelease can replace **bridge_transfer.
RunLoop que
What is the RunLoop
As defined in the Official Apple documentation
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. RunLoop is an event loop that schedules tasks and handles events. The idea is to keep threads busy when they have something to do and dormant when they have nothing to do.
The NodeJS event loop is implemented in pseudo-code as follows:
while(AppIsRunning) {
id whoWakesMe = SleepForWakingUp();
id event = GetEvent(whoWakesMe);
HandleEvent(event);
}
Copy the code
This pseudo-code was shared by Sunnyx’s RunLoop technology
As you can see, RunLoop is such a simple way to work. But the internal implementation is much more complex than the code above. We’ll take a deeper look at RunLoop in the next section.
OSX/iOS provides two such objects: NSRunLoop and CFRunLoopRef.
CFRunLoopRef is within the CoreFoundation framework and provides apis for pure C functions, all of which are thread-safe.
NSRunLoop is a wrapper based on CFRunLoopRef that provides object-oriented apis, but these apis are not thread-safe.
RunLoop underlying data structure
As shown in the figure above, there are multiple modes inside each RunLoop, and inside each mode are sources0, source1, observers, and timers. The elements in a mode are collectively called mode items. Let’s review the data structure definitions of these five classes:
- CFRunLoopRef
- CFRunLoopModeRef
- CFRunLoopSourceRef
- CFRunLoopTimerRef
- CFRunLoopObserverRef
CFRunLoopRef
typedef struct __CFRunLoop * CFRunLoopRef;
struct __CFRunLoop
{
// CoreFoundation runtime base information
CFRuntimeBase _base;
// Lock for the mode list operation
pthread_mutex_t _lock; /* locked for accessing mode list */
// Wake up the port
__CFPort _wakeUpPort; // used for CFRunLoopWakeUp
// Whether it has been used
Boolean _unused;
// A data structure that runloop resets
volatile _per_run_data *_perRunData; // reset for runs of the run loop
// The thread of the runloop
pthread_t _pthread;
uint32_t _winthread;
// Store the common mode set
CFMutableSetRef _commonModes;
// Set common mode item
CFMutableSetRef _commonModeItems;
// runloop current mode
CFRunLoopModeRef _currentMode;
// Store a set of modes
CFMutableSetRef _modes;
// Runloop internal block list header pointer
struct _block_item* _blocks_head;
// The pointer to the end of the runloop block list
struct _block_item* _blocks_tail;
// Run time point
CFAbsoluteTime _runTime;
// Sleep time point
CFAbsoluteTime _sleepTime;
CFTypeRef _counterpart;
};
// The RunLoop is reset after each run
typedef struct _per_run_data
{
uint32_t a;
uint32_t b;
uint32_t stopped; // Whether runloop stops
uint32_t ignoreWakeUps; // Whether runloop has been awakened
} _per_run_data;
// List nodes
struct _block_item
{
// point to the next _block_item
struct _block_item* _next;
// Can be either string or set, meaning that a block may correspond to one or more modes
CFTypeRef _mode; // CFString or CFSet
// Hold the actual block to execute
void (^_block)(void);
};
Copy the code
As you can see from the code above, CFRunLoopRef is actually a structure pointer of type __CFRunLoop. The following attributes within __CFRunLoop need attention:
_pthread
_commonModes
_currentMode
_modes
CFRunLoopModeRef
typedef struct __CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoopMode
{
// CoreFoundation runtime base information
CFRuntimeBase _base;
// mutex, runloop must be locked before locking
pthread_mutex_t _lock; /* must have the run loop locked before locking this */
// The mode name
CFStringRef _name;
// mode Whether to stop
Boolean _stopped;
char _padding[3];
// source0
CFMutableSetRef _sources0;
// source1
CFMutableSetRef _sources1;
// observers
CFMutableArrayRef _observers;
// timers
CFMutableArrayRef _timers;
CFMutableDictionaryRef _portToV1SourceMap;
// A collection of ports
__CFPortSet _portSet;
/ / the observer's mask
CFIndex _observerMask;
// If the GCD timer is defined
#if USE_DISPATCH_SOURCE_FOR_TIMERS
// GCD timer
dispatch_source_t _timerSource;
/ / the queue
dispatch_queue_t _queue;
// Set to true when the GCD timer is triggered
Boolean _timerFired; // set to true by the source when a timer has fired
Boolean _dispatchTimerArmed;
#endif
// if MK_TIMER is used
#if USE_MK_TIMER_TOO
/ / MK_TIMER port
mach_port_t _timerPort;
Boolean _mkTimerArmed;
#endif
#if DEPLOYMENT_TARGET_WINDOWS
DWORD _msgQMask;
void (*_msgPump)(void);
#endif
// Timer soft critical point
uint64_t _timerSoftDeadline; /* TSR */
// Timer hard critical point
uint64_t _timerHardDeadline; /* TSR */
};
Copy the code
CFRunLoopModeRef is a pointer to a __CFRunLoopMode structure. The following attributes within __CFRunLoopMode require attention:
_sources0
_sources1
_observers
_timers
CFRunLoopSourceRef
typedef struct __CFRunLoopSource * CFRunLoopSourceRef;
struct __CFRunLoopSource
{
// CoreFoundation runtime base information
CFRuntimeBase _base;
uint32_t _bits;
/ / the mutex
pthread_mutex_t _lock;
// The priority of source. The value is small and the priority is higher
CFIndex _order; /* immutable */
/ / runloop collection
CFMutableBagRef _runLoops;
// a consortium, indicating that source is either source0 or source1
union {
CFRunLoopSourceContext version0; /* immutable, except invalidation */
CFRunLoopSourceContext1 version1; /* immutable, except invalidation */
} _context;
};
typedef struct {
CFIndex version;
// Source information
void * info;
const void *(*retain)(const void *info);
void (*release)(const void *info);
CFStringRef (*copyDescription)(const void *info);
// Determine the source equal function
Boolean (*equal)(const void *info1, const void *info2);
CFHashCode (*hash)(const void *info);
void (*schedule)(void *info, CFRunLoopRef rl, CFStringRef mode);
void (*cancel)(void *info, CFRunLoopRef rl, CFStringRef mode);
// source Specifies the task block to execute
void (*perform)(void *info);
} CFRunLoopSourceContext;
Copy the code
As you can see from the code above, CFRunLoopSourceRef is actually a structure pointer of type __CFRunLoopSource. The __CFRunLoopSource structure is relatively simple, with a few attributes to focus on:
_order
_context
CFRunLoopSourceContext
Under theperform
A function pointer
CFRunLoopTimerRef
typedef struct CF_BRIDGED_MUTABLE_TYPE(NSTimer) __CFRunLoopTimer * CFRunLoopTimerRef;
struct __CFRunLoopTimer
{
// CoreFoundation runtime base information
CFRuntimeBase _base;
uint16_t _bits;
/ / the mutex
pthread_mutex_t _lock;
// Timer corresponds to the runloop
CFRunLoopRef _runLoop;
// Set of modes corresponding to timer
CFMutableSetRef _rlModes;
// Next trigger time
CFAbsoluteTime _nextFireDate;
// Timer Interval for each task
CFTimeInterval _interval; /* immutable */
// Error allowed by timer
CFTimeInterval _tolerance; /* mutable */
// Trigger time point
uint64_t _fireTSR; /* TSR units */
// Priority of the timer
CFIndex _order; /* immutable */
// The task to be executed by the timer
CFRunLoopTimerCallBack _callout; /* immutable */
// Timer context
CFRunLoopTimerContext _context; /* immutable, except invalidation */
};
typedef struct {
CFIndex version;
void * info;
const void *(*retain)(const void *info);
void (*release)(const void *info);
CFStringRef (*copyDescription)(const void *info);
} CFRunLoopTimerContext;
typedef void (*CFRunLoopTimerCallBack)(CFRunLoopTimerRef timer, void *info);
Copy the code
CFRunLoopTimerRef is a pointer to a __CFRunLoopTimer structure. And here __CFRunLoopTimer is preceded by CF_BRIDGED_MUTABLE_TYPE(NSTimer)
#define CF_BRIDGED_TYPE(T) __attribute__((objc_bridge(T)))
#define CF_BRIDGED_MUTABLE_TYPE(T) __attribute__((objc_bridge_mutable(T)))
#define CF_RELATED_TYPE(T,C,I) __attribute__((objc_bridge_related(T,C,I)))
#else
#define CF_BRIDGED_TYPE(T)
#define CF_BRIDGED_MUTABLE_TYPE(T)
#define CF_RELATED_TYPE(T,C,I)
#endif
Copy the code
It can be seen from here that CFRunLoopTimerRef and NS Stimer are toll-free bridged.
There are several attributes in __CFRunLoopTimer that need to be focused on:
- _order
- _callout
- _context
CFRunLoopObserverRef
typedef struct __CFRunLoopObserver * CFRunLoopObserverRef;
struct __CFRunLoopObserver
{
// CoreFoundation runtime base information
CFRuntimeBase _base;
/ / the mutex
pthread_mutex_t _lock;
// Observer corresponding runloop
CFRunLoopRef _runLoop;
// The observer observed how many runloops there were
CFIndex _rlCount;
CFOptionFlags _activities; /* immutable */
// Observer priority
CFIndex _order; /* immutable */
// Observer callback function
CFRunLoopObserverCallBack _callout; /* immutable */
// Observer context
CFRunLoopObserverContext _context; /* immutable, except invalidation */
};
typedef struct {
CFIndex version;
void * info;
const void *(*retain)(const void *info);
void (*release)(const void *info);
CFStringRef (*copyDescription)(const void *info);
} CFRunLoopObserverContext;
typedef void (*CFRunLoopObserverCallBack)(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info);
Copy the code
As you can see from the code above, CFRunLoopObserverRef is actually a structure pointer of type __CFRunLoopObserver.
There are several attributes in __CFRunLoopObserver that need to be focused on:
- _order
- _callout
- _context
CFRunLoopActivity
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0),
kCFRunLoopBeforeTimers = (1UL << 1),
kCFRunLoopBeforeSources = (1UL << 2),
kCFRunLoopBeforeWaiting = (1UL << 5),
kCFRunLoopAfterWaiting = (1UL << 6),
kCFRunLoopExit = (1UL << 7),
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
Copy the code
As you can see from the source code above, RunLoop has the following states
- KCFRunLoopEntry: the Loop is about to enter
- KCFRunLoopBeforeTimers: The Timer is to be processed
- KCFRunLoopBeforeSources: Source is about to be processed
- KCFRunLoopBeforeWaiting: is about to go to sleep
- KCFRunLoopAfterWaiting: just woke up from sleep
- KCFRunLoopExit: Loop is coming
RunLoop relationships with threads
To figure out how runloops relate to threads, start with the CFRunLoopGetMain and CFRunLoopGetCurrent functions:
CFRunLoopRef CFRunLoopGetMain(void)
{
CHECK_FOR_FORK();
// Declare an empty RunLoop
static CFRunLoopRef __main = NULL; // no retain needed
// If __main is empty, the _CFRunLoopGet0 function is called to get RunLoop
if(! __main) __main = _CFRunLoopGet0(pthread_main_thread_np());// no CAS needed
return __main;
}
CFRunLoopRef CFRunLoopGetCurrent(void)
{
CHECK_FOR_FORK();
// Get runloop from Thread Specific Data (TSD)
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
// If you get it, return it
if (rl)
return rl;
// If not in TSD, _CFRunLoopGet0 is called
return _CFRunLoopGet0(pthread_self());
}
Copy the code
Pthread_main_thread_np () gets the main thread, and its internal implementation is as follows
#define pthread_main_thread_np() _CF_pthread_main_thread_np()
Copy the code
CF_EXPORT pthread_t _CF_pthread_main_thread_np(void); pthread_t _CF_pthread_main_thread_np(void) { return _CFMainPThread; }
void __CFInitialize(void) { …
_CFMainPThread = pthread_self();
. }
We then explore the _CFRunLoopGet0 function, which is implemented as follows
// A dictionary that stores runloops and threads
static CFMutableDictionaryRef __CFRunLoops = NULL;
static CFLock_t loopsLock = CFLockInit;
// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
// if t is 0, we want to get the main thread RunLoop
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t)
{
Pthread_main_thread_np () ¶
if (pthread_equal(t, kNilPthreadT))
{
t = pthread_main_thread_np();
}
/ / lock
__CFLock(&loopsLock);
// If __CFRunLoops is empty, initialize the dictionary and add mainLoop to the dictionary
if(! __CFRunLoops) {/ / unlock
__CFUnlock(&loopsLock);
// Initialize a mutable dictionary
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0.NULL, &kCFTypeDictionaryValueCallBacks);
// Get the RunLoop corresponding to the main thread
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
// Store runloop and thread in the dictionary, thread for key and runloop for value
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
if(! OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void *volatile *)&__CFRunLoops))
{
// Release the dictionary
CFRelease(dict);
}
/ / release the mainLoop
CFRelease(mainLoop);
/ / lock
__CFLock(&loopsLock);
}
// Get the loop object from the dictionary based on the thread t passed in
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
// Open loops
__CFUnlock(&loopsLock);
// If loop is empty
if(! loop) {// Create a new loop
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
// Loop loops
__CFLock(&loopsLock);
// Get loops from loops
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
if(! loop) {// If you don't find loop in the dictionary, put a new loop in the Loops dictionary
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
// don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
// Do not release runloop objects in the dictionary lock scope, because CFRunLoopDeallocate will release runloop objects
__CFUnlock(&loopsLock);
/ / release newLoop
CFRelease(newLoop);
}
// Check whether t is the current thread
if (pthread_equal(t, pthread_self()))
{
// Set Thread Specific Data (TSD), which is also a dictionary
// The __CFTSDKeyRunLoop is the key and the loop object value is stored in TSD
/** // Set some thread specific data in a pre-assigned slot. Don't pick a random value. Make sure you're using a slot that is unique. Pass in a destructor to free this data, or NULL if none is needed. Unlike pthread TSD, The destructor is per-thread. // When the TSD of pthreads is different, CF_EXPORT void *_CFSetTSD(uint32_t slot, void *newVal, void (*destructor)(void *)); * /
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr))
{
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS - 1), (void(*) (void*))__CFFinalizeRunLoop); }}return loop;
}
Copy the code
With the above source code, we have identified a data structure with a dictionary at the bottom. The dictionary stores pthread_t thread keys and CFRunLoopRef values. We can draw the following conclusions:
- Each thread has a unique RunLoop object corresponding to it
- Runloops are stored in a global Dictionary, with threads as keys and runloops as values
- The thread is created without a RunLoop object; the RunLoop is created the first time it gets it
- The RunLoop is destroyed at the end of the thread
- The RunLoop for the main thread is automatically acquired (created), and RunLoop is not enabled for the child thread by default
Runloop underlying
RunLoop Boot mode
void CFRunLoopRun(void)
{ /* DOES CALLOUT */
int32_t result;
do
{
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0 e10.false);
CHECK_FOR_FORK();
} while(kCFRunLoopRunStopped ! = result && kCFRunLoopRunFinished ! = result); }SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled)
{ /* DOES CALLOUT */
CHECK_FOR_FORK();
return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
Copy the code
From the above source:
- The default startup mode is through
CFRunLoopRun
To implement, the timeout is passed in1.0 e10
This is a large number, so it can be interpreted as no timeout. - You can customize the boot mode and set other parameters, such as the timeout period and mode.
RunLoop core logic
Let’s go to CFRunLoopRunSpecific:
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled)
{ /* DOES CALLOUT */
CHECK_FOR_FORK();
// If the runloop is being recycled, return kCFRunLoopRunFinished to indicate that the runloop is complete
if (__CFRunLoopIsDeallocating(rl))
return kCFRunLoopRunFinished;
// Lock runloop
__CFRunLoopLock(rl);
// Find the given mode from the runloop
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
// If no mode is found and currentMode of the current runloop is null, enter if logic
// if __CFRunLoopModeIsEmpty is empty, runloop has completed all tasks
if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode))
{
Boolean did = false;
// If currentMode is not null
if (currentMode)
// Unlock currentMode
__CFRunLoopModeUnlock(currentMode);
// Unlock runloop
__CFRunLoopUnlock(rl);
return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
}
// Temporarily extract per_run_data from runloop
volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
// Retrieve the current mode of runloop
CFRunLoopModeRef previousMode = rl->_currentMode;
// Assign the found mode to runloop's _curentMode, that is, the runloop completes the mode switch
rl->_currentMode = currentMode;
// Initialization returns result
int32_t result = kCFRunLoopRunFinished;
// If an Observer is registered to listen for kCFRunLoopEntry status (about to enter loop), notify the Observer
if (currentMode->_observerMask & kCFRunLoopEntry)
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
// The runloop core function __CFRunLoopRun
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
// Notify the Observer if the observer is registered to listen for kCFRunLoopExit status (loop is about to be rolled out)
if (currentMode->_observerMask & kCFRunLoopExit)
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
// Unlock currentMode
__CFRunLoopModeUnlock(currentMode);
// Restore the original previousPerRun
__CFRunLoopPopPerRunData(rl, previousPerRun);
// Restore the original mode
rl->_currentMode = previousMode;
// Unlock runloop
__CFRunLoopUnlock(rl);
return result;
}
Copy the code
After simplifying the code in this section:
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled)
{ /* DOES CALLOUT */
// Notify Observers to enter loop
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
// Specific things to do
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
// Notify Observers of exit loop
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
return result;
}
Copy the code
Work pays off, finally came to the core process __CFRunLoopRun function, to put it bluntly, a running cycle is a __CFRunLoopRun run.
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode)
Copy the code
There are five __CFRunLoopRun arguments, each representing:
rl
Object: CFRunLoopRefrlm
: Indicates the name of modeseconds
: Timeout periodstopAfterHandle
: Whether to return directly after processing sourcepreviousMode
: Mode of the previous run cycle
The complete and condensed __CFRunLoopRun code is provided below. Readers can choose to read it according to their own needs.
__CFRunLoopRun Complete code
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode)
{
// Get the start time
uint64_t startTSR = mach_absolute_time();
if (__CFRunLoopIsStopped(rl))
{
// If Runloop is stopped, change the stop status of the underlying data structure of Runloop to flag
__CFRunLoopUnsetStopped(rl);
// Return the status kCFRunLoopRunStopped
return kCFRunLoopRunStopped;
}
else if (rlm->_stopped)
{
// When Mode is stopped, change the Mode member _stopped to false
rlm->_stopped = false;
// Return the status kCFRunLoopRunStopped
return kCFRunLoopRunStopped;
}
// Determine whether there is a Mach Port for the main queue
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();
#if USE_DISPATCH_SOURCE_FOR_TIMERS
Fetch the corresponding Mach Port from the mode member variable _queue
mach_port_name_t modeQueuePort = MACH_PORT_NULL;
if (rlm->_queue)
{
modeQueuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue);
if(! modeQueuePort) { CRASH("Unable to get port for run loop mode queue (%d)".- 1); }}#endif
// Declare an empty GCD timer
dispatch_source_t timeout_timer = NULL;
// Initializes a timeout context structure pointer object
struct __timeout_context *timeout_context = (struct __timeout_context *)malloc(sizeof(*timeout_context));
// If the runloop timeout is less than or equal to 0
if (seconds <= 0.0)
{ // instant timeout
// Set seconds to 0s
seconds = 0.0;
// set the termTSR property of the timeout context to 0,0 ULL for an Unsigned Long Long
timeout_context->termTSR = 0ULL;
}
else if (seconds <= TIMER_INTERVAL_LIMIT)
{
// If the runloop timeout is less than or equal to TIMER_INTERVAL_LIMIT
If the main thread exists, get the main queue of the main thread
// If the main thread does not exist, get the global queue
dispatch_queue_t queue = pthread_main_np() ? __CFDispatchQueueGetGenericMatchingMain() : __CFDispatchQueueGetGenericBackground();
// Pass in the queue and initialize a GCD object of type timer
timeout_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0.0.queue);
// Retain the GCD object
dispatch_retain(timeout_timer);
// Assign the GCD object to the ds property of the timeout context structure
timeout_context->ds = timeout_timer;
// Retain the runloop object and assign to the RL property of the Timeout context structure
timeout_context->rl = (CFRunLoopRef)CFRetain(rl);
// Add the start time of the runloop to the time to run to get the runloop life time
// Then assign to the termTSR property of the timeout context structure
timeout_context->termTSR = startTSR + __CFTimeIntervalToTSR(seconds);
// Sets the context of the GCD object to a custom "timeout context" structure
dispatch_set_context(timeout_timer, timeout_context); // source gets ownership of context
// Setting the callback of the GCD object can be understood as the task to be performed
/** static void __CFRunLoopTimeout(void *arg) { struct __timeout_context *context = (struct __timeout_context *)arg; context->termTSR = 0ULL; CFRUNLOOP_WAKEUP_FOR_TIMEOUT(); CFRunLoopWakeUp(context->rl); // The interval is DISPATCH_TIME_FOREVER, so this won't fire again} 2. The CFRUNLOOP_WAKEUP_FOR_TIMEOUT() macro is inside a useless do-while(0) operation that can be ignored by 3. The runloop */ is then woken up by the CFRunLoopWakeUp function
dispatch_source_set_event_handler_f(timeout_timer, __CFRunLoopTimeout);
// Set the cancellation callback of the GCD object
dispatch_source_set_cancel_handler_f(timeout_timer, __CFRunLoopTimeoutCancel);
// Add the start time to the given runloop expiration time, because the result is in seconds and needs to be converted to nanoseconds, so multiply by 10 ^ 9
uint64_t ns_at = (uint64_t)((__CFTSRToTimeInterval(startTSR) + seconds) * 1000000000ULL);
// Set the GCD timer
// The second argument indicates when the timer starts, that is, if CFRunLoopRun comes in
// Since the value passed in is 1.0e10, the timer will never fire, which means the runloop will never time out
// The third parameter indicates the timer interval of nanoseconds. The DISPATCH_TIME_FOREVER parameter is passed, indicating that the timer is fired only once, not multiple times
// The fourth parameter indicates the nanosecond fault tolerance time of the timer
dispatch_source_set_timer(timeout_timer, dispatch_time(1, ns_at), DISPATCH_TIME_FOREVER, 1000ULL);
// Start GCD timer
dispatch_resume(timeout_timer);
}
else
{ // infinite timeout
// Set the timeout to 9999999999
// Then set the termTSR property of the custom Timeout context structure to UNIT64_MAX
seconds = 9999999999.0;
timeout_context->termTSR = UINT64_MAX;
}
// Initialize whether a Boolean was last sent through the Dispatch port
Boolean didDispatchPortLastTime = true;
int32_t retVal = 0;
do
{
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
voucher_mach_msg_state_t voucherState = VOUCHER_MACH_MSG_STATE_UNCHANGED;
voucher_t voucherCopy = NULL;
#endif
// Initialize an unsigned int8 integer message buffer with a size of 3KB
uint8_t msg_buffer[3 * 1024];
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
// Initialize an empty MSG structure pointer variable of type mach_MSG_header_t
mach_msg_header_t *msg = NULL;
// Initialize a port with the value MACH_PORT_NULL
mach_port_t livePort = MACH_PORT_NULL;
// get _portSet from mode
__CFPortSet waitSet = rlm->_portSet;
// Set ignoreWakeUps to 0 for runloop's underlying data structure
__CFRunLoopUnsetIgnoreWakeUps(rl);
// Check whether the mask and upper kCFRunLoopBeforeTimers enumeration values of the Mode observer are 1
// If successful, notify Observers that Timers are about to be processed
if (rlm->_observerMask & kCFRunLoopBeforeTimers)
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
// Check if the mask and the kCFRunLoopBeforeSources enumeration are 1 in the observer of mode
// If successful, notify Observers that they are about to handle Sources
if (rlm->_observerMask & kCFRunLoopBeforeSources)
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
// Process Blocks, passing runloop and mode
__CFRunLoopDoBlocks(rl, rlm);
// Process Source0, passing runloop and mode, and stopAfterHandle
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
// If processing Sources0 succeeds, process Blocks again
if (sourceHandledThisLoop)
{
__CFRunLoopDoBlocks(rl, rlm);
}
// If source0 succeeds or the timeout is 0, set poll to true
Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
// If the primary queue port is not empty and the last loop did not have a primary queue port
if(MACH_PORT_NULL ! = dispatchPort && ! didDispatchPortLastTime) {#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
// Initialize a MSG of the mach_MSG_header_t structure
msg = (mach_msg_header_t *)msg_buffer;
// Check whether there is a message on the port of the main queue. If so, enter handle_msg
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL))
{
// There is a message coming from the port of the main queue and runloop needs to be woken up
goto handle_msg;
}
#endif
}
// Make the last loop main queue value empty
didDispatchPortLastTime = false;
// No message is pulled from port, and mode's _observerMask is true with kCFRunLoopBeforeWaiting,
// Notify Observers that they are about to hibernate
if(! poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);/** CF_INLINE void __CFRunLoopSetSleeping(CFRunLoopRef rl) { __CFBitfieldSetValue(((CFRuntimeBase *)rl)->_cfinfo[CF_INFO_BITS], 1, 1, 1); } * /
// Set the flag for runloop sleeping
__CFRunLoopSetSleeping(rl);
// do not do any user callouts after this point (after notifying of sleeping)
// Do not make any user calls after runloop has gone to sleep
// Must push the local-to-this-activation ports in on every loop
// iteration, as this mode could be run re-entrantly and we don't
// want these ports to get serviced.
/** // The local-to-this-activation ports must be pushed in // iteration for each loop, since this mode can be run again and we do not // want these ports to be serviced. * /
// Add the primary queue port to waitSet
__CFPortSetInsert(dispatchPort, waitSet);
/ / unlock mode
__CFRunLoopModeUnlock(rlm);
/ / unlock runloop
__CFRunLoopUnlock(rl);
SleepStart is 0 if poll is true, otherwise the current time
CFAbsoluteTime sleepStart = poll ? 0.0 : CFAbsoluteTimeGetCurrent();
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
#if USE_DISPATCH_SOURCE_FOR_TIMERS
// This do-while loop is for the GCD timer
do
{
if (kCFUseCollectableAllocator)
{
// objc_clear_stack(0);
// <rdar://problem/16393959>
// Set the msg_buffer buffer to 0
memset(msg_buffer, 0.sizeof(msg_buffer));
}
msg = (mach_msg_header_t *)msg_buffer;
// Check whether the port of waitSet has a message
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
// Check that modeQueuePort is not empty and livePort is modeQueuePort
if(modeQueuePort ! = MACH_PORT_NULL && livePort == modeQueuePort) {// Drain the internal queue. If one of the callout blocks sets the timerFired flag, break out and service the timer.
/** Clears the internal queue. If one of the annotation blocks has the timerFired flag set, interrupt and service the timer. * /
while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue))
;
// If mode's _timerFired is true, set it to false and exit the do-while loop
if (rlm->_timerFired)
{
// Leave livePort as the queue port, and service timers below
rlm->_timerFired = false;
break;
}
else
{
if(msg && msg ! = (mach_msg_header_t *)msg_buffer)
free(msg); }}else
{
// Go ahead and leave the inner loop.
break; }}while (1);
#else
if (kCFUseCollectableAllocator)
{
// objc_clear_stack(0);
// <rdar://problem/16393959>
// Set the msg_buffer buffer to 0
memset(msg_buffer, 0.sizeof(msg_buffer));
}
msg = (mach_msg_header_t *)msg_buffer;
// Check whether the port of waitSet has a message
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
#endif
#endif
// Lock runloop
__CFRunLoopLock(rl);
// Lock mode
__CFRunLoopModeLock(rlm);
// If poll is true, the home team wakes up the Runloop; if poll is true, the home team gets the runloop's sleep time
rl->_sleepTime += (poll ? 0.0 : (CFAbsoluteTimeGetCurrent() - sleepStart));
// Must remove the local-to-this-activation ports in on every loop
// iteration, as this mode could be run re-entrantly and we don't
// want these ports to get serviced. Also, we don't want them left
// in there if this function returns.
/** CF_INLINE kern_return_t __CFPortSetRemove(__CFPort port, __CFPortSet portSet) { if (MACH_PORT_NULL == port) { return -1; } return mach_port_extract_member(mach_task_self(), port, portSet); } * /
// Remove dispatchPort from waitSet because dispatchPort is added to waitSet in each runloop iteration
__CFPortSetRemove(dispatchPort, waitSet);
/** CF_INLINE void __CFRunLoopSetIgnoreWakeUps(CFRunLoopRef rl) { rl->_perRunData->ignoreWakeUps = 0x57414B45; // 'WAKE'} WAKE runloop */
__CFRunLoopSetIgnoreWakeUps(rl);
// user callouts now OK again
// Set the sleeping flag of runloop to 0
__CFRunLoopUnsetSleeping(rl);
// Notify Observers to end hibernation
if(! poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting); handle_msg:;/** CF_INLINE void __CFRunLoopSetIgnoreWakeUps(CFRunLoopRef rl) { rl->_perRunData->ignoreWakeUps = 0x57414B45; // 'WAKE'} indicates that a port message has been received and needs to be processed
__CFRunLoopSetIgnoreWakeUps(rl);
// If the wakeup port is empty, nothing is done
if (MACH_PORT_NULL == livePort)
{
CFRUNLOOP_WAKEUP_FOR_NOTHING();
// handle nothing
}
// If the wakeup port is _wakeUpPort of runloop, CFRUNLOOP_WAKEUP_FOR_WAKEUP() is called
// CFRUNLOOP_WAKEUP_FOR_WAKEUP() does nothing
else if (livePort == rl->_wakeUpPort)
{
CFRUNLOOP_WAKEUP_FOR_WAKEUP();
// do nothing on Mac OS
}
#if USE_DISPATCH_SOURCE_FOR_TIMERS
// If the wake port is modeQueuePort, the timer woke runloop
else if(modeQueuePort ! = MACH_PORT_NULL && livePort == modeQueuePort) {// Call CFRUNLOOP_WAKEUP_FOR_TIMER,
CFRUNLOOP_WAKEUP_FOR_TIMER();
/ / the timer
if(! __CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {// Re-arm the next timer, because we apparently fired early__CFArmNextTimerInMode(rlm, rl); }}#endif
#if USE_MK_TIMER_TOO
// If the wakeup port is mode's _timerPort
else if(rlm->_timerPort ! = MACH_PORT_NULL && livePort == rlm->_timerPort) { CFRUNLOOP_WAKEUP_FOR_TIMER();// On Windows, we have observed an issue where the timer port is set before the time which we requested it to be set. For example, we set the fire time to be TSR 167646765860, but it is actually observed firing at TSR 167646764145, which is 1715 ticks early. The result is that, when __CFRunLoopDoTimers checks to see if any of the run loop timers should be firing, it appears to be 'too early' for the next timer, and no timers are handled.
// In this case, the timer port has been automatically reset (since it was returned from MsgWaitForMultipleObjectsEx), and if we do not re-arm it, then no timers will ever be serviced again unless something adjusts the timer list (e.g. adding or removing timers). The fix for the issue is to reset the timer here if CFRunLoopDoTimers did not handle a timer itself. 9308754
/ / the timer
if(! __CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {// Re-arm the next timer__CFArmNextTimerInMode(rlm, rl); }}#endif
// If the wake port is dispatchPort, it is the primary queue that wakes Runloop
else if (livePort == dispatchPort)
{
// Execute CFRUNLOOP_WAKEUP_FOR_DISPATCH()
CFRUNLOOP_WAKEUP_FOR_DISPATCH();
/ / unlock mode
__CFRunLoopModeUnlock(rlm);
/ / unlock runloop
__CFRunLoopUnlock(rl);
// set TSD with __CFTSDKeyIsInGCDMainQ as key and 6 as value
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6.NULL);
// Perform the main queue task
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
// set TSD with __CFTSDKeyIsInGCDMainQ as key and 0 as value
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0.NULL);
// Lock runloop
__CFRunLoopLock(rl);
// Lock mode
__CFRunLoopModeLock(rlm);
// flag that this loop handles the primary queue
sourceHandledThisLoop = true;
didDispatchPortLastTime = true;
}
else
{
// The source1 awakens
CFRUNLOOP_WAKEUP_FOR_SOURCE();
// 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);
// Despite the name, this works for windows handles as well
// Fetch source1 from the port
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;
// Execute the source1z task
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
if (NULL! = reply) { (void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL); CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply); }}// Restore the previous voucher
_CFSetTSD(__CFTSDKeyMachMessageHasVoucher, previousVoucher, os_release);
}
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
if(msg && msg ! = (mach_msg_header_t *)msg_buffer)
free(msg);
#endif
// Process Blocks, passing runloop and mode
__CFRunLoopDoBlocks(rl, rlm);
/** CFRunLoopRunInMode() // Reasons for CFRunLoopRunInMode() to Return enum {kCFRunLoopRunFinished = 1, Loop kCFRunLoopRunStopped = 2, loop kCFRunLoopRunTimedOut = 3, / / loop timeout kCFRunLoopRunHandledSource = 4 / / loop for the home side column mission}; * /
Exit the do-while loop if the main queue task is executed and stopAfterHandle is true
if (sourceHandledThisLoop && stopAfterHandle)
{
retVal = kCFRunLoopRunHandledSource;
}
// If time out, exit the do-while loop
else if (timeout_context->termTSR < mach_absolute_time())
{
retVal = kCFRunLoopRunTimedOut;
}
// If runloop has stopped, exit the do-while loop
else if (__CFRunLoopIsStopped(rl))
{
__CFRunLoopUnsetStopped(rl);
retVal = kCFRunLoopRunStopped;
}
// Exit the do-while loop if the _stopped property of mode is true
else if (rlm->_stopped)
{
rlm->_stopped = false;
retVal = kCFRunLoopRunStopped;
}
// If mode is null, exit the do-while loop
else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode))
{
retVal = kCFRunLoopRunFinished;
}
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
voucher_mach_msg_revert(voucherState);
os_release(voucherCopy);
#endif
} while (0 == retVal);
// If the GCD timer is enabled
if (timeout_timer)
{
// Stop the timer and release the resource
dispatch_source_cancel(timeout_timer);
dispatch_release(timeout_timer);
}
else
{
// Release the timeout context structure pointer
free(timeout_context);
}
return retVal;
}
Copy the code
__CFRunLoopRun Simplifies code
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode)
{
int32_t retVal = 0;
do
{
// Notify Observers that Timers are about to be processed
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
// Notify Observers that they are about to process Sources
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
/ / processing Blocks
__CFRunLoopDoBlocks(rl, rlm);
/ / Source0 processing
if (__CFRunLoopDoSources0(rl, rlm, stopAfterHandle))
{
/ / processing Blocks
__CFRunLoopDoBlocks(rl, rlm);
}
Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
// Check whether Source1 exists
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL))
{
// If there is Source1, jump to handle_msg
goto handle_msg;
}
didDispatchPortLastTime = false;
// Notify Observers that they are about to hibernate
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
__CFRunLoopSetSleeping(rl);
CFAbsoluteTime sleepStart = poll ? 0.0 : CFAbsoluteTimeGetCurrent();
do
{
if (kCFUseCollectableAllocator)
{
// objc_clear_stack(0);
// <rdar://problem/16393959>
memset(msg_buffer, 0.sizeof(msg_buffer));
}
msg = (mach_msg_header_t *)msg_buffer;
// Wait for another message to wake up the current thread
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
if(modeQueuePort ! = MACH_PORT_NULL && livePort == modeQueuePort) {// Drain the internal queue. If one of the callout blocks sets the timerFired flag, break out and service the timer.
while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue))
;
if (rlm->_timerFired)
{
// Leave livePort as the queue port, and service timers below
rlm->_timerFired = false;
break;
}
else
{
if(msg && msg ! = (mach_msg_header_t *)msg_buffer)
free(msg); }}else
{
// Go ahead and leave the inner loop.
break; }}while (1);
// user callouts now OK again
__CFRunLoopUnsetSleeping(rl);
// Notify Observers to end hibernation
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
handle_msg:
if(Awakened by timer) {/ / deal with timers
__CFRunLoopDoTimers(rl, rlm, mach_absolute_time());
}
else if(awakened by GCD) {// Process the GCD main queue
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
}
else
{
// Wake up by Source1
__CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
}
/ / processing Blocks
__CFRunLoopDoBlocks(rl, rlm);
if (sourceHandledThisLoop && stopAfterHandle)
{
retVal = kCFRunLoopRunHandledSource;
}
else if (timeout_context->termTSR < mach_absolute_time())
{
retVal = kCFRunLoopRunTimedOut;
}
else if (__CFRunLoopIsStopped(rl))
{
__CFRunLoopUnsetStopped(rl);
retVal = kCFRunLoopRunStopped;
}
else if (rlm->_stopped)
{
rlm->_stopped = false;
retVal = kCFRunLoopRunStopped;
}
else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode))
{
retVal = kCFRunLoopRunFinished;
}
voucher_mach_msg_revert(voucherState);
os_release(voucherCopy);
} while (0 == retVal);
return retVal;
}
Copy the code
Tasks performed by RunLoop
From __CFRunLoopRun, we know that RunLoop performs the following tasks:
__CFRunLoopDoObservers
__CFRunLoopDoBlocks
__CFRunLoopDoSources0
__CFRunLoopDoSource1
__CFRunLoopDoTimers
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
Below, we will analyze one by one:
__CFRunLoopDoObservers
/* rl is locked, rlm is locked on entrance and exit */
static void __CFRunLoopDoObservers() __attribute__((noinline));
static void __CFRunLoopDoObservers(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopActivity activity)
{ /* DOES CALLOUT */
CHECK_FOR_FORK();
// Check whether the number of observers corresponding to the passed mode is 0
CFIndex cnt = rlm->_observers ? CFArrayGetCount(rlm->_observers) : 0;
if (cnt < 1)
return;
/* Fire the observers */
// #define STACK_BUFFER_DECL(T, N, C) T N[C]
// There are two cases
// If the number of observers in mode is less than or equal to 1024, then it equals CFRunLoopObserverRef buffer[CNT]
// If the number of observers in mode is greater than 1024, it is equivalent to CFRunLoopObserverRef buffer[1]
// So the cache size is allocated by the number of observers
STACK_BUFFER_DECL(CFRunLoopObserverRef, buffer, (cnt <= 1024)? cnt :1);
// If the number of observers is less than 1024, assign the buffer cache obtained in the previous step to collectedObservers
// If the number of observers is greater than 1024, the malloc function is called to allocate space. The final space size is = number of observers * CFRunLoopObserverRef type memory size
// We get an array pointer variable collectedObservers of type CFRunLoopObserverRef
CFRunLoopObserverRef *collectedObservers = (cnt <= 1024)? buffer : (CFRunLoopObserverRef *)malloc(cnt * sizeof(CFRunLoopObserverRef));
CFIndex obs_cnt = 0;
Observers // loop through all observers in the incoming runloop mode
for (CFIndex idx = 0; idx < cnt; idx++)
{
// Take an observer from the observers collection
CFRunLoopObserverRef rlo = (CFRunLoopObserverRef)CFArrayGetValueAtIndex(rlm->_observers, idx);
// Enter the logic inside if if the following three conditions are met
// 1. Check whether the current observer state matches the incoming state by using the and operation
// 2. Check whether the observer is valid
/** // Bit 3 in the base reserved bits is used for invalid state in run loop objects CF_INLINE Boolean __CFIsValid(const void *cf) { return (Boolean)__CFBitfieldGetValue(((const CFRuntimeBase *)cf)->_cfinfo[CF_INFO_BITS], 3, 3); } CF_INLINE void __CFSetValid(void *cf) { __CFBitfieldSetValue(((CFRuntimeBase *)cf)->_cfinfo[CF_INFO_BITS], 3, 3, 1); } CF_INLINE void __CFUnsetValid(void *cf) { __CFBitfieldSetValue(((CFRuntimeBase *)cf)->_cfinfo[CF_INFO_BITS], 3, 3, 0); } * /
// 3. Check whether the Observer has been fired
/** Bit 0 of the base reserved bits is used for firing state Bit 1 of the base reserved bits is used for repeats state CF_INLINE Boolean __CFRunLoopObserverIsFiring(CFRunLoopObserverRef rlo) { return (Boolean)__CFBitfieldGetValue(((const CFRuntimeBase *)rlo)->_cfinfo[CF_INFO_BITS], 0, 0); } * /
/** After exploring the second and third steps, you can see that the CFRuntimeBase Bit 0 is used to indicate the startup state, Bit 1 is used to indicate the repeated state, and Bit 3 is used to indicate the available state of the runloop object */
if (0! = (rlo->_activities & activity) && __CFIsValid(rlo) && ! __CFRunLoopObserverIsFiring(rlo)) {Retain the Observer and store it into the collectedObservers collection
// The CoreFoundation framework requires manual MRC, that is, CFRetain is called to connect to the received object. At the same time, a paired CFRelease operation is required to prevent memory leakscollectedObservers[obs_cnt++] = (CFRunLoopObserverRef)CFRetain(rlo); }}// Unlocks the incoming mode
// The underlying implementation is a mutex
/** CF_INLINE void __CFRunLoopModeUnlock(CFRunLoopModeRef rlm) { //CFLog(6, CFSTR("__CFRunLoopModeLock unlocking %p"), rlm); pthread_mutex_unlock(&(rlm->_lock)); } * /
__CFRunLoopModeUnlock(rlm);
// Unlocks the incoming runloop
// The underlying implementation is a mutex
/** CF_INLINE void __CFRunLoopUnlock(CFRunLoopRef rl) { // CFLog(6, CFSTR("__CFRunLoopLock unlocking %p"), rl); pthread_mutex_unlock(&(((CFRunLoopRef)rl)->_lock)); } * /
__CFRunLoopUnlock(rl);
// Iterate according to the variable obs_cnt, which is the number of valid observers, inside the for loop above
for (CFIndex idx = 0; idx < obs_cnt; idx++)
{
// Retrieve an observer
CFRunLoopObserverRef rlo = collectedObservers[idx];
// Lock the observer
// The underlying implementation is a mutex
/** CF_INLINE void __CFRunLoopObserverLock(CFRunLoopObserverRef rlo) { pthread_mutex_lock(&(rlo->_lock)); // CFLog(6, CFSTR("__CFRunLoopObserverLock locked %p"), rlo); } * /
__CFRunLoopObserverLock(rlo);
// Check again if the Observer is invalid
if (__CFIsValid(rlo))
{
// Retrieve the repeated state from the observer, then invert it and assign it to the Boolean variable doInvalidate
// If the observer repeat is true, then doInvalidate is false.
// If observer repeat is false, doInvalidate is true,
/ / is called CFRunLoopObserverInvalidate to the observer from the subordinate runloop, then the observer is releasedBoolean doInvalidate = ! __CFRunLoopObserverRepeats(rlo);// Set the observer runtimeBase bit 0 to 1, indicating that the observer has been started
/** CF_INLINE void __CFRunLoopObserverSetFiring(CFRunLoopObserverRef rlo) { __CFBitfieldSetValue(((CFRuntimeBase *)rlo)->_cfinfo[CF_INFO_BITS], 0, 0, 1); } * /
__CFRunLoopObserverSetFiring(rlo);
// Unlock observer
__CFRunLoopObserverUnlock(rlo);
// Call the __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ function, passing the observer's corresponding callout, and the observer itself, The current status of runloop and observer context information
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(rlo->_callout, rlo, activity, rlo->_context.info);
if (doInvalidate)
{
CFRunLoopObserverInvalidate(rlo);
}
// Remove firing status of the Observer
__CFRunLoopObserverUnsetFiring(rlo);
}
else
{
// Note The Observer is invalid and can be unlocked
__CFRunLoopObserverUnlock(rlo);
}
// Since the Observer has either triggered a callback or has failed to do anything, it is necessary to release the Observer and start the next loop
CFRelease(rlo);
}
// Lock runloop
__CFRunLoopLock(rl);
// Lock mode
__CFRunLoopModeLock(rlm);
// If the set is not equal to buffer, it is initialized by malloc and free is required
if(collectedObservers ! = buffer)free(collectedObservers);
}
Copy the code
We can see that the __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ is finally executed, and we go inside it:
static void __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__() __attribute__((noinline));
static void __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(CFRunLoopObserverCallBack func, CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
if (func)
{
func(observer, activity, info);
}
asm __volatile__(""); // thwart tail-call optimization
}
Copy the code
__CFRunLoopDoBlocks
/** Matches mode from runloop's _block_item list, and executes the block task */
static Boolean __CFRunLoopDoBlocks(CFRunLoopRef rl, CFRunLoopModeRef rlm)
{ // Call with rl and rlm locked
Struct _block_item *_blocks_head; Struct _block_item *_blocks_tail; Struct _block_item {struct _block_item *_next; // next _block_item CFTypeRef _mode; // CFString or CFSet // mode can be a string or a set void (^_block)(void); // block }; So the underlying runloop stores the _block_item structure pointer object */ as a single linked list
// If the block header pointer is null, the current runloop has no blocks to execute and returns false
if(! rl->_blocks_head)return false;
// If mode is empty or the mode name is empty, false is also returned
if(! rlm || ! rlm->_name)return false;
// Whether a block is executed
Boolean did = false;
// Retrieve the block header pointer
struct _block_item *head = rl- > _blocks_head;
// Fetch the end pointer to the block list
struct _block_item *tail = rl- > _blocks_tail;
// Empty the block header pointer on the runloop
rl->_blocks_head = NULL;
// Void the end pointer of the block list on runloop
rl->_blocks_tail = NULL;
// Get commonModes of runloop
CFSetRef commonModes = rl->_commonModes;
// Get the name of the mode passed in
CFStringRef curMode = rlm->_name;
// Unlock mode
__CFRunLoopModeUnlock(rlm);
// Unlock runloop
__CFRunLoopUnlock(rl);
// Initialize a null pointer
struct _block_item *prev = NULL;
// Initialize a pointer to the head of the list
struct _block_item *item = head;
// Iterate through the block list from the obtained block header pointer
while (item)
{
// The current _block_item structure variable
struct _block_item *curr = item;
// Move the pointer forward one position for the next loop
item = item->_next;
// Initialize the doit Boolean value
Boolean doit = false;
// Determine whether the mode of _block_item is a string or a set, that is, whether the block_item corresponds to one mode or multiple modes
if (CFStringGetTypeID() == CFGetTypeID(curr->_mode))
{
// indicates a mode
// Determine the two cases
// 1. Check whether the mode passed in is equal to the mode inside block_item
// 2. Check whether the mode inside block_item is kCFRunLoopCommonModes, and check whether the commonModes are passed in
// If one of the above conditions is true, doit is false otherwise
doit = CFEqual(curr->_mode, curMode) || (CFEqual(curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(commonModes, curMode));
}
else
{
// Indicates multiple modes
// Also judge two cases
// 1. Check whether the mode set inside _block_item contains the mode passed in
// 2. Check whether the mode set inside _block_item contains kCFRunLoopCommonModes and whether there are any passed modes in the commonModes
// If one of the above conditions is true, doit is false otherwise
doit = CFSetContainsValue((CFSetRef)curr->_mode, curMode) || (CFSetContainsValue((CFSetRef)curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(commonModes, curMode));
}
As long as the _block_item node satisfies the mode matching relation, it executes its internal block task
// If doit is false, the block task is not _block_item, so prev points to _block_item
// Since item = item->_next has been done previously, item already refers to the next _block_item
if(! doit) prev = curr;// If doit is true, the current _block_item is used to execute the block task
if (doit)
{
// This can be divided into three cases
The destination node is any node between the beginning and the end
// The target node is the head node
// The destination node is the last node
// The target node needs to be deleted because the block task has been completed
// Check whether the prev pointer is null. If not, point _next of the prev pointer to item. Note that item is the _block_item to traverse next time
// The function here is case 1
if (prev)
prev->_next = item;
// There are two boundary values, namely, the task to execute the block is the head node or the tail node
// Determine if the _block_time currently being traversed is equal to the head pointer
// Since the head points to the head of the list before entering the while loop, it can enter the condition of the logic inside the if
// The head is the node where the block task is to be executed. At this point, the head pointer points to the next node. At the end of the loop, there is a judgment,
// If head is not empty, then rl->_blocks_head is redirected to head, that is, the node that performed the block and happened to be the head node is deleted.
// The function here is case two
if (curr == head)
head = item;
// Determine if the _block_time being traversed is equal to the tail pointer
// Since the tail pointer is the last node of the list pointed to before the while loop, it can enter the logic condition inside if
// The tail pointer points to the same node as the prev pointer.
// The prev pointer points to the preceding node of the tail node, which is the last node of the block
// The function here is case 3
if (curr == tail)
tail = prev;
// Retrieve the block in _block_item that is currently being traversed
void (^block)(void) = curr->_block;
// Releases the mode for _block_item if it is currently being traversed
CFRelease(curr->_mode);
// Release the _block_item that is currently being traversed
free(curr);
// Judge doIT again
if (doit)
{
// Pass the block to execute the task
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
// Set did to true
did = true;
}
/ / release the block
Block_release(block); // do this before relocking to prevent deadlocks where some yahoo wants to run the run loop reentrantly from their dealloc
// Releasing blcok before relocking prevents programmers from rerunning runloop in the dealloc method and causing deadlocks}}// Lock runloop
__CFRunLoopLock(rl);
// Lock mode
__CFRunLoopModeLock(rlm);
// If the head pointer is not null, the if logic is also required
if (head)
{
Rl -> _blockS_head = tail->_next = NULL
tail->_next = rl->_blocks_head;
// make rl->_blocks_head point to the node to which the head pointer points
Rl ->_blocks_head = rl->_blocks_head = rl->_blocks_head
// If the block to execute is found in the while loop above, head points to
rl->_blocks_head = head;
// The rl->_blocks_tail pointer is null, so the if logic must be advanced
Rl ->_blocks_tail points to the node to which the tail pointer points
if(! rl->_blocks_tail) rl->_blocks_tail = tail; }return did;
}
Copy the code
You can see that the actual callback is performed by the CFRUNLOOP_IS_CALLING_OUT_TO_A__BLOCK function, which has the following internal implementation
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__() __attribute__((noinline));
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(void (^block)(void))
{
if (block)
{
block();
}
asm __volatile__(""); // thwart tail-call optimization
}
Copy the code
__CFRunLoopDoSources0
/* rl is locked, rlm is locked on entrance and exit */
static Boolean __CFRunLoopDoSources0(CFRunLoopRef rl, CFRunLoopModeRef rlm, Boolean stopAfterHandle) __attribute__((noinline));
static Boolean __CFRunLoopDoSources0(CFRunLoopRef rl, CFRunLoopModeRef rlm, Boolean stopAfterHandle)
{ /* DOES CALLOUT */
CHECK_FOR_FORK();
// Initialize sources and empty it
CFTypeRef sources = NULL;
// initialize the return value sourceHandled and set it to false to indicate whether source0 has been handled
Boolean sourceHandled = false;
/* Fire the version 0 sources */
if (NULL! = rlm->_sources0 &&0 < CFSetGetCount(rlm->_sources0))
{
// Check that mode _source0 is not empty and has a size greater than 0
// CFSetApplyFunction (Calls a function once for each value in the set.)
// This function executes the passed pointer to each element in the passed set
// The first argument is set
// The second argument is a function pointer to be executed once on each element of set
// The third argument is passed as the second argument to the function pointer
CFSetApplyFunction(rlm->_sources0, (__CFRunLoopCollectSources0), &sources);
}
Source0 = source0 = source0
if (NULL! = sources) {// Unlock mode
__CFRunLoopModeUnlock(rlm);
// Unlock runloop
__CFRunLoopUnlock(rl);
// Sources can be a runloop source or an array of runloop sources
// sources is either a single (retained) CFRunLoopSourceRef or an array of (retained) CFRunLoopSourceRef
if (CFGetTypeID(sources) == CFRunLoopSourceGetTypeID())
{
Sources is a single runloop source
// The type is strong
CFRunLoopSourceRef rls = (CFRunLoopSourceRef)sources;
// Lock RLS
__CFRunLoopSourceLock(rls);
if (__CFRunLoopSourceIsSignaled(rls))
{
Signaled RLS is signaled
Signaled the signs off
__CFRunLoopSourceUnsetSignaled(rls);
// Determine whether the RLS is valid
if (__CFIsValid(rls))
{
// Unlock the RLS
__CFRunLoopSourceUnlock(rls);
// Execute the source0 callback
/** static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__() __attribute__((noinline)); static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(void (*perform)(void *), void *info) { if (perform) { perform(info); } asm __volatile__(""); // thwart tail-call optimization } */
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(rls->_context.version0.perform, rls->_context.version0.info);
CHECK_FOR_FORK();
// Set the result of processing source0 to true
sourceHandled = true;
}
else{ __CFRunLoopSourceUnlock(rls); }}else
{
Signaled RLS is not signaled__CFRunLoopSourceUnlock(rls); }}else
{
// sources is an array
// Fetch the number of source0
CFIndex cnt = CFArrayGetCount((CFArrayRef)sources);
// Sort sources, ascending by _order
/** static CFComparisonResult __CFRunLoopSourceComparator(const void *val1, const void *val2, void *context) { CFRunLoopSourceRef o1 = (CFRunLoopSourceRef)val1; CFRunLoopSourceRef o2 = (CFRunLoopSourceRef)val2; if (o1->_order < o2->_order) return kCFCompareLessThan; if (o2->_order < o1->_order) return kCFCompareGreaterThan; return kCFCompareEqualTo; } * /
CFArraySortValues((CFMutableArrayRef)sources, CFRangeMake(0, cnt), (__CFRunLoopSourceComparator), NULL);
// Iterate through the sources array
for (CFIndex idx = 0; idx < cnt; idx++)
{
// Fetch a source0 RLS
CFRunLoopSourceRef rls = (CFRunLoopSourceRef)CFArrayGetValueAtIndex((CFArrayRef)sources, idx);
// Lock RLS
__CFRunLoopSourceLock(rls);
if (__CFRunLoopSourceIsSignaled(rls))
{
Signaled RLS is signaled
Signaled the signs off
__CFRunLoopSourceUnsetSignaled(rls);
// Determine whether the RLS is valid
if (__CFIsValid(rls))
{
// Unlock the RLS
__CFRunLoopSourceUnlock(rls);
// Execute the source0 callback
/** static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__() __attribute__((noinline)); static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(void (*perform)(void *), void *info) { if (perform) { perform(info); } asm __volatile__(""); // thwart tail-call optimization } */
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(rls->_context.version0.perform, rls->_context.version0.info);
CHECK_FOR_FORK();
// Set the result of processing source0 to true
sourceHandled = true;
}
else{ __CFRunLoopSourceUnlock(rls); }}else
{
Signaled RLS is not signaled
__CFRunLoopSourceUnlock(rls);
}
// If a source0 process is complete and stopAfterHandle is true, the loop is broken
if (stopAfterHandle && sourceHandled)
{
break; }}}/ / release sources
CFRelease(sources);
// Lock runloop
__CFRunLoopLock(rl);
// Unlock mode
__CFRunLoopModeLock(rlm);
}
// returns the result of processing source0
return sourceHandled;
}
Copy the code
The actual callback is the __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ function, which is internally implemented as follows
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__() __attribute__((noinline));
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(void (*perform)(void *), void *info)
{
if (perform)
{
perform(info);
}
asm __volatile__(""); // thwart tail-call optimization
}
Copy the code
__CFRunLoopDoSource1
// msg, size and reply are unused on Windows
static Boolean __CFRunLoopDoSource1() __attribute__((noinline));
static Boolean __CFRunLoopDoSource1(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopSourceRef rls
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
,
mach_msg_header_t *msg, CFIndex size, mach_msg_header_t **reply
#endif
)
{ /* DOES CALLOUT */
CHECK_FOR_FORK();
// Initialize whether the source1 result was successfully processed
Boolean sourceHandled = false;
/* Fire a version 1 source */
// Retian source1
CFRetain(rls);
// Unlock mode
__CFRunLoopModeUnlock(rlm);
// Unlock runloop
__CFRunLoopUnlock(rl);
// lock source1
__CFRunLoopSourceLock(rls);
// If source1 is valid
if (__CFIsValid(rls))
{
/** // Bit 1 of the base reserved bits is used for signalled state CF_INLINE Boolean __CFRunLoopSourceIsSignaled(CFRunLoopSourceRef rls) { return (Boolean)__CFBitfieldGetValue(rls->_bits, 1, 1); } CF_INLINE void __CFRunLoopSourceSetSignaled(CFRunLoopSourceRef rls) { __CFBitfieldSetValue(rls->_bits, 1, 1, 1); } CF_INLINE void __CFRunLoopSourceUnsetSignaled(CFRunLoopSourceRef rls) { __CFBitfieldSetValue(rls->_bits, 1, 1, 0); } * /
// Set the source1 status to inactive
__CFRunLoopSourceUnsetSignaled(rls);
/ / unlock source1
__CFRunLoopSourceUnlock(rls);
__CFRunLoopDebugInfoForRunLoopSource(rls);
// Perform the source1 callback
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(rls->_context.version1.perform,
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
msg, size, reply,
#endif
rls->_context.version1.info);
CHECK_FOR_FORK();
sourceHandled = true;
}
else
{
// Write to log, source1 is invalid
if (_LogCFRunLoop)
{
CFLog(kCFLogLevelDebug, CFSTR("%p (%s) __CFRunLoopDoSource1 rls %p is invalid"), CFRunLoopGetCurrent(), *_CFGetProgname(), rls);
}
// Unlock source1
__CFRunLoopSourceUnlock(rls);
}
/ / release source1
CFRelease(rls);
// Lock runloop
__CFRunLoopLock(rl);
// Lock mode
__CFRunLoopModeLock(rlm);
return sourceHandled;
}
Copy the code
The real callback function is __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__, internally implemented as follows:
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__() __attribute__((noinline));
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
void *(*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info),
mach_msg_header_t *msg, CFIndex size, mach_msg_header_t **reply,
#else
void (*perform)(void *),
#endif
void *info)
{
if (perform)
{
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
*reply = perform(msg, size, kCFAllocatorSystemDefault, info);
#else
perform(info);
#endif
}
asm __volatile__(""); // thwart tail-call optimization
}
Copy the code
__CFRunLoopDoTimers
// rl and rlm are locked on entry and exit
static Boolean __CFRunLoopDoTimers(CFRunLoopRef rl, CFRunLoopModeRef rlm, uint64_t limitTSR)
{ /* DOES CALLOUT */
// Result of initializing whether the timer was successfully processed
Boolean timerHandled = false;
// Initializes an empty timers collection
CFMutableArrayRef timers = NULL;
// Iterate over the mode _ITEMrs timer array
for (CFIndex idx = 0, cnt = rlm->_timers ? CFArrayGetCount(rlm->_timers) : 0; idx < cnt; idx++)
{
/ / remove the timer
CFRunLoopTimerRef rlt = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(rlm->_timers, idx);
// If timer is valid and has not yet been triggered
if(__CFIsValid(rlt) && ! __CFRunLoopTimerIsFiring(rlt)) {// And the timer's trigger time is less than or equal to the specified time
if (rlt->_fireTSR <= limitTSR)
{
// Initializes the timers collection
if(! timers) timers = CFArrayCreateMutable(kCFAllocatorSystemDefault,0, &kCFTypeArrayCallBacks);
// Add timer to timers collectionCFArrayAppendValue(timers, rlt); }}}// Iterate over the timers collection, which is filled with timers that need to work
for (CFIndex idx = 0, cnt = timers ? CFArrayGetCount(timers) : 0; idx < cnt; idx++)
{
/ / remove the timer
CFRunLoopTimerRef rlt = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(timers, idx);
// Perform the timer callback
Boolean did = __CFRunLoopDoTimer(rl, rlm, rlt);
timerHandled = timerHandled || did;
}
// Release the timers collection
if (timers)
CFRelease(timers);
return timerHandled;
}
static Boolean __CFRunLoopDoTimer(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopTimerRef rlt)
{ /* DOES CALLOUT */
// Result of successfully initializing timer
Boolean timerHandled = false;
uint64_t oldFireTSR = 0;
/* Fire a timer */
/ / for the timer to retain
CFRetain(rlt);
// Lock the timer
__CFRunLoopTimerLock(rlt);
// If the timer is valid, the timer trigger time is not reached, the timer is not triggered, and the timer's runloop is the passed runloop, enter the if logic
if(__CFIsValid(rlt) && rlt->_fireTSR <= mach_absolute_time() && ! __CFRunLoopTimerIsFiring(rlt) && rlt->_runLoop == rl) {// Initializes the empty context_info
void *context_info = NULL;
// Initializes the empty context_release function pointer
void (*context_release)(const void *) = NULL;
// If the retain function pointer to the timer context property is not empty, enter the if logic
if (rlt->_context.retain)
{
// Assign info retain to context_info
context_info = (void *)rlt->_context.retain(rlt->_context.info);
// Assign the timer's release pointer to context_release
context_release = rlt->_context.release;
}
else
{
// Retrieve the timer's info
context_info = rlt->_context.info;
}
// Whether the interval of the timer is 0
Boolean doInvalidate = (0.0 == rlt->_interval);
// Set the bits of the timer to 1
__CFRunLoopTimerSetFiring(rlt);
// Just in case the next timer has exactly the same deadlines as this one, we reset these values so that the arm next timer code can correctly find the next timer in the list and arm the underlying timer.
// Just in case a timer has exactly the same expiration date as that timer, we reset these values so that the enable next timer code correctly finds the next timer in the list and enables the base timer.
rlm->_timerSoftDeadline = UINT64_MAX;
rlm->_timerHardDeadline = UINT64_MAX;
/ / unlock the timer
__CFRunLoopTimerUnlock(rlt);
// Trigger the lock on timer
__CFRunLoopTimerFireTSRLock();
/ / remove _fireTSR
oldFireTSR = rlt->_fireTSR;
// Trigger unlock for timer
__CFRunLoopTimerFireTSRUnlock();
// Assemble the next timer
__CFArmNextTimerInMode(rlm, rl);
// Unlock mode
__CFRunLoopModeUnlock(rlm);
// Unlock runloop
__CFRunLoopUnlock(rl);
// Perform the real timer callback
__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(rlt->_callout, rlt, context_info);
CHECK_FOR_FORK();
// If the timer runs only once
if (doInvalidate)
{
// Make timer invalid
CFRunLoopTimerInvalidate(rlt); /* DOES CALLOUT */
}
// If the context_release pointer is not null, context_info is released
if (context_release)
{
context_release(context_info);
}
// Lock runloop
__CFRunLoopLock(rl);
// Unlock mode
__CFRunLoopModeLock(rlm);
// Lock the timer
__CFRunLoopTimerLock(rlt);
// Set the result of processing timer to true
timerHandled = true;
// Set the bits of the timer to 0
__CFRunLoopTimerUnsetFiring(rlt);
}
// If the timer is valid and the timer is processed successfully
if (__CFIsValid(rlt) && timerHandled)
{
/* This is just a little bit tricky: we want to support calling * CFRunLoopTimerSetNextFireDate() from within the callout and * honor that new time here if it is a later date, otherwise * it is completely ignored. */
// If the old trigger time is less than the current trigger time
if (oldFireTSR < rlt->_fireTSR)
{
/* Next fire TSR was set, and set to a date after the previous * fire date, so we honor it. */
__CFRunLoopTimerUnlock(rlt);
// The timer was adjusted and repositioned, during the
// callout, but if it was still the min timer, it was
// skipped because it was firing. Need to redo the
// min timer calculation in case rlt should now be that
// timer instead of whatever was chosen.
__CFArmNextTimerInMode(rlm, rl);
}
else
{
uint64_t nextFireTSR = 0LL;
uint64_t intervalTSR = 0LL;
if (rlt->_interval <= 0.0) {}else if (TIMER_INTERVAL_LIMIT < rlt->_interval)
{
intervalTSR = __CFTimeIntervalToTSR(TIMER_INTERVAL_LIMIT);
}
else
{
intervalTSR = __CFTimeIntervalToTSR(rlt->_interval);
}
if (LLONG_MAX - intervalTSR <= oldFireTSR)
{
nextFireTSR = LLONG_MAX;
}
else
{
if (intervalTSR == 0)
{
// 15304159: Make sure we don't accidentally loop forever here
CRSetCrashLogMessage("A CFRunLoopTimer with an interval of 0 is set to repeat");
HALT;
}
uint64_t currentTSR = mach_absolute_time();
nextFireTSR = oldFireTSR;
while (nextFireTSR <= currentTSR)
{
nextFireTSR += intervalTSR;
}
}
CFRunLoopRef rlt_rl = rlt->_runLoop;
if (rlt_rl)
{
CFRetain(rlt_rl);
CFIndex cnt = CFSetGetCount(rlt->_rlModes);
STACK_BUFFER_DECL(CFTypeRef, modes, cnt);
CFSetGetValues(rlt->_rlModes, (const void **)modes);
// To avoid A->B, B->A lock ordering issues when coming up
// towards the run loop from a source, the timer has to be
// unlocked, which means we have to protect from object
// invalidation, although that's somewhat expensive.
for (CFIndex idx = 0; idx < cnt; idx++)
{
CFRetain(modes[idx]);
}
__CFRunLoopTimerUnlock(rlt);
for (CFIndex idx = 0; idx < cnt; idx++)
{
CFStringRef name = (CFStringRef)modes[idx];
modes[idx] = (CFTypeRef)__CFRunLoopFindMode(rlt_rl, name, false);
CFRelease(name);
}
__CFRunLoopTimerFireTSRLock();
rlt->_fireTSR = nextFireTSR;
rlt->_nextFireDate = CFAbsoluteTimeGetCurrent() + __CFTimeIntervalUntilTSR(nextFireTSR);
for (CFIndex idx = 0; idx < cnt; idx++)
{
CFRunLoopModeRef rlm = (CFRunLoopModeRef)modes[idx];
if (rlm)
{
__CFRepositionTimerInMode(rlm, rlt, true);
}
}
__CFRunLoopTimerFireTSRUnlock();
for (CFIndex idx = 0; idx < cnt; idx++)
{
__CFRunLoopModeUnlock((CFRunLoopModeRef)modes[idx]);
}
CFRelease(rlt_rl);
}
else{ __CFRunLoopTimerUnlock(rlt); __CFRunLoopTimerFireTSRLock(); rlt->_fireTSR = nextFireTSR; rlt->_nextFireDate = CFAbsoluteTimeGetCurrent() + __CFTimeIntervalUntilTSR(nextFireTSR); __CFRunLoopTimerFireTSRUnlock(); }}}else
{
/ / unlock the timer
__CFRunLoopTimerUnlock(rlt);
}
// Release timer
CFRelease(rlt);
return timerHandled;
}
Copy the code
The real callback is the CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION function, which has the following internal implementation:
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__() __attribute__((noinline));
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(CFRunLoopTimerCallBack func, CFRunLoopTimerRef timer, void *info)
{
if (func)
{
func(timer, info);
}
asm __volatile__(""); // thwart tail-call optimization
}
Copy the code
CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE
static void __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__() __attribute__((noinline));
static void __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(void *msg)
{
_dispatch_main_queue_callback_4CF(msg);
asm __volatile__(""); // thwart tail-call optimization
}
Copy the code
conclusion
The resources
Understand Runloop-ibireme in depth
Decrypt runloop-mrpeak
The Core Foundation,