1. What is runloop?

A runLoop is a loop that receives and processes asynchronous message events. In a loop, wait for an event to occur and then send the event to a place where it can be processed. From the source can also be seen, is essentially a do-while loop.




2. What does runloop do?

2.1. Keep the program running continuously

We know that the program starts with the main function, as shown below. UIApplicationMain will start the main thread runloop to ensure that the program can continue running when there is no task to execute.

int main(int argc,char* argv[]) {   @autoreleasepool {        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));    }}


2.2. Handle various events in the APP (touches, timers, performSelector, etc.)

A runloop is executed and a callback to __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ is called. That triggers the ViewController’s ‘TouchBegan’ method up.




Similarly, we could write another timer code that, in the same way, would see that runloop was also executed and the __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ was called back.




Under the main thread, almost everything starts from the following six functions:

CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION

CFRunloop is calling out to an abserver callback function

Used to report external changes to the current state of the RunLoop, many of the mechanisms in the framework are triggered by the RunLoopObserver, such as CAAnimation

CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK

CFRunloop is calling out to a block

Message notifications, non-delayed Perform, Dispatch calls, block callbacks, KVO

CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE

CFRunloop is servicing the main desipatch queue

Main thread callback

CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION

CFRunloop is calling out to a timer callback function

A delayed Perform, delayed dispatch call

CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION

CFRunloop is calling out to a source 0 perform function

Handle App internal events and manage (trigger) by App itself, such as UIEvent and CFSocket. Normal function calls, system calls

CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION

CFRunloop is calling out to a source 1 perform function

Managed by RunLoop and kernel and driven by Mach port such as CFMachPort and CFMessagePort


2.3 Save CPU resources, the performance of the provider: work as it should, rest as it should

If you look at the CPU consumption in the main function, you can see that runloop uses almost no CPU when it starts hibernation and only wakes up when it needs to do something. The while loop is a constant drain on CPU performance.







3. How does runloop relate to threads?

Each RunLoop is associated with a thread. Specifically, there were threads first, then runloops.

The relationship between threads and runloops is clear in the first section of the Official RunLoop documentation.

We don’t have to, and preferably don’t, create runloops explicitly. Apple provides two apis for us to get runloops.

CFRunLoopGetMain() and CFRunLoopGetCurrent(), used to get the MainRunLoop and the current thread’s RunLoop, respectively.

Let’s take a look at the source implementation of these two functions:




The RunLoop is obtained by the _CFRunLoopGet0 function and takes the thread as an argument.

Next, take a look at the implementation of _CFRunLoopGet0




If the global dictionary is not found, create a new one and store it in the global dictionary with the thread as the key and RunLoop as the Value. Create a MainRunLoop and save it to the global dictionary.

So we can conclude that runloops correspond to threads one to one.

4. Runloop objects and mode?

By:

NSLog(@”%@”, [NSRunLoop mainRunLoop]); You can see inside the RunLoop.

In order for a RunLoop to run, it must be supported by a Mode object, which must have :(NSSet *)Source, (NSArray *)Timer, Source and Timer




CFRunloop and CFRunLoopMode:







It can be concluded that each RunLoop contains several modes, and each Mode contains several sources/timers/observers.

Each time a RunLoop starts, only one of the modes can be specified. This Mode is called CurrentMode. If you want to change the Mode, you must exit the Loop and re-specify another Mode to enter. This is mainly to separate different groups of Source/ Source/ Observer from each other (you can switch Mode to complete different Timer/ Source/ Observer).




The system registers five modes by default:

NSDefaultRunLoopMode: the default Mode of the App in which the main thread normally runs (by default)

UITrackingRunLoopMode: interface tracking Mode, used for ScrollView tracking touch sliding, to ensure that interface sliding is not affected by other modes (operating under the UI interface)

UIInitializationRunLoopMode: enter one of the first Mode when just start the App, start after the completion of the will no longer be used

GSEventReceiveRunLoopMode: accept system internal Mode of events, usually in less than (drawing)

NSRunLoopCommonModes: This is a placeholder Mode, not a true Mode. (RunLoop cannot start this Mode. Setting this Mode does not change the fact that RunLoop can only run in one Mode at a time.)

The following are the main differences between NSDefaultRunLoopMode and UITrackingRunLoopMode and NSRunLoopCommonModes. Take a look at the following code:

– (void)viewDidLoad {   

[super viewDidLoad];

NSTimer * timer = [NSTimer timerWithTimeInterval: 2.0 target: self selector: @ the selector (run) the userInfo: nil repeats: YES]; // The timer added in default mode when we drag the textView, Not run run method [[NSRunLoop currentRunLoop] addTimer: timer forMode: NSDefaultRunLoopMode];

// Add timer in UI trace mode when we drag the textView, Run method will run [[NSRunLoop currentRunLoop] addTimer: timer forMode: UITrackingRunLoopMode];

// Timer can run in two modes, equivalent to the above two lines of code written together

[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; }

– (void)run{

    NSLog(@”——–run”);

}

[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];

[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:YES modes:@[UITrackingRunLoopMode]];

[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:YES modes:@[NSRunLoopCommonModes]];


CFRunLoopTimerRef

CFRunLoopTimerRef is an event-based trigger

CFRunLoopTimerRef is basically NSTimer, which is affected by the Mode of RunLoop

There are two ways to create a Timer. The following method must be manually added to the RunLoop to be called

// A timer created this way must be manually added to the RunLoop to be called

NSTimer * timer = [NSTimer timerWithTimeInterval: 2.0 target: self selector: @ the selector (time) the userInfo: nil repeats: YES];

[[NSRunLoop currentRunLoop] addTimer:timerforMode:NSDefaultRunLoopMode];

// Make the RunLoop run at the same time

[[NSRunLoop currentRunLoop] run];

When a Timer is created using scheduledTimer, it is automatically added to the current thread and runs in NSDefaultRunLoopMode as follows:

[NSTimer scheduledTimerWithTimeInterval: 2.0 target: self selector: @ the selector (run) the userInfo: nil repeats: YES];

/ * note: The scheduledTimer has been automatically added to the current runLoop and is NSDefaultRunLoopMode. For this method to work, you must first run the runLoop object with the timer added. Created by scheduledTimerWithTimeInterval timer can be modified by the following method mode * /

[[NSRunLoop currentRunLoop] addTimer:timer2 forMode:UITrackingRunLoopMode];

Note: GCD timer is not affected by RunLoop Mode

CADisplayLink *display = [CADisplayLink displayLinkWithTarget:self selector:@selector(run)]; / * note: CADisplayLink, which also runs under Runloop, [display addToRunLoop:[NSRunLoop currentRunLoop] there is a way to add a CADisplayLink object to a Runloop object. forMode:NSDefaultRunLoopMode];

CFRunLoopSourceRef

CFRunLoopSourceRef is actually the event source (input source)

According to official documents, Source classification

Port-based Sources: Indicates that some messages are sent from the Mac source that interacts with other threads

Custom Input Sources: user-defined Input Sources

Cocoa Perform Selector Sources(self performSelector:…)

Classification by function call stack, Source

Source0: non-port-based (touch events, button click events)

Source1: Port-based, communicates with other threads through the kernel, receiving and distributing system events

(Touch the hardware, receive and distribute system events via Source1 to Source0 for processing)

To see how Source passes events through the function call stack, we do the following experiment:




As you can see, from the time the program starts start, the function call stack listens for the event click all the way down to the -buttonclick: method, passing through CFRunLoopSource0, indicating that our buttonClick event belongs to Source0.

On the other hand, Source1 is port-based, that is, Source1 interacts with hardware. Touches are first wrapped as an event on the screen, then distributed to Source0, and finally processed by Source0.




We can customize a source (only source0, source1 is triggered by the system kernel) :




CFRunLoopObserverRef

CFRunLoopObserverRef is an observer that can listen for RunLoop status changes. It mainly listens for the following time nodes:

/* Run Loop Observer Activities */

typedefCF_OPTIONS(CFOptionFlags, CFRunLoopActivity)

{   

KCFRunLoopEntry = (1UL <<0),// 1 is about to enter Loop

KCFRunLoopBeforeTimers = (1UL <<1),// 2 is about to process Timer kCFRunLoopBeforeSources = (1UL <<2),// 4 is about to process Source KCFRunLoopBeforeWaiting = (1UL <<5),// 32 is going to sleep

KCFRunLoopAfterWaiting = (1UL <<6),// 64 just woke up from sleep

KCFRunLoopExit = (1UL <<7),// 128 is about to exit Loop

KCFRunLoopAllActivities =0x0FFFFFFFU// Listen for all events

};

// 1. Create an observer to listen for RunLoop

// Parameter 1: there is a default value CFAllocatorRef :CFAllocatorGetDefault() // Parameter 2: CFOptionFlags Activities Listen for the enumeration of RunLoop activities as shown above

// Repeat listen for Boolean repeats YES

// Parameter 4: CFIndex order pass 0

CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities,YES,0, ^(CFRunLoopObserverRef observer,CFRunLoopActivity activity) {

NSLog(@”%zd”, activity); // This method can do something before adding a timer, something before adding a source. });

// 2. Add observer to listen on the current RunLoop object CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);

CFRelease(observer); CFRelease(observer);

The state of the RunLoop can be observed by printing





5. Runloop source code analysis

Using a timer as an example, let’s look at the source code of the items added to a runloopMode by a timer:




Add runloopRun to runloopRun.




Before running CFRunloopRun, notify the Observer to enter runloop: kCFRunLoopEntry, and then notify the Observer to exit runloop: kCFRunLoopExit. CFRunLoopRun: CFRunLoopRun: CFRunLoopRun: CFRunLoopRun: CFRunLoopRun




Look again at the specific __CFRunLoopDoBlocks execution code: You can see that every item is looped out from the mode items (mode items are added by addTimer, of course, you can also add addObserver, addSource, here is the example of timer) : If the execution condition DOIT is met, the __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__ callback is fired.




Therefore, we can come to the conclusion that no matter the timer, observer or source, it is necessary to add items into the runloop first before it can be executed during the runloop run.