Runloop profile
A Runloop is simply a waiting loop in which an event wakes up the thread to perform related event processing, while no event wakes up the thread to sleep. The events it receives include two types:
- input sources
- Port System definition event
- Custom User-defined events
- So perform is a method that you’re familiar with
- timer souces
The Runloop create
Apple provides two interfaces, CFRunLoopGetMain() and CFRunLoopGetCurrent(), to create or get a thread-bound runloop. Source code we refer to Apple open source Swift cross-platform implementation, we focus on the analysis of CFRunLoopGetCurrent:
CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
return _CFRunLoopGet0(pthread_self());
}
Copy the code
There are two main steps from the source code:
- Introduced to the key
__CFTSDKeyRunLoop
Through the_CFGetTSD
Gets a CFRunLoopRef, if any - If step 1 is used
_CFRunLoopGet0
Get CFRunLoopRef
What is _CFGetTSD? Guess is to get some cache reference. Find the source code confirmation:
CF_EXPORT void *_CFGetTSD(uint32_t slot) {
return _CFGetTSDCreateIfNeeded(slot, true);
}
CF_EXPORT void *_CFGetTSDCreateIfNeeded(const uint32_t slot, const Boolean create) CF_RETURNS_NOT_RETAINED {
...
void * result = NULL;
__CFTSDTable *table = __CFTSDGetTable(create); //code below
if (table) {
uintptr_t *slots = (uintptr_t *)(table->data);
result = (void *)slots[slot];
}
...;
return result;
}
_CFSTDTable *__CFTSDGetTable(constBoolean create){ __CFSTDTable *table = __CFTSDGetSpecific(); . ; __CFSDSetSpecific(table); }void *__CFTSDGetSpecific(){
return pthread_getspecific(__CFTSDIndexKey);
}
Copy the code
Confirm that _CFGetTSD is actually retrieving __CFTSDTable via pthread_getSpecific thread-local storage TLS (not shared by multiple threads but only accessed by the current thread), The CFRunLoopRef is then retrieved from the table via key__CFTSDKeyRunLoop.
If _CFGetTSD cannot get a runloop from the cache, proceed with _CFRunLoopGet0.
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(_CFThreadRef t) {
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
__CFLock(&loopsLock);
if(! __CFRunLoops) { CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault,0.NULL, &kCFTypeDictionaryValueCallBacks);
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
if(! OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
CFRelease(mainLoop);
}
CFRunLoopRef newLoop = NULL;
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
if(! loop) { newLoop = __CFRunLoopCreate(t); CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop); loop = newLoop; } __CFUnlock(&loopsLock);// don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
if (newLoop) { CFRelease(newLoop); }
if (pthread_equal(t, pthread_self())) {
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
#if _POSIX_THREADS
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS- 1), (void(*) (void *))__CFFinalizeRunLoop);
#else
_CFSetTSD(__CFTSDKeyRunLoopCntr, 0, &__CFFinalizeRunLoop);
#endif}}return loop;
}
Copy the code
If _CFRunLoopGet0 is not passed, mainRunLoop is created.
- Get the runloop from the global container map cache, where key is the thread pointer
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
- If not, create a new runloop
newloop = __CFRunLoopCreate(t)
- Use Settings to cache
CFDictionarySetValue
(global) +_CFSetTSD
(TLS)
The question here is, why do you store two copies of dictionary and _CFSetTSD?
Runloop state/Mode
Runloop state
You can use the Observer to specify function callbacks for state changes
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0),
kCFRunLoopBeforeTimers = (1UL << 1), // Before processing Timer
kCFRunLoopBeforeSources = (1UL << 2), // before processing Source
kCFRunLoopBeforeWaiting = (1UL << 5), // About to sleep
kCFRunLoopAfterWaiting = (1UL << 6), // Just woke up from hibernation
kCFRunLoopExit = (1UL << 7), / / out of the Loop
};
Copy the code
RunloopMode
struct __CFRunLoopMode {. ; CFStringRef _name; 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__CFPort _timerPort; Boolean _mkTimerArmed; . ; };Copy the code
Each RunloopMode can contain the name name, Sources timers event; There are two kinds of timer in Swift CFRunloop open source implementation, one is based on Diapatch and the other is based on CFPort.
struct __CFRunLoop {. ; __CFPort _wakeUpPort;// used for CFRunLoopWakeUp_CFThreadRef _pthread; CFMutableSetRef _commonModes; CFMutableSetRef _commonModeItems; CFRunLoopModeRef _currentMode; . ; };Copy the code
As you can see from the code structure, Runloop contains multiple Runloopmodes, each of which can contain different source/timer events. So we can add the defined source/port etc to the system preset or mode we defined.
CFRunloop Run
The internal logic of RunLoop
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);
}
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
CHECK_FOR_FORK();
if (modeName == NULL || modeName == kCFRunLoopCommonModes || CFEqual(modeName, kCFRunLoopCommonModes)) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
CFLog(kCFLogLevelError, CFSTR("invalid mode '%@' provided to CFRunLoopRunSpecific - break on _CFRunLoopError_RunCalledWithInvalidMode to debug. This message will only appear once per execution."), modeName);
_CFRunLoopError_RunCalledWithInvalidMode();
});
return kCFRunLoopRunFinished;
}
if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
__CFRunLoopLock(rl);
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {/ / 1
if (currentMode) { __CFRunLoopModeUnlock(currentMode); }
__CFRunLoopUnlock(rl);
return kCFRunLoopRunFinished;
}
volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
CFRunLoopModeRef previousMode = rl->_currentMode;
rl->_currentMode = currentMode;
int32_t result = kCFRunLoopRunFinished;
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);/ / 2
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled,previousMode); / / 3
if (currentMode->_observerMask & kCFRunLoopExit ){
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
}
...;
return result;
}
Copy the code
CFRunLoopRun calls CFRunLoopRunInMode and eventually CFRunLoopRunSpecific. The logical implementation is within CFRunLoopRunSpecific.
-
__CFRunLoopFindMode(rl, modeName, false), currentMode (rl, modeName, false), At this point the runloop exits. This explains why afNetwork runloop starts with an empty port: to avoid runloop exit.
-
The notification goes to runloop, kCFRunLoopEntry
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry)
-
Execute __CFRunLoopRun, the underlined internal implementation with the following code and main logic
- Notice the Observer:
kCFRunLoopBeforeTimers
.kCFRunLoopBeforeSources
- Notify source0 and process source0
__CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
- Notification of impending hibernation
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
- Sleep, waiting for the event port message to wake up
__CFRunLoopServiceMachPort
callmach_msg(msg, MACH_RCV_MSG, port);
// thread wait for receive msg} - Notifies the thread that it has been awakened afterwaiting
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting);
- If timer wakes up, process timer
CFRUNLOOP_WAKEUP_FOR_TIMER
- If it is a dispatch wake up, process
DISPATCH
- If the source1 event wakes up, process
Source1
- Above the loop
- Notice the Observer:
static int32_t__CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) { ... ; __CFRunLoopUnsetIgnoreWakeUps(rl);//1. Notify kCFRunLoopBeforeTimers && kCFRunLoopBeforeSources
if (rlm->_observerMask & kCFRunLoopBeforeTimers) {
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
}
if (rlm->_observerMask & kCFRunLoopBeforeSources) {
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
}
// Execute the added BLOCK
__CFRunLoopDoBlocks(rl, rlm);
/ / 2. Handle Sources0Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle); .//3. Notify kCFRunLoopBeforeWaiting that sleep is imminent
if(! poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);4.dormancy__CFRunLoopServiceMachPort mach_msg(a)
__CFRunLoopSetSleeping(rl);
__CFPortSetInsert(dispatchPort, waitSet);
CFAbsoluteTime sleepStart = poll ? 0.0: CFAbsoluteTimeGetCurrent(); . msg = (mach_msg_header_t *)msg_buffer;
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0: TIMEOUT_INFINITY, &voucherState, &voucherCopy, rl, rlm); . ; __CFRunLoopSetIgnoreWakeUps(rl);// user callouts now OK again
__CFRunLoopUnsetSleeping(rl);
// 6. Notify afterwaiting
if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
handle_msg:;
__CFRunLoopSetIgnoreWakeUps(rl);
...;
else if(rlm->_timerPort ! = MACH_PORT_NULL && livePort == rlm->_timerPort) { CFRUNLOOP_WAKEUP_FOR_TIMER();if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
rlm->_timerSoftDeadline = UINT64_MAX;
rlm->_timerHardDeadline = UINT64_MAX;
__CFArmNextTimerInMode(rlm, rl);
}
}
...;
/* --- DISPATCHES --- */
#if __HAS_DISPATCH__
else if (livePort == dispatchPort) {
CFRUNLOOP_WAKEUP_FOR_DISPATCH();
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6.NULL);
CFRUNLOOP_ARP_BEGIN;
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
CFRUNLOOP_ARP_END;
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0.NULL);
sourceHandledThisLoop = true;
didDispatchPortLastTime = true;
}
#endif
/* --- SOURCE1S --- */
else {
CFRUNLOOP_WAKEUP_FOR_SOURCE();
CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
if (rls) {
mach_msg_header_t *reply = NULL;
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
if (NULL! = reply) {// switch mach_msg to kernel mode and wait for the event to occur
(void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL); CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply); }}/* --- BLOCKS --- */
if(msg && msg ! = (mach_msg_header_t *)msg_buffer) free(msg);
// Execute the block added to the runloop__CFRunLoopDoBlocks(rl, rlm); retVal = .... ; }while (0== retVal); . ;return retVal;
}
static Boolean __CFRunLoopServiceMachPort(mach_port_name_t port, mach_msg_header_t **buffer, size_t buffer_size, mach_port_t *livePort, mach_msg_timeout_t timeout, voucher_mach_msg_state_t * _Nonnull voucherState, voucher_t *voucherCopy, CFRunLoopRef rl, CFRunLoopModeRef rlm) {
kern_return_t ret = KERN_SUCCESS;
for (;;) { /* In that sleep of death what nightmares may come ... * /. ret = mach_msg(msg, MACH_RCV_MSG|MACH_RCV_VOUCHER|MACH_RCV_LARGE|((TIMEOUT_INFINITY ! = timeout) ? MACH_RCV_TIMEOUT :0)|MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0)|MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AV), 0, msg->msgh_size, port, timeout, MACH_PORT_NULL); . } HALT;return false;
}
Copy the code
supplement
Autoreleasepool and runloop
Corresponding runloop timing:
- Entry:
_objc_autoreleasePoolPush
- Exit:
_objc_autoreleasePoolPop
- BeforeWaiting:
_objc_autoreleasePoolPop
->_objc_autoreleasePoolPush
Runloop and UIView drawing
The system registers a beforeWaiting observer that iterates through the view hierarchy previously changed by setFrame, setNeedsLayout/Display, before going to sleep. Call layer. Layout_if_needed – > layoutsublayers – > the layoutsubviews – > display – drawRect
_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()
QuartzCore:CA::Transaction::observer_callback:
CA::Transaction::commit();
CA::Context::commit_transaction();
CA::Layer::layout_and_display_if_needed();
CA::Layer::layout_if_needed();
[CALayer layoutSublayers];
[UIView layoutSubviews];
CA::Layer::display_if_needed();
[CALayer display];
[UIView drawRect];
Copy the code
timer
NSTimer registers with runloop repeatedly for each callback point. After a certain point in time + tolerance, the next callback will be skipped.
GCD
Dispatch_async (dispatch_get_main_queue(),block), registered to runloop, The block is executed by __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__; Non-main threads are handled by libDispatch.
PerformSelecter
Essentially, the timer is set with runloop and the call is executed when the time is up.
tips
Swift open source CFRunloop has a long source code because it is suitable for various platforms. The core is that runloop’s extremely low CPU usage without events is due to the mach_msg system call in the while loop, which wakes up the thread only when an event occurs, and can handle different types of events at appropriate times, as well as notifying the user via callback functions. My view on source code is that for system-level developers, you need to focus on the underlying details; For those engaged in application development, individuals should have the ability to read the source code, but not too rigidly in the details of the implementation, understanding its ideas and principles of attention to solve the awareness of engineering problems is more important.