Old driver production – source analysis of RunLoop

Old driver production - source analysis of RunLoop
RunLoop detailed

Have to say, people’s inertia is really terrible ah.
started writing about runLoop on Saturday, telling himself to start writing this blog on Wednesday, Thursday. But now it’s time stamp. It’s 4.30 Sunday. Finish out and do not know what time you, ha ha ha

Old driver production - source analysis of RunLoop
lazy cancer

What does this talk about? This issue stresses runLoop yo. Since runLoop, this mysterious thing seems to be as the ultimate topic interview to pick people, because it is not difficult to use, daily development where runLoop is less and less, no time to the accumulation of knowledge in this area is still relatively scarce compared to should understand, so runLoop can also be side developers development experience, finally kill certainly has been taken as the selection of talents. You may be angry in the face of the usual said less than I will not affect the development of ah! Yes, it’s not, but it’s just a filter. But the egg pain is that in the domestic environment, the relevant information on the runLoop is very few, it is difficult to develop an in-depth understanding of the developers.

For the above reasons, the old driver today to the old driver’s personal point of view, as far as possible the old driver to understand the runLoop knowledge.

In today’s article you may see the following:

  • RunLoop related knowledge

What is runLoop

Literal translation, run. Event loop. Why does
have this loop? We know that any program will end after the last sentence of the program. However, for the mobile application that we want, he obviously can not run an event after the end of the operation, he should have the ability to continue to accept the event to continue to deal with events. So the basic idea is to use a while cycle so that the program can not go to the end of the last sentence, but in the loop to accept the event. So we need runLoop. But it is worth noting that the concept of runLoop is not unique to iOS, because the runLoop should go is a model, this model also exists in other platforms, but called runLoop I don’t know.

Old driver production - source analysis of RunLoop
cycle

How runLoop is implemented

The first thing to do is clear, usually we use the Foundation framework of the NSRunLoop class to do something, but in fact NSRunLoop is a simple package layer in the CoreFoundation framework based on CFRunLoop. So here we focus on CFRunLoop, after all, we can get the CFRunLoop source.

Composition of 1.runLoop

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;};

We may be able to CFRunLoop is such a structure.
we can see the structure weight is used to ensure that the lock _lock thread safe, have to wake up runLoop port _wakeUpPort (here comes back, without attachment), a thread object _pthread, and a set of _modes model and some other auxiliary attributes.

1.1 _pthread

Here I would like to say is that runLoop and threads are one-to-one correspondence. In other words, a runLoop corresponds to a thread, a thread corresponds to a runLoop. Here we can see from the runLoop constructor and 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-> HALT; _wakeUpPort) __CFRunLoopSetIgnoreWakeUps (loop); loop-> _commonModes = CFSetCreateMutable (kCFAllocatorSystemDefault, 0, & kCFTypeSetCallBacks); CFSetAddValue (loop-> _commonModes, kCFRunLoopDefaultMode); loop-> _co MmonModeItems = NULL; _currentMode = loop-> NULL; loop-> _modes = CFSetCreateMutable (kCFAllocatorSystemDefault, 0, & kCFTypeSetCallBacks); loop-> loop-> _blocks_head = NULL; _blocks_tail = NULL; _counterpart = loop-> NULL; loop-> _pthread = t; #if DEPLOYMENT_TARGET_WINDOWS loop-> _winthread = GetCurrentThreadId (#else); _winthread = loop-> 0; #endif RLM = __CFRunLoopFindMode (loop, kCFRunLoopDefaultMode, true); if (NULL! = RLM) __CFRunLoopModeUnlock (RLM); return loop;}

It can be seen that constructing a runLoop object requires only one pthread_t thread. That is, a runLoop corresponds to a thread.

CF_EXPORT CFRunLoopRef _CFRunLoopGet0 (pthread_t T) {if (pthread_equal (T, kNilPthreadT) {//) if the incoming thread null default from the main thread corresponding to runLoop = pthread_main_thread_np (T);} __CFSpinLock (& loopsLock); if ({//__CFRunLoops! __CFRunLoops) is a global dictionary, the following code as if the global dictionary there is no global dictionary is created, and the main thread corresponding to the mainLoop in the dictionary __CFSpinUnlock (& loopsLock); 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 (dict)) {CFRelease}); CFRelease (mainLoop); __CFSpinLock (& loopsLock);} CFRunLoopRef loop = (CFRunLoopRef) CFDictionaryGetValue (__CFRunLoops, pthreadPointer (T)); / / from the global dictionary runLoop __CFSpinUnlock, remove the corresponding thread (& loopsLock); if (loop!) {// if the corresponding thread runLoop is empty, then create the corresponding multiplication of runLoop and stored in a global dictionary CFRunLoopRef newLoop = __CFRunLoopCreate (T); __CFSpinLock (& loopsLock); loop (CFRunLoopRef = CFDictionaryGetValue (__CFRunLoops, pthreadPointer) (T) (loop; if) {CFDictionarySetValue (__CFRu!) NLoops, 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) (__CFTSDKeyRunLoop, _CFSetTSD) {(void *) loop, NULL (0); if (__CFTSDKeyRunLoopCntr = = _CFGetTSD) {_CFSetTSD (__CFTSDKeyRunLoopCntr), (void *) (PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*) (void * __CFFinalizeRunLoop))}}); return loop;}

This is the runLoop access function, we see the system from a global dictionary to take out the runLoop, key is a thread, which is sufficient to show that runLoop is a one-to-one correspondence with the thread.

It is worth mentioning that, at the beginning of a thread is not corresponding to the runLoop, is in the call to get the function of the time corresponds to a runLoop. Because this is a corresponding relationship between the runLoop class management, rather than thread.

Of course, these two are private API, CF truly exposed only the interface of the two:

CF_EXPORT CFRunLoopRef CFRunLoopGetCurrent (void); CF_EXPORT CFRunLoopRef CFRunLoopGetMain (void);

The realization of the two method is very simple, as long as the corresponding thread into the function can be obtained:

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 CFRunLoopRef CFRunLoopGetCurrent (void);} {(CHECK_FOR_FORK); CFRunLoopRef RL = (CFRunLoopRef) _CFGetTSD (__CFTSDKeyRunLoop); if (RL) return RL; return _CFRunLoopGet0 (pthread_self ());}

1.2 _modes

We see that a runLoop also maintains a collection of _modes. So what does this modes do? It should be said that _modes is the core of runLoop. Keke (knock on the blackboard), draw the focus.

First of all, we look at the _modes in the end what are installed?
answer is __CFRunLoopMode object. So what is he?

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

Here the old driver picked out several points, there are signs to mark runLoopMode _name, there are two event sources set _sources0, _sources1, a group of observers _obeserver, one group is added to the runLoop _timers, and one for the timing of the _timerSource Mode maintains itself, _timerPort. The two one is the GCD clock is a kernel clock.

As for why runLoopMode was so long, the opportunity in the runLoopRun to achieve the following combination of code at.


2.runLoop code implementation

Well, then the code is a bit long, give you a look at the process, and then look at the process code.

Old driver production - source analysis of RunLoop
figure is my stolen

Ahead of high-energy warning, a lot of code!

RunLoop core code

RLM are locked on / RL entrance and / static int32_t exit __CFRunLoopRun (CFRunLoopRef RL, CFRunLoopModeRef RLM, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {uint64_t (startTSR = mach_absolute_time); / / get the current kernel time if (__CFRunLoopIsStopped (RL) {//) if the current runLoop or runLoopMode to stop the state directly if return __CFRunLoopUnsetStopped (RL); return kCFRunLoopRunStopped else;} if (rlm-> _stopped) {rlm-> _stopped = false; return kCFRunLoopRunStopped;} / / to determine whether it is the first time in the main thread to start RunLoop, if it is RunLoop and the current main thread RunLoop, then give a distributed queue scheduling port mach_port_name_t dispatchPort Boolean = MACH_PORT_NULL; libdispatchQSafe = pthread_main_np (&); & ((HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY; & & NULL = previousMode (HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY) ||! & & _CFGetTSD = 0 (__CFTSDKeyIsInGCDMainQ)) if (libdispatchQSafe); & & (CFRunLoopGetMain) (RL = &); & (rl-> _commonModes; CFSetContainsValue rlm-> dispatchPort, _name)) = _dispatch_get_main_queue_port_4CF (#if USE_DISPATCH_SOURCE_FOR_TIMERS); / / to the current mode of dispatch queue port 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 initializes a GCD timer for the current management mode of dispatch_source_t timeout_timer timeout = NULL; struct = *timeout_context (__timeout_context struct __timeout_context malloc (*) sizeof (* timeout_context) if (seconds); < = 0; seconds = timeout) {/ / instant 0; timeout_context-> termTSR = 0ULL;} else (seconds < if = TIMER_INTERVAL_LIMIT) {dispatch_queue_t queue = dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_HIGH, DISPATCH_QUEUE_OVERCOMMIT); timeout_timer = dispatch_source_create (DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); dispatch_retain (timeout_timer); timeout_context-> DS = timeout_timer; Tim Eout_context-> RL = (CFRunLoopRef) CFRetain (RL); timeout_context-> termTSR = startTSR + __CFTimeIntervalToTSR (seconds); dispatch_set_context (timeout_timer, timeout_context); / / source gets ownership of context dispatch_source_set_event_handler_f (timeout_timer, __CFRunLoopTimeout); dispatch_source_set_cancel_handler_f (timeout_timer, __CFRunLoopTimeoutCancel); uint64_t ns_at = (uint64_t) (__CFTSRToTimeInterval ((startTSR) + seconds * 1000000000ULL); dispatch_source_set_timer (timeout_timer), dispatch_time (1, ns_at), DISPATCH_TIME_FOREVER, 1000ULL); dispatch_resume (timeout_timer);} else {/ / infinite timeout seconds = 9999999999; timeout_context-> = UINT6; termTSR 4_MAX;} / / the first step, enter the cycle of Boolean didDispatchPortLastTime int32_t = true; retVal = 0; do {uint8_t msg_buffer[3 * 1024]; #if DEPLOYMENT_TARGET_MACOSX DEPLOYMENT_TARGET_EMBEDDED DEPLOYMENT_TARGET_EMBEDDED_MINI mach_msg_header_t || || *msg = NULL; mach_port_t = livePort MACH_PORT_NULL; #elif DEPLOYMENT_TARGET_WINDOWS HANDLE livePort Boolean = NULL; windowsMessageReceived = false; #endif = _portSet; __CFPortSet waitSet rlm-> / / wake __CFRunLoopUnsetIgnoreWakeUps set the current cycle monitor port (RL); / / the second step, notify the observer ready to start processing Timer source if (rlm-> _observerMask & event; kCFRunLoopBeforeTimers; __CFRunLoop) DoObservers (RL, RLM, kCFRunLoopBeforeTimers); / / the third step, notify the observer ready to start processing Source source if (rlm-> _observerMask; & event; kCFRunLoopBeforeSources) __CFRunLoopDoObservers (RL, RLM, kCFRunLoopBeforeSources); / / implementation submitted to runLoop block __CFRunLoopDoBlocks (RL, RLM); / / the fourth step, implementation of source0 source Boolean sourceHandledThisLoop = __CFRunLoopDoSources0 (RL, RLM, stopAfterHandle); / / if the current source0 source event processing after the completion of execution is submitted to the runLoop in block if (sourceHandledThisLoop) {__CFRunLoopDoBlocks (RL, RLM);} / / mark is waiting to wake up Boolean port poll || (0ULL = sourceHandledThisLoop = = timeou T_context-> termTSR); / / the fifth step test port if the port is incident to jump to the handle_msg (initial implementation will not enter judgment, because the didDispatchPortLastTime true if (MACH_PORT_NULL) = dispatchPort & &!! didDispatchPortLastTime) {#if DEPLOYMENT_TARGET_MACOSX MSG = DEPLOYMENT_TARGET_EMBEDDED_MINI DEPLOYMENT_TARGET_EMBEDDED || || (mach_msg_header_t *) (__CFRunLoopServiceMachPort (msg_buffer; if dispatchPort, & MSG, sizeof (msg_buffer), & livePort, 0)) {goto handle_msg;} #elif DEPLOYMENT_TARGET_WINDOWS if (__CFRunLoopWaitForMultipleObjects (NULL, & dispatchPort, 0, 0, & livePort, NULL)) {goto handle_msg;} # ENDIF} didDispatchPortLastTime = false; / / the sixth step, notify the observer (poll thread enters the dormant if! & (rlm-> _observerMask; & & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers (RL, RLM, kCFRunLoopBeforeWaiting); / / mark current runLoop sleep state __CFRunLoopSetSleeping (RL); / / do not do any user callouts after this point (after notifying of sleeping Must the local-to-this-activation ports) / push in on every loop as this mode could / iteration be run re-entrantly and we don't want these ports to get serviced. / __CFPortSetInsert (dispatchPort, waitSet); __CFRunLoopModeUnlock (RLM); __CFRunLoopUnlock (RL); / / seventh step, enter the cycle began to read port information, if the port information is a wake wake current runLoop #if DEPLOYMENT_TARGET_MACOSX DEPLOYMENT_TARGET_EMBEDDED DEPLOYMENT_TARGET_EMBEDDED_MINI #if USE_DISPATCH_SOURCE_FOR_TIMERS do || || {if (kCFUseCollectableAllocator) {objc_clear_stack (0); memset (msg_buffer, 0, sizeof (msg_buffer));} (MSG = mach_msg_header_t * msg_buffer; __CFRunLoopServiceMachPort) (waitSet, & MSG, sizeof (msg_buffer), & livePort, poll? 0: TIMEOUT_INFINITY); 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); #else if (kCFUseCollectableAllocator) {objc_clear_stack (0); memset (msg_buffer, 0, sizeof (msg_buffer) }); MSG = (mach_msg_header_t *) msg_buffer; __CFRunLoopServiceMachPort (waitSet, & MSG, sizeof (msg_buffer), & livePort, poll? 0: TIMEOUT_INFINITY); #endif #elif DEPLOYMENT_TARGET_WINDOWS use the app-supplied message / Here queue mask. They will set this if they are interested in having this run loop receive windows messages. __CFRunLoopWaitForMultipleObjects (waitSet, NULL, poll? 0: TIMEOUT_INFINITY, rlm-> _msgQMask, & livePort, & windowsMessageReceived); #endif __CFRunLoopLock (RL); __CFRunLoopModeLock (RLM); / / Must remove the local-to-this-activation ports in on every loop as this mode could / iteration be run re-entrantly and we don't These ports to get serviced. / want Also, we don't want them left in there if this function returns. / __CFPortSetRemove (dispatchPort, waitSet); / / mark the current runLoop (RL) __CFRunLoopSetIgnoreWakeUps is awake; / / user callouts now OK again __CFRunLoopUnsetSleeping (RL); / / the eighth step, notice the observer thread is awakened (if poll! & (rlm-> _observerMask; & & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers (RL, RLM, kCFRunLoopAfterWaiting); / / Executive port event handle_msg:; / / runLoop ignore the port set at this time (to ensure thread safety) Wake (RL __CFRunLoopSetIgnoreWakeUps); #if DEPLOYMENT_TARGET_WINDOWS if (windowsMessageRe Ceived These Win32 APIs) {/ / cause a callout, so make sure we're unlocked first and relocked after __CFRunLoopModeUnlock (RLM); __CFRunLoopUnlock (RL); if (rlm-> _msgPump) {rlm-> _msgPump (else);} {MSG MSG; if & MSG (PeekMessage (NULL, 0, 0, PM_REMOVE. | PM_NOYIELD)) {TranslateMessage (& MSG); DispatchMessage (& MSG);}} __CFRunLoopLock (RL); __CFRunLoopModeLock (RLM); sourceHandledThisLoop = true; / / To prevent starvation of sources other than the message queue, we check again to see if any other sources Need to be serviced the mask so / / Use 0 for windows messages are ignored this time. Also use 0 for the timeout, because we're just checking to see if the things are signalled right now will wait on them again we later. Ignore the dispatch source / / NOTE: (it's not in the wait set anymore and also don't run) the observers here since we are polling. __CFRunLoopSetSleeping (RL); __CFRunLoopModeUnlock (RLM); __CFRunLoopUnlock (RL; __CFRunLoopWaitForM)

This method is a bit long, 300 lines of code =. =

The 300 lines of the process is actually the above induction of the 10 steps:

First enter the runLoop corresponding to the Mode and start the cycle, and then do three things before hibernation: DoBlocks, DoSource0, detection of source1 port is a message, if any, then skip the rest later.
then the runLoop enters a dormant state until a port event wakes up runLoop, and then wakes up to respond to the port event and then start the cycle again. Until runLoop timeout or runLoop is stopped at the end of runLoop.

However, the code is very good, here we can go to a lot of problems.

1.source0, source1

First of all, the source event is divided into two kinds, one is not based on the port of source0, has been a port based source1.

Source0 contains only a callback (function pointer), which does not initiate an event. When you use, you need to call CFRunLoopSourceSignal (source), the Source marked to be processed, and then manually call CFRunLoopWakeUp (runloop) to wake up RunLoop, let it handle this event. Source1 contains a mach_port and a callback (function pointer) that is used to send messages to each other through the kernel and other threads. This Source can take the initiative to wake up the RunLoop thread, the principle will be mentioned below. – – from an in-depth understanding of RunLoop


Source0 mainly deal with internal events App and App responsible for their own management (Chu Fa), such as UIEvent, CFSocket source1 are Runloop and Mach port driver, kernel management, such as CFMahPort, CFMessagePort, runLoop from the sun source line sharing video

2.NSTimer event is achieved by means of runLoop.

This old driver early in the CoreAnimation series third introduces three old driver Timer is mentioned, Timer will be submitted to runLoop when initializing Timer, and to specify the mode, can work. Today we can talk about it.

How did this event come about? And why is it sometimes delayed? Why is the Timer created in the child thread not implemented? First of all, after entering the cycle begins, to handle the source0 event processing to detect whether there is a source1 port, if a Timer time interval just to here may get a message, runLoop will jump directly to the port to handle the Timer event to activate. Second, why delay? As we know, the two port event is executed in two runLoop loops. For example, the Timer time interval of 1 second, for the first time in the Timer callback after entering runLoop the next cycle immediately in a very short period of time, this is not the Timer callback and is a very large amount of computation, the computation time of more than 1, then runLoop second cycles will be performed for a long time, waiting for the May the upcoming Timer second callback signal can not enter the next cycle, so Timer second will be postponed callback. Third, why does the Timer created in the child thread and submitted to the current runLoop do not run? This is still from the runLoop function to see, when the call currentRunLoop will take the current thread corresponding to the runLoop, and for the first time is not taken, it will create a new runLoop. But! This runLoop does not have run. Is not open =. =


3 at the same time, runLoop can only run the same kind of mode. How did the commonMode come true?

We can know from the structure of runLoop, a runLoop will contain a variety of runLoopMode, runLoop is constantly switching between these mode to complete the corresponding task in the Mode.

Multiple mode in Old driver production - source analysis of RunLoop
runLoop

First of all, why say that runLoop can only switch between a variety of Mode, at the same time there can be only one?
because the above method must pass a runLoopMode, and then this method throughout, are in use.

We see that in the above method, we must first pass a specified mode to execute the event in the corresponding mode. So the so-called CommonMode is how to achieve it?

We see runLoop in the implementation of the task has been transferred to CFRunLoopDoBlocks such a function, then this function is what kind of?

Static Boolean __CFRunLoopDoBlocks (CFRunLoopRef RL, CFRunLoopModeRef RLM Call with RL and) {/ / RLM locked if (rl-> return! _blocks_head) false; if (RLM ||!! rlm-> return false; _name)... Omitting some non key... While (item) {struct _block_item *curr = item; item = item-> _next; Boolean doit = false; if (CFStringGetTypeID (CFGetTypeID) = (curr-> _mode)) {doit = CFEqual (curr-> _mode, curMode (CFEqual) || (curr-> _mode, kCFRunLoopCommonModes) & & CFSetContainsValue (commonModes, curMode));} else {doit = CFSetContainsValue (_mode (CFSetRef) curr-> || (CFSetContainsValue), curMode ((CFSetRef) curr-> _mode, kCFRunLoopCommonModes) & (CFSetContainsValue; & CommonModes, curMode));} if (doit!) prev = curr; if (doit) {if (prev) prev-> _next = item; if (curr = = head) head = item; if (curr = = tail) tail = prev; void (^block) (void) = curr-> _block; CFRelease (curr-> _mode); free (curr); if (doit) {__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ (block); did = true;}... Omit some non key... Return did;}

We see that the bool doit variable determines whether the current block is executed. By default, he is No, he is set to the condition of true is CFEqual (curr-> _mode, curMode (CFEqual) || (curr-> _mode, kCFRunLoopCommonModes) & & CFSetContainsValue (commonModes, curMode)). Is the current mode and set equal to mode or the current mode to commonMode (here a string) and commonMode (here is a set, if you do not understand, please see the runLoop structure) in this set contains the specified mode.

This is because the existence of this judgment allows the commondMode can be executed at any Mode.
of course this is submitted to the runLoop code block will go to __CFRunLoopDoBlocks this method.

Similarly, we can know through the above code, runLoop port wake up events need to pass CFRunLoopDoSource1 and CFRunLoopDoTimers two methods to call. __CFRunLoopDoSource1 method is nothing to say, call the source event runLoopSourceRef. We focus on the realization of Timer, the core code is as follows:

Static Boolean __CFRunLoopDoTimers (CFRunLoopRef RL, CFRunLoopModeRef RLM, uint64_t limitTSR) {/ * DOES CALLOUT * / Boolean timerHandled = false CFMutableArrayRef; timers = NULL; / / Timers array traversal runLoopMode maintenance, take the effective timer and add new temporary array for (CFIndex IDX = 0, CNT = rlm-> CFArrayGetCount (rlm-> _timers _timers? 0): IDX; < CNT; idx++) {CFRunLoopTimerRef = RLT (CFRunLoopTimerRef) CFArrayGetValueAtIndex (rlm-> _timers, IDX); if (__CFIsValid (RLT) & & __CFRunLoopTimerIsFiring! (RLT)) {if (rlt-> _fireTSR = < limitTSR) {if (timers = CFArrayCreateMutable (timers!) kCFAllocatorSystemDefault, 0, & kCFTypeArrayCallBacks); CFArrayAppendValue (timers, RLT);}}} / / traverse the temporary array each call to __CFRunLoopDoTimer for (CFIndex Timer IDX = 0, CNT = timers? CFArrayGetCount (timers): IDX 0; < CNT; idx++) {CFRunLoopTimerRef = RLT (CFRunLoopTimerRef) CFArrayGetValueAtIndex (timers, IDX; Boolean = __CFRunLoopDoTimer (did) RL, RLM, RLT); timerHandled = timerHandled || did;} if (timers) CFRelease (timers); return timerHandled;}

As we can see, here is whether Timer callback depends entirely on the corresponding Mode array _Timers. So when we add Timer to the commonModes must be at the same time will be added to the commonModes Timer included in the other Mode, we look at the code:

Void CFRunLoopAddTimer (CFRunLoopRef RL, CFRunLoopTimerRef RLT, CFStringRef modeName) {CHECK_FOR_FORK (); if (__CFRunLoopIsDeallocating (RL) if (return); __CFIsValid (RLT) ||! (NULL! = rlt-> _runLoop; & & rlt-> _runLoop! = RL)) return; __CFRunLoopLock (RL); if (modeName = = kCFRunLoopCommonModes) {//commonModes to commonModes branch / / represents a collection of Mode CFSetRef set = rl-> _commonModes? CFSetCreateCopy (kCFAllocatorSystemDefault, rl-> _commonModes: NULL); if (NULL = = rl-> _commonModeItems) {/ / commonModeItems will join the current timer rl-> _commonModeItems = CFSetCreateMutable (kCFAllocatorSystemDefault, 0, & kCFTypeSetCallBacks);} CFSetAddValue (rl-> _commonModeItems, RLT); if (NULL! = set) {CFTypeRef context[2] = {rl, rlt}; add new item to all common-modes / * * / / / the main or this sentence, this sentence is the role of all objects in the collection are calling the __CFRunLoopAddItemToCommonModes method. CFSetApplyFunction (set (__CFRunLoopAddItemToCommonModes), (void * context)); CFRelease (set);}} else {// non commonModes branch of 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;} CFSetAddValue (rlt-> _rlModes, rlm-> _name); __CFRunLoopTimerUnlock (RLT); (__CFRunLoopTimerFireTSRLock); __CFRepositionTimerInMode (RLM, RLT, false); (__CFRunLoopTimerFireTSRUnlock); if (! _CFExecutableLinkedOnOrAfter (CFSystemVersionLion)) we don't do {/ / Normally 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);}}}; static void __CFRunLoopAddItemToCommonModes (const void *value, void *ctx) {CFStringRef = modeName (CFStringRef) value; CFRunLoopRef = Rl (CFRunLoopRef) (((CFTypeRef) CTX) [0]; CFTypeRef (CFTypeRef = item) (() (* CFTypeRef) CTX) [1] (CFGetTypeID); if (item) = = __kCFRunLoopSourceTypeID) {CFRunLoopAddSource (RL, item, modeName (CFRunLoopSourceRef) else (CFGetTypeID);} if (item = = __kCFRunLoopObserverTypeID) {CFRunLoopAddObserver (RL), (CFRunLoopObserverRef) item, modeName else if (CFGetTypeID);} (item = = __kCFRunLoopTimerTypeID) {CFRunLoopAddTimer (RL), (CFRunLoopTimerRef) item, modeName);}}

We can see that, when added to the commonModes, the actual system is to find all the commonModes on behalf of the Mode, such as defaultMode and trackingMode, so that they were added to the mode.
the same method and CFRunLoopAddSource/CFRunLoopAddObserver are the same reason.

So when scrollView or its subclasses to scroll, UIKIT will automatically switch the current runLoopMode to UITrackingRunLoopMode, so you add in the defaultMode of course, the timer will not go.


How does the 4.runLoop sleep is awakened?

From the seventh step, we see that runLoop is in a dormant state. However, the so called sleep state indicates that the current runLoop is marked as sleep, and enters a while loop. Then in the loop to read the port message. If you read a wakeup message from the port, the break drops the while loop and enters the wake.

RunLoop on several of the old drivers have also talked about the mode, in the CoreAnimation of the third have talked about, here only a list.

  • NSDefaultRunLoopMode
  • NSConnectionReplyMode
  • NSModalPanelRunLoopMode
  • UITrackingRunLoopMode
  • NSRunLoopCommonModes

5 what events can wake up runLoop?

As can be seen from the source code, the so-called runLoop into hibernation is only a while cycle, as follows:

Do {if (kCFUseCollectableAllocator) {objc_clear_stack (0); memset (msg_buffer, 0, sizeof (msg_buffer));} (MSG = mach_msg_header_t * msg_buffer (__CFRunLoopServiceMachPort); waitSet, & MSG, sizeof (msg_buffer), livePort, poll & amp;? 0: TIMEOUT_INFINITY); 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 servic E 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);

We also have to look at a function, __CFRunLoopServiceMachPort:

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) {Boolean originalBuffer = true; kern_return_t = RET (KERN_SUCCESS; for; In; that) {/ * sleep of death what nightmares may come... / mach_msg_header_t *msg = (mach_msg_header_t *) *buffer; msg-> msgh_bits = 0; msg-> msgh_local_port = port; msgh_remote_port = msg-> MACH_PORT_NULL; msg-> msg-> msgh_size = buffer_size; msgh_id = 0; if (TIMEOUT_INFINITY = = timeout) {(CFRUNLOOP_SLEEP)}; else {CFRUNLOOP_POLL}); (RET = mach_msg (MSG, MACH_RCV_MSG|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); CFRUNLOOP_WAKEUP (RET); if (MACH_MSG_SUCCESS = = RET) {*livePort = MSG? Msg-> msgh_local_port: MACH_PORT_NULL; return true;} if (MACH_RCV_TIMED_OUT = = RET) {if (! OriginalBuffer) free (MSG); *buffer = NULL; *livePort = MACH_PORT_NULL; return false;} if (MACH_RCV_TOO_LARGE! = RET) buffer_size = round_msg (break; msg-> msgh_size + MAX_TRAILER_SIZE); if (originalBuffer) *buffer = NULL; originalBuffer = false; *buff Er = realloc (*buffer, buffer_size);} HALT; return false;}

We look at the back of this function, here only two will be assigned to livePort, a successful access to news, according to the assignment of msg-> msgh_local_port or MACH_PORT_NULL, and another message will get timeout value for the MACH_PORT_NULL. First of all, please remember these two conclusions.

Then we take a closer look at the while, after the call to __CFRunLoopServiceMachPort if livePort becomes modeQueuePort (livePort = MACH_PORT_NULL), represents the current queue for the detection port, then re entered the two stage cycle under the condition of _dispatch_runloop_root_queue_perform_4CF, that Timer activated the two cycle to jump out of a loop cycle. The purpose of this step is embarrassing old drivers did not understand.

So if livePort is not modeQueuePort when our runLoop is awake. This means that the __CFRunLoopServiceMachPort given by livePort has only two possibilities: one is MACH_PORT_NULL, and the other is the port of the true message.

So we can see the following runLoop processing port time method as follows:

If (MACH_PORT_NULL = livePort) {// what all don't do, there would be such overtime or information is too large (CFRUNLOOP_WAKEUP_FOR_NOTHING); / / handle nothing else} if (livePort = = rl-> _wakeUpPort) {// only external calls CFRunLoopWakeUp will enter this branch, it is the external interface of CFRUNLOOP_WAKEUP_FOR_WAKEUP runLoop (active wake); do nothing on Mac OS / #if USE_DISPATCH_SOURCE_FOR_TIMERS else} if (modeQueuePort! = MACH_PORT_NULL & & livePort = modeQueuePort) {// here is not wake up from runLoop after dormancy here, but the fifth step jump in runLoop10 step in here, is the timer event CFRUNLOOP_WAKEUP_FOR_TIMER (); processing time ellipsis... Is the code... #endif else if} (livePort = = dispatchPort) {// here is GCD submitted to the mainQueue block port CFRUNLOOP_WAKEUP_FOR_DISPATCH (omitted) events;... GCD code... Else {//} before all the cases are not, then wake up runLoop just might be the source of the source1 event. CFRUNLOOP_WAKEUP_FOR_SOURCE (); omit the code for handling source1 source Events...}

RunLoop wake up, and wake up after the time is the above process, we can look at the comments after each branch. At the same time, the core code of runLoopRun will be interpreted.

The rest of the run method is in fact the core method of packaging the old driver not to say:

  • CFRunLoopRunSpecific
  • CFRunLoopRun
  • CFRunLoopRunInMode

So far, the core of the entire process of the old runLoop driver is also calculated with the analysis of all over again!

Old driver production - source analysis of RunLoop
breath

What runLoop can do

Having said so much, what can runLoop do?

The following content from the in-depth understanding of RunLoop, sun source runLoop line sharing video:

AutoReleasePool:

After the start of App, apple registered in the main thread RunLoop two Observer, the callback is _wrapRunLoopWithAutoreleasePoolHandler (). The first Observer monitoring event is Entry (which is about to enter the Loop), which will call _objc_autoreleasePoolPush () to create an auto release pool. Its order is -2147483647, the highest priority, to ensure that the creation of the release pool occurred before all other callback. The second Observer monitors two events: BeforeWaiting (to sleep) when calling the _objc_autoreleasePoolPop () and _objc_autoreleasePoolPush () to release the old pool and create a new pool; Exit (will exit Loop) call _objc_autoreleasePoolPop () to release autorelease pool. The Observer of the order is 2147483647, with the lowest priority to ensure that its release pool occurs after all other callbacks. The code executed in the main thread is usually written in the event callback, Timer callback. These callbacks will be created by the RunLoop AutoreleasePool surround, so there will be no memory leaks, developers do not have to display the creation of Pool.

CAAnimation

We know that CAAniamtion provides us with inter – class animation, as long as the developers have given the state of the middle state after the system automatically generated. So how is the animation, it is given by the developer after the state, the system calculates the parameters of each intermediate state, and then open a timer to callback and change the property.

Event response

Apple registered a Source1 (based on Mach port) to receive system events, the callback function is __IOHIDEventSystemClientQueueCallback. When a hardware event (touch / lock screen / shake, etc.) occurs, the IOKit.framework generates a IOHIDEvent event and is received by the SpringBoard. The details of this process can refer to here. SpringBoard only receive keys (lock screen / mute, etc.), touch, acceleration, proximity sensors, and several other Event, followed by the use of Mach port forward to the needs of the App process. Apple then registered the Source1 will trigger a callback, and call _UIApplicationHandleEventQueue () for the application of internal distribution. _UIApplicationHandleEventQueue (IOHIDEvent) will be processed and packaged into UIEvent for processing or distribution, including the identification of UIGesture/ processing screen rotation / sent to UIWindow, etc.. Events such as UIButton clicks and touchesBegin/Move/End/Cancel events are usually done in this callback.

gesture recognition

When the _UIApplicationHandleEventQueue (above) identifies a gesture, it will first call the Cancel to the current touchesBegin/Move/End series callback interrupt. The system then marks the corresponding UIGestureRecognizer to be processed. Apple has registered a Observer monitoring BeforeWaiting (Loop will enter dormancy) event, the callback function of this Observer is (_UIGestureRecognizerUpdateObserver), its interior will get all just been marked for processing GestureRecognizer, and implement the GestureRecognizer callback. When there is a change in the UIGestureRecognizer (creation / destruction / status change), the callback will be processed accordingly.

timer

Not much to say about this.

PerformSelecter

When the NSObject is called performSelecter:afterDelay:, it actually creates an internal Timer and adds it to the RunLoop of the current thread. So if the current thread does not have RunLoop, this method will fail. When performSelector:onThread: is called, in fact, it will create a Timer to add to the corresponding thread, similarly, if the corresponding thread does not RunLoop the method will fail.

Basically the same.


The old driver to write this blog, it should be the requirements of the friends of the basin to write, but also for the previous summary of the blog and their own interpretation of the source code. After all, there is no source of runLoop after the mysterious. Just hope that we understand that runLoop is not how terrible things, as long as we go to see a little bit, he is also the code written by people = ah. But the old driver lazy to even the pseudo code did not write to you directly on the source. Forgive someone who has a terminal cancer.

Still want to thank the two great God Guo Yaoyuan and sun source before the great God of the two blog and video to explain let me very good. In the name of the great God, I will bring the source word. =


Reference material:

  • Deep understanding of RunLoop
  • Sun Yuan runLoop line sharing video
  • Comments on the RunLoop part of the source code
  • CFRunLoop source

Also if you want to see the sun source video, the old driver has to download link, and then from the beginning to 10 minutes when 1 hours is 35 minutes behind the dry cargo, partial discussion, time tight boots can skip, but behind that share good ideas, listen to what is good.

If you want to see the runLoop source code, because there are more than 4 thousand lines in the source code, the old driver to read the source code when talking about the number of useful lines are recorded down, you can look for the corresponding:

Method name The number of rows
__CFRunLoopFindMode #754
__CFRunLoopCreate #1321
_CFRunLoopGet0 #1358
__CFRunLoopAddItemToCommonModes #1536
__CFRunLoopDoObservers #1668
__CFRunLoopDoSources0 #1764
__CFRunLoopDoSource1 #1829
__CFRepositionTimerInMode #1999
__CFRunLoopDoTimer #2024
__CFRunLoopDoTimers #2152
__CFRunLoopServiceMachPort #2196
__CFRunLoopRun #2308
CFRunLoopRunSpecific #2601
CFRunLoopRun #2628
CFRunLoopRunInMode #2636
CFRunLoopWakeUp #2645
CFRunLoopAddSource #2791
CFRunLoopAddObserver #2978
CFRunLoopAddTimer #3081

After closing!

Old driver production - source analysis of RunLoop
finished the work

The old driver of this blog really waste a lot of things, like good give attention ~ Da ~

Shameless advertising time:

DWCoreTextLabel support cocoaPods ~
pod search DWCoreTextLabel

Old driver production - source analysis of RunLoop
DWCoreTextLabel

Insert pictures, draw pictures, add events to achieve a sentence

Old driver production - source analysis of RunLoop
sentence implementation

As far as possible to keep the system Label attribute allows you to seamlessly transition to use

Seamless transition of Old driver production - source analysis of RunLoop

Well, said so much, the old driver put the address: DWCoreTextLabel, the baby to a star bar ~ love you yo ~!