• Small knowledge, big challenge! This article is participating in the creation activity of “Essential Tips for Programmers”.

1. Runloop

Runloop introduction

A RunLoop, called an event processing loop, is part of the thread-specific infrastructure for scheduling work and coordinating the receipt of incoming events. Application in the running process will produce a large number of system and user events, including the timer event, the user interaction events (mouse and keyboard touchpad operation), the modal window events, all kinds of system Source, using a custom Source event and so on, each event is stored in the different FIFO FIFO queue, waiting for the event loop processes in turn. The purpose of RunLoop is to keep threads busy when there is work to be done and to put them to sleep when there is no work to be done.

  • Block application: CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK
  • Call timer: CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION
  • Response source0: CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION
  • In response to source1: CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION
  • GCD Main queue :CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE
  • The observer source: CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION

After the timer is run and the timer is interrupted, __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ is called

A normal do while loop can see that this takes up about 15% of the CPU.

Runloop is basically zero.

Runloop low-level analysis

Search for CFRunLoop underneath and see that CFRunLoopGetCurrent is called

This will try to get or runloop, if not, it will get from the current thread.

Here, if t==0, which is the main thread, pthread_main_thread_NP is called to get the main thread. A mutable dictionary is then created, and the runloop is created by calling __CFRunLoopCreate. The main thread and the created runloop are then placed in the dictionary.

If it is not the main thread, the same operation is performed, except that the thread becomes the current thread.

This means that threads and runloops are bound:

Next look at __CFRunLoopCreate, where the member variables of CFRunLoopRef can be seen in the subassignment below.

Click properties to see the entire structure. CFRunLoop is a structure with a lot of properties in it. See here that _commonModes and _commonModeItems are collection types.

When adding a transaction to a RunLoop, you specify a mode that can be used when adding other transactions, so that mode and transaction are one-to-many.

RunLoopMode structure:

There are three types of transactions:

  • Source
  • Observer
  • Timer

So how do transactions depend on mode to run? In source search addTimer, found that there is a call in __CFRunLoopAddItemsToCommonMode CFRunLoopAddTimer, also found here also proved that the transaction runs three types.

Now look at CFRunLoopAddTimer and see what happens in commonModes. This will get the _commonModeItems set of Runloop modes and determine if the transaction (_commonModeItems) in Runloop is empty. If so, create a new set and copy it to the _commonModeItems set of Runloop. If it is not empty, add it directly to the _commonModeItems collection.

I’m just adding the timer here, so where do I run the timer? You see CFRunLoopRun is calling CFRunLoopRunSpecific.

CFRunLoopRunSpecific calls __CFRunLoopRun and notifies state changes before and after.

__CFRunLoopRun calls __CFRunLoopDoTimers.

The __CFRunLoopDoTimers will traverse the timers in the Runloop and add the timers that meet the conditions to the timers. After traversing, the timers will traverse the timers and execute the timers in the Timers. This is called __CFRunLoopDoTimer.

The callback is then triggered by a call to __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ inside the __CFRunLoopDoTimer.

So the flow here is:

  • CFRunLoopAddTimer
  • CFRunLoopRun
  • CFRunLoopRunSpecific
  • __CFRunLoopRun
  • __CFRunLoopDoTimers
  • __CFRunLoopDoTimer
  • __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__

2.3 Principle of Runloop

Continuing with CFRunLoopRun, the CFRunLoopRun parameter is passed a 1.0e10 (1* 10^10) timeout.

When you saw __CFRunLoopRun called earlier, you saw a two-state notification.

Click on it to see other states besides kCFRunLoopEntry and kCFRunLoopExit. Among them, kCFRunLoopBeforeWaiting and kCFRunLoopAfterWaiting are the most frequently used, representing before and after sleep.

Then look at __CFRunLoopRun. Timeout_context is marked in seconds, a GCD Source Timer is created, and the timeout callback is set.

Moving down you see a doWhile loop where Runloop processes most of its transactions.

Abbreviated code:

/* rl, RLM are locked on entrance and exit */ static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef RLM, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode){start a timer by GCD, Dispatch_source_t timeout_timer = NULL; . dispatch_resume(timeout_timer); int32_t retVal = 0; // Notify Observers: about to deal with timer events __CFRunLoopDoObservers(RL, RLM, kCFRunLoopBeforeTimers); / / notify Observers: __CFRunLoopDoObservers(rl, RLM, kCFRunLoopBeforeSources) // Handle Blocks __CFRunLoopDoBlocks(rl, RLM); Sources0 Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, RLM, stopAfterHandle); If (sourceHandledThisLoop) {// Process Blocks __CFRunLoopDoBlocks(rl, RLM); } / / judge whether port (Source1) if (__CFRunLoopWaitForMultipleObjects (NULL, & dispatchPort, 0, 0, & livePort, NULL)) {// Handle messages 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); // 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: