It started on a personal blog

What is the RunLoop

What is runloop? Runloop is what it sounds like. It’s basically a loop, but it’s an advanced loop. While a normal while loop causes the CPU to wait in a busy state, Runloop is an “idle” wait, which is analogous to epoll under Linux. When no event occurs, the Runloop goes to sleep. When an event occurs, the Runloop goes to the corresponding Handler to handle the event. Runloop keeps threads busy when they need to do something, and puts them to sleep when they don’t

Before you start, think about these interview questions

  • How does runloop relate to threads?
  • Relation between Timer and Runloop?
  • If I add an NSTimer that responds every 3 seconds, when I drag the tableview, the timer may not respond. What can I do?
  • Implementation logic inside Runloop?
  • How does runloop respond to user actions?
  • Talk about several states of runLoop
  • What is the mode of runloop?

Source code analysis

Before answering questions, let’s look at the source code

Opensource.apple.com/tarballs/CF RunLoop source code… Download the latest CF-1153.18.tar.gz(the latest version at the time of writing)

View the source code

CFRunLoopRef CFRunLoopGetCurrent(void) {
      CHECK_FOR_FORK();
      CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
      if (rl) return rl;
      return _CFRunLoopGet0(pthread_self());
}
Copy the code

through

_CFRunLoopGet0 obtainedCopy the code

Go in and see what’s done

loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
	if(! loop) { CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop); loop = newLoop; }Copy the code

If the loop is empty, create a runloop for the key from this thread

Summary:

  • Each thread has a unique RunLoop object corresponding to it
  • Runloops are stored in a global Dictionary, with threads as keys and runloops as values
  • The thread is created without a RunLoop object; the RunLoop is created the first time it gets it
  • The RunLoop will be destroyed at the end of the thread.

The runloop mode

Let’s take a look at the five classes for Runloop in Core Foundation

CFRunLoopRef

CFRunLoopModeRef

CFRunLoopSourceRef

CFRunLoopTimerRef

CFRunLoopObserverRef
Copy the code

Take a look at the runloop 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;
    CFAbsoluteTime _runTime;
    CFAbsoluteTime _sleepTime;
    CFTypeRef _counterpart;
};
Copy the code

Only the main ones are left

typedef struct __CFRunLoop * CFRunLoopRef; struct __CFRunLoop { pthread_t _pthread; CFMutableSetRef _commonModes; CFMutableSetRef _commonModeItems; CFRunLoopModeRef _currentMode; CFMutableSetRef _modes; CFMutableSetRef _modes; };Copy the code

CFRunLoopRef contains _modes, which are a set of CFRunLoopModeRef modes. Only one of these modes is the current mode, called _currentMode

Now let’s look at what’s in runloopMode, and again, just keep the main ones, the key ones are the following four, right

To sum it up

  • CFRunLoopModeRef indicates the running mode of RunLoop
  • A RunLoop contains several Mode, each Mode and contains several Source0 / Source1 / Timer/Observer
  • Only one of these modes can be selected as currentMode when RunLoop starts
  • If you need to switch Mode, you can only exit the Loop and select another Mode to enter
  • Different groups of Source0 / Source1 / Timer/Observer can be separated, each other
  • If there is no any Source0 / Source1 / Timer Mode/Observer, RunLoop immediately exit
typedef struct __CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoopMode {

    CFMutableSetRef _sources0;
    CFMutableSetRef _sources1;
    CFMutableArrayRef _observers;
    CFMutableArrayRef _timers;
};
Copy the code

There are many modes in RunLoop, but only one is currently running, represented by a graph

The logic used in a particular type of runloop is shown below

So, what exactly do _sources0, _sources1, _observers, _timers J contain? Sum it up with a picture and then go into details

As shown in the above, _sources0 contains touch events, and the performSelector: onThread: run the code certificate, first create a new project, realize the click event, NSLog just to breaking point

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
}

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    NSLog(@"This is printed just to break the dots."); After the} @end breakpoint is paused, enter the LLDB instruction bt as shown in the figure belowCopy the code

The SOURCE0 method __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ is called from the print log, so verify performSelector

So let’s look at the Timer again

For the other cases, the reader will verify.

Use a diagram to summarize the logic of RunLoop

Source code internal detail analysis

If you want to analyze the source code, you should first know where the entry is


SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */ CHECK_FOR_FORK(); // notify Observers: enter Loop __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry); Result = __CFRunLoopRun(rl, currentMode, seconds,returnAfterSourceHandled, previousMode); // notify Observers: exit Loop __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);return result;
}
Copy the code

__CFRunLoopRun = __CFRunLoopRun = __CFRunLoopRun

/* rl, rlm are locked on entrance and exit */
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    int32_t retVal = 0;
    do{// notify Observers: about to handle Timers __CFRunLoopDoObservers(RL, RLM, kCFRunLoopBeforeTimers); // notify Observers: about to deal with Sources __CFRunLoopDoObservers(Rl, RLM, kCFRunLoopBeforeSources); // Handle Blocks __CFRunLoopDoBlocks(rl, RLM); / / Sources0 processingif(__CFRunLoopDoSources0(rl, RLM, stopAfterHandle)) {__CFRunLoopDoBlocks(rl, RLM); } Boolean poll =sourceHandledThisLoop || (0ULL == timeout_context->termTSR); // Check whether there issource1
        if(__CFRunLoopServiceMachPort(dispatchPort, & MSG, sizeof(MSg_buffer), &livePort, 0, &voucherState, NULL)) {// If there is onesource1 goto handle_msg goto handle_msg; } // Notify Observers: about to hibernate __CFRunLoopDoObservers(rl, RLM, kCFRunLoopBeforeWaiting); __CFRunLoopSetSleeping(rl); // Notify Observers: end hibernation __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
    handle_msg:;
        
        if{// Handle Timers __CFRunLoopDoTimers(rl, RLM, mach_absolute_time())}else if{// Handle GCD __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(MSG); }else{/ / to be here, that means be awakened Source1 __CFRunLoopDoSource1 (rl, RLM, RLS, MSG, MSG - > msgh_size, & reply) | |sourceHandledThisLoop; // Handle Blocks __CFRunLoopDoBlocks(rl, RLM); // Set the return value to decide whether to continue the loopif (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;
            }
            voucher_mach_msg_revert(voucherState);
            os_release(voucherCopy);
        } while (0 == retVal);
        
        return retVal;
}


Copy the code

This is what it looks like in a screenshot

When the LLDB debugs the stack, how do these methods get called? Here uses __CFRunLoopDoTimers as an example

There are five known modes

KCFRunLoopDefaultMode: the default Mode of the App, usually the main thread is run in this Mode UITrackingRunLoopMode: Interface tracking Mode, to ScrollView tracking touch sliding, guarantee the interface slip is not affected by other Mode UIInitializationRunLoopMode: When just start the App the first to enter the first Mode, after the completion of the start will no longer use GSEventReceiveRunLoopMode: accept system internal Mode of events, usually in less than kCFRunLoopCommonModes: This is a placeholder Mode, not a real ModeCopy the code

The state of the RunLoop

/* Run Loop Observer Activities */ typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) { kCFRunLoopEntry = (1UL << 0), KCFRunLoopBeforeTimers = (1UL << 1), // Timer kCFRunLoopBeforeSources = (1UL << 2), // Source kCFRunLoopBeforeWaiting = (1UL << 5), kCFRunLoopAfterWaiting = (1UL << 6), KCFRunLoopExit = (1UL << 7), // About to exit Loop kCFRunLoopAllActivities = 0x0FFFFFFFU};Copy the code

Two common modes

  • KCFRunLoopDefaultMode (NSDefaultRunLoopMode) : The default Mode of the App, in which the main thread usually runs

  • UITrackingRunLoopMode: interface tracing Mode, used by ScrollView to track touch sliding, ensuring that the interface sliding is not affected by other modes

RunLoop and NSTimer

We know that the NSTimer timer, by default, will be interrupted by the UIScrollView, which will affect the use of the timer. The reason is that when you scroll, the RunLoop switches to UITrackingRunLoopMode, but the timer is in NSDefaultRunLoopMode, so it stops. The solution is to set NSRunLoopCommonModes. Special attention should be paid to:

NSRunLoopCommonModes are not really modes, they are just tokens

References for this article:

RunLoop official source code

Basic principles of iOS

For more information, please pay attention to the personal public number