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.

Runloopcycle

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_CFRunLoopGet0Function 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
  • createRunLoopTo return toCFRunLoopRefType.

Find the definition of CFRunLoopRef:

typedef struct __CFRunLoop * CFRunLoopRef;
Copy the code
  • RunLoopIt’s also an object, essentially__CFRunLoopPointer 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
  • _commonModesand_commonModeItemsBoth are collection types that represent theRunloopThere 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
  • aRunLoopCorresponding 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 theRunLoop, must be specifiedModeTo prove thatTimerThe operation of theRunLooptheMode.

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:

  • BlockApplication:__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__;

As shown in figure:

  • calltimer:__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__;

As shown in figure:

  • The responsesource0:__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:

  • observerSource:__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

Runloopchart

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

TimerThe 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 onrunloop 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__CFRunLoopRunFunction.

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:

  • timerThe callback.

Processing flow

  • Adds timer to the specified Mode by CFRunLoopAddTimer.

  • Transaction execution depends on runloop run:

    • callCFRunLoopRunCFRunLoopRunSpecificFunction,RunloopBetween entering and leaving, call__CFRunLoopRunFunction.
  • 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: