The paper
Runloop is an event processing loop that is part of the thread-dependent infrastructure. It is used to schedule work and coordinate the receipt of incoming events.
Runloop is a do.. A while loop, unlike a normal loop, keeps the thread busy while there is work and puts it to sleep when there is no work.
Runloop role:
-
Keep the program running continuously;
-
Handles various events in the App (touches, timers, performSelector);
-
Save CPU resources, do what you need to do, and rest when you need to rest.
Runloop
cycle
Runloop is encapsulated as CFRunloop at the bottom level. The code is as follows:
void CFRunLoopRun(void) { /* DOES CALLOUT */ int32_t result; Do {// 1.0e10: Scientific count 1*10^10 result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false); CHECK_FOR_FORK(); } while (kCFRunLoopRunStopped ! = result && kCFRunLoopRunFinished ! = result); }Copy the code
Relationship to threads
There are two ways to get runloops:
CFRunLoopRef mainRunloop = CFRunLoopGetMain(); // CFRunLoopRef currentRunloop = CFRunLoopGetCurrent();Copy the code
Main running cycle
Enter CFRunLoopGetMain:
CFRunLoopRef CFRunLoopGetMain(void) { CHECK_FOR_FORK(); static CFRunLoopRef __main = NULL; // no retain needed if (! __main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed return __main; }Copy the code
- call
_CFRunLoopGet0
Function to find by the main thread.
Current running cycle
Enter the CFRunLoopGetCurrent function:
CFRunLoopRef CFRunLoopGetCurrent(void) { CHECK_FOR_FORK(); CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop); if (rl) return rl; Return _CFRunLoopGet0(pthread_self()); }Copy the code
-
Search in TSD is preferred.
-
Call the _CFRunLoopGet0 function to look through the current thread.
_CFRunLoopGet0
Enter the _CFRunLoopGet0 function:
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {if (pthread_equal(t, kNilPthreadT)) { The default main thread t = pthread_main_thread_NP (); } __CFSpinLock(&loopsLock); if (! __CFRunLoops) { __CFSpinUnlock(&loopsLock); // Create a global dictionary, Marked as kCFAllocatorSystemDefault CFMutableDictionaryRef dict = CFDictionaryCreateMutable (kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks); CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np()); // dict : Key value // Use dict for key-value binding, CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_NP ()), mainLoop); if (! OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) { CFRelease(dict); } CFRelease(mainLoop); __CFSpinLock(&loopsLock); } // Get Runloop CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t)); __CFSpinUnlock(&loopsLock); if (! Loop) {// If not, create a Runloop CFRunLoopRef newLoop = __CFRunLoopCreate(t); __CFSpinLock(&loopsLock); loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t)); if (! Loop) {CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop); loop = newLoop; } // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it __CFSpinUnlock(&loopsLock); CFRelease(newLoop); } if (pthread_equal(t, pthread_self())) { _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL); if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) { _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop); } } return loop; }Copy the code
-
Runloops correspond to threads one by one;
-
The RunLoop object is created when the RunLoop is first fetched, and destroyed at the end of the thread.
-
The main thread RunLoop object is automatically created by the system, while the child thread RunLoop object must be actively created by the developer.
-
RunLoop is not thread-safe;
-
The current thread cannot manipulate other threads’ RunLoop objects internally.
structure
Enter the __CFRunLoopCreate function:
static CFRunLoopRef __CFRunLoopCreate(pthread_t t) { CFRunLoopRef loop = NULL; CFRunLoopModeRef rlm; uint32_t size = sizeof(struct __CFRunLoop) - sizeof(CFRuntimeBase); loop = (CFRunLoopRef)_CFRuntimeCreateInstance(kCFAllocatorSystemDefault, __kCFRunLoopTypeID, size, NULL); if (NULL == loop) { return NULL; } (void)__CFRunLoopPushPerRunData(loop); __CFRunLoopLockInit(&loop->_lock); loop->_wakeUpPort = __CFPortAllocate(); if (CFPORT_NULL == loop->_wakeUpPort) HALT; __CFRunLoopSetIgnoreWakeUps(loop); loop->_commonModes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks); CFSetAddValue(loop->_commonModes, kCFRunLoopDefaultMode); loop->_commonModeItems = NULL; loop->_currentMode = NULL; loop->_modes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks); loop->_blocks_head = NULL; loop->_blocks_tail = NULL; loop->_counterpart = NULL; loop->_pthread = t; #if DEPLOYMENT_TARGET_WINDOWS loop->_winthread = GetCurrentThreadId(); #else loop->_winthread = 0; #endif rlm = __CFRunLoopFindMode(loop, kCFRunLoopDefaultMode, true); if (NULL ! = rlm) __CFRunLoopModeUnlock(rlm); return loop; }Copy the code
- create
RunLoop
To return toCFRunLoopRef
Type.
Find the definition of CFRunLoopRef:
typedef struct __CFRunLoop * CFRunLoopRef;
Copy the code
RunLoop
It’s also an object, essentially__CFRunLoop
Pointer to the structure type.
Find the definition of the __CFRunLoop structure:
struct __CFRunLoop {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* locked for accessing mode list */
__CFPort _wakeUpPort; // used for CFRunLoopWakeUp
Boolean _unused;
volatile _per_run_data *_perRunData; // reset for runs of the run loop
pthread_t _pthread;
uint32_t _winthread;
CFMutableSetRef _commonModes;
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;
struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;
CFTypeRef _counterpart;
};
Copy the code
_commonModes
and_commonModeItems
Both are collection types that represent theRunloop
There may be more than one.
Modes
Get all Modes under Runloop:
CFRunLoopRef lp = CFRunLoopGetCurrent();
CFArrayRef modeArray= CFRunLoopCopyAllModes(lp);
NSLog(@"modeArray == %@",modeArray);
Copy the code
Print the output:
modeArray == (
UITrackingRunLoopMode,
GSEventReceiveRunLoopMode,
kCFRunLoopDefaultMode
)
Copy the code
- a
RunLoop
Corresponding to multipleModes
.
Create an NSTimer, add it to the Runloop and run:
NSTimer *timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"fire in home -- %@",[[NSRunLoop currentRunLoop] currentMode]);
}];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
Copy the code
- Added to the
RunLoop
, must be specifiedMode
To prove thatTimer
The operation of theRunLoop
theMode
.
Then get the Mode in which the current Runloop is running again:
CFRunLoopRef lp = CFRunLoopGetCurrent();
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(lp);
NSLog(@"mode == %@",mode);
Copy the code
Print the output:
mode == kCFRunLoopDefaultMode
Copy the code
Mode type:
-
NSDefaultRunLoopMode: default Mode, normally in this Mode;
-
NSConnectionReplyMode: This mode is used in conjunction with NSConnection objects to monitor replies;
-
NSModalPanelRunLoopMode: Use this mode to identify events for the modal panel;
-
NSEventTrackingRunLoopMode: use this Mode to track events from the user interaction, such as: UITableView slide up and down;
-
NSRunLoopCommonModes: Pseudo-modes. This collection includes default, modal, and event tracking modes by default.
In official documents, there are five modes mentioned above. In iOS, only NSDefaultRunLoopMode and NSRunLoopCommonModes are exposed.
And NSRunLoopCommonModes is pseudo Mode, nature is the Mode of collection, containing NSDefaultRunLoopMode, NSModalPanelRunLoopMode, NSEventTrackingRunLoopMode.
Mode is mainly used to specify the priority of events in RunLoop.
Items
In addition to timers, we can also specify other transactions that are executed under CFRunLoopDefaultMode, all with one Mode for multiple Items.
Source, Timer, and Observer are collectively referred to as Item.
Runloop can handle the following transaction types:
Block
Application:__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__
;
As shown in figure:
- call
timer
:__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
;
As shown in figure:
- The response
source0
:__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
;
As shown in figure:
-
Response source1: __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__;
-
GCD main queue: __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__;
As shown in figure:
observer
Source:__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__
.
Such as code:
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(gotNotification:) name:@"helloMyNotification" object:nil];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[[NSNotificationCenter defaultCenter] postNotificationName:@"helloMyNotification" object:@"cooci"];
}
- (void)gotNotification:(NSNotification *)noti{
// __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__
NSLog(@"gotNotification = %@",noti);
}
Copy the code
Source
Source represents events that wake up the RunLoop. For example, when the user clicks on the screen, a RunLoop is created, which is divided into Source0 and Source1.
-
Source0: indicates non-system events, that is, user-defined events.
-
Source1: indicates the system event. It is responsible for the underlying communication and has the wake up capability.
Timer
NSTimer timer is commonly used.
Observer
The Observer is used to monitor and respond to changes in the status of RunLoop.
The Observer is defined as:
/* Run Loop Observer Activities */ typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {// Enter kCFRunLoopEntry = (1UL << 0), // About to handle Timers kCFRunLoopBeforeTimers = (1UL << 1), // Sources kCFRunLoopBeforeSources = (1UL << 2), // Enter sleep kCFRunLoopBeforeWaiting = (1UL << 5), // Wake up kCFRunLoopAfterWaiting = (1UL << 6), // Exit kCFRunLoopExit = (1UL << 7), kCFRunLoopAllActivities = 0x0FFFFFFFU};Copy the code
Runloop
chart
Thread and RunLoop one to one:
A RunLoop corresponds to multiple Modes and a Mode to multiple Items.
Source, Timer, and Observer are collectively referred to as Item.
Transaction processing
Timer
The sample
Create CFRunLoopTimer, add it to Runloop and execute:
- (void)cfTimerDemo { CFRunLoopTimerContext context = { 0, ((__bridge void *)self), NULL, NULL, NULL }; CFRunLoopRef rlp = CFRunLoopGetCurrent(); /** Parameter 1: memory used to allocate the object parameter 2: at what time is triggered (distance now) parameter 3: at what time is triggered parameter 4: future parameter parameter 5 :CFRunLoopObserver priority When there are multiple CFRunloopObservers in the same Runloop phase, I would normally use 0. Parameter 6: callback, such as triggering events. Parameter 7: Context record information */ CFRunLoopTimerRef timerRef = CFRunLoopTimerCreate(kCFAllocatorDefault, 0, 1, 0, 0, lgRunLoopTimerCallBack, &context); CFRunLoopAddTimer(rlp, timerRef, kCFRunLoopDefaultMode); } void lgRunLoopTimerCallBack(CFRunLoopTimerRef timer, void *info){ NSLog(@"%@---%@",timer,info); }Copy the code
Enter the CFRunLoopAddTimer function:
void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) { CHECK_FOR_FORK(); if (__CFRunLoopIsDeallocating(rl)) return; if (! __CFIsValid(rlt) || (NULL ! = rlt->_runLoop && rlt->_runLoop ! = rl)) return; __CFRunLoopLock(rl); If (modeName == kCFRunLoopCommonModes) {CFSetRef set = rl->_commonModes? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL; If (NULL == rl->_commonModeItems) {rl->_commonModeItems = rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks); } // Add the CFRunLoopTimer parameter to Items CFSetAddValue(rl->_commonModeItems, RLT); // Set is not NULL if (NULL! = set) { CFTypeRef context[2] = {rl, rlt}; /* Add new item to all common-modes */ / set the callback function, Added to the common - modes CFSetApplyFunction (set, (__CFRunLoopAddItemToCommonModes), (void *) context). CFRelease(set); }} else {// Address by name Mode CFRunLoopModeRef RLM = __CFRunLoopFindMode(rl, modeName, true); if (NULL ! = rlm) { if (NULL == rlm->_timers) { CFArrayCallBacks cb = kCFTypeArrayCallBacks; cb.equal = NULL; rlm->_timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &cb); } } if (NULL ! = rlm && ! CFSetContainsValue(rlt->_rlModes, rlm->_name)) { __CFRunLoopTimerLock(rlt); if (NULL == rlt->_runLoop) { rlt->_runLoop = rl; } else if (rl ! = rlt->_runLoop) { __CFRunLoopTimerUnlock(rlt); __CFRunLoopModeUnlock(rlm); __CFRunLoopUnlock(rl); return; } // If a match is found, add Runloop to it, depending on Runloop run CFSetAddValue(RLT ->_rlModes, RLM ->_name); __CFRunLoopTimerUnlock(rlt); __CFRunLoopTimerFireTSRLock(); __CFRepositionTimerInMode(rlm, rlt, false); __CFRunLoopTimerFireTSRUnlock(); if (! _CFExecutableLinkedOnOrAfter(CFSystemVersionLion)) { // Normally we don't do this on behalf of clients, but for // backwards compatibility due to the change in timer handling... if (rl ! = CFRunLoopGetCurrent()) CFRunLoopWakeUp(rl); } } if (NULL ! = rlm) { __CFRunLoopModeUnlock(rlm); } } __CFRunLoopUnlock(rl); }Copy the code
CFRunLoopAddTimer = CFRunLoopAddTimer = CFRunLoopAddTimer
- So, the execution of a real transaction depends on
runloop run
.
__CFRunLoopRun
Enter the CFRunLoopRun function:
void CFRunLoopRun(void) { /* DOES CALLOUT */ int32_t result; Do {result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false); CHECK_FOR_FORK(); } while (kCFRunLoopRunStopped ! = result && kCFRunLoopRunFinished ! = result); }Copy the code
-
The CFRunLoopRunSpecific function is used to obtain the result result.
-
1.0E10: Scientific notation, 1 * 10 ^ 10.
Enter the CFRunLoopRunSpecific function:
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */ CHECK_FOR_FORK(); if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished; __CFRunLoopLock(rl); CurrentMode = __CFRunLoopFindMode(rl, modeName, false); / / if you don't find | | mode not registered in any event, the end, Not to circulate the if (NULL = = currentMode | | __CFRunLoopModeIsEmpty (rl, currentMode, rl - > _currentMode)) {Boolean did = false; if (currentMode) __CFRunLoopModeUnlock(currentMode); __CFRunLoopUnlock(rl); return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished; } volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl); CFRunLoopModeRef previousMode = rl->_currentMode; Rl ->_currentMode = currentMode; Int32_t result = kCFRunLoopRunFinished; if (currentMode->_observerMask & kCFRunLoopEntry ) /// 1. Notifying Observers: RunLoop is about to enter loop. __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry); result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode); if (currentMode->_observerMask & kCFRunLoopExit ) /// 10. Notify Observers: RunLoop is about to exit. __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit); __CFRunLoopModeUnlock(currentMode); __CFRunLoopPopPerRunData(rl, previousPerRun); rl->_currentMode = previousMode; __CFRunLoopUnlock(rl); return result; }Copy the code
- Between entering and exiting, call
__CFRunLoopRun
Function.
Enter the __CFRunLoopRun function:
- Each type has a specific handler function call.
__CFRunLoopDoTimer
Enter the __CFRunLoopDoTimers function:
static Boolean __CFRunLoopDoTimers(CFRunLoopRef rl, CFRunLoopModeRef rlm, uint64_t limitTSR) { /* DOES CALLOUT */ Boolean timerHandled = false; CFMutableArrayRef timers = NULL; For (CFIndex idx = 0, CNT = RLM ->_timers? CFArrayGetCount(rlm->_timers) : 0; idx < cnt; CFRunLoopTimerRef RLT = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(RLM ->_timers, IDx); if (__CFIsValid(rlt) && ! __CFRunLoopTimerIsFiring(rlt)) { if (rlt->_fireTSR <= limitTSR) { if (! timers) timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks); CFArrayAppendValue(timers, rlt); } } } for (CFIndex idx = 0, cnt = timers ? CFArrayGetCount(timers) : 0; idx < cnt; idx++) { CFRunLoopTimerRef rlt = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(timers, idx); Boolean did = __CFRunLoopDoTimer(rl, RLM, RLT); timerHandled = timerHandled || did; } if (timers) CFRelease(timers); return timerHandled; }Copy the code
Enter the __CFRunLoopDoTimer function:
timer
The callback.
Processing flow
-
Adds timer to the specified Mode by CFRunLoopAddTimer.
-
Transaction execution depends on runloop run:
- call
CFRunLoopRun
–CFRunLoopRunSpecific
Function,Runloop
Between entering and leaving, call__CFRunLoopRun
Function.
- call
-
Observers, Source0, Source1, and Timer are included in __CFRunLoopRun:
-
For timer processing, __CFRunLoopDoTimers is called to iterate over the currently running timers;
-
For a single timer, the __CFRunLoopDoTimer function is called to perform transaction processing.
-
Runloop transaction flow diagram:
The underlying principle
Core code in CFRunLoopRunSpecific:
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */ CHECK_FOR_FORK(); if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished; __CFRunLoopLock(rl); CurrentMode = __CFRunLoopFindMode(rl, modeName, false); // Notify Observers: RunLoop is about to enter loop. __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry); / / internal function, into the loop result = __CFRunLoopRun (rl, currentMode, seconds, returnAfterSourceHandled, previousMode); // Notify Observers: RunLoop is about to exit. __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit); return result; }Copy the code
Core code in __CFRunLoopRun:
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) { int32_t retVal = 0; Do {// itmes do // notify Observers: about to handle the timer event __CFRunLoopDoObservers(RL, RLM, kCFRunLoopBeforeTimers); // Notify Observers: about to handle the Source event __CFRunLoopDoObservers(rl, RLM, kCFRunLoopBeforeSources); // Handle Blocks __CFRunLoopDoBlocks(rl, RLM); Sources0 Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, RLM, stopAfterHandle); If (sourceHandledThisLoop) {// Process Blocks __CFRunLoopDoBlocks(rl, RLM); If (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {// Handle message goto handle_msg; } // Notify Observers: about to enter hibernation __CFRunLoopDoObservers(rl, RLM, kCFRunLoopBeforeWaiting); __CFRunLoopSetSleeping(rl); __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy); // user callouts now OK again __CFRunLoopUnsetSleeping(rl); // notify Observers: astounded, ending dormancy __CFRunLoopDoObservers(rl, RLM, kCFRunLoopAfterWaiting); Handle_msg: if (awakened by Timer) {// Handle Timers __CFRunLoopDoTimers(rl, RLM, mach_absolute_time()); } else if (GCD wake up) {/// handle GCD __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(MSG); } else if (by Source1) {/// by Source1, Source1 __CFRunLoopDoSource1(rl, RLM, RLS, MSG, MSG ->msgh_size, &reply)} // Handle block __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; } } while (0 == retVal); return retVal; }Copy the code
Flow chart: