Decryption - The mysterious RunLoop

Decryption - The mysterious RunLoop

introduction

RunLoop has always been a mysterious field. Many developers with 2 or 3 years of experience cannot accurately describe its function. In fact, RunLoop is not as mysterious as everyone imagines. This article will take you to analyze the "mysterious RunLoop"

What is RunLoop

Literally

  • Run loop
  • Running laps

[[184147]]

cycle

Basic Function

  • Keep the program running (such as the main run loop)
  • Handle various events in the App (such as touch events, timer events, Selector events)
  • Save CPU resources and improve program performance: work when you need to work, and rest when you need to rest

Existence Value

No RunLoop

With RunLoop

Main run loop

  • A RunLoop is started inside the UIApplicationMain function in line 14 of the code
  • Therefore, the UIApplicationMain function never returns, keeping the program running.
  • The default RunLoop is associated with the main thread.

RunLoop Object

  • There are 2 sets of APIs in iOS to access and use RunLoop

Foundation

NSRunLoop

Core Foundation

CFRunLoopRef

  • Both NSRunLoop and CFRunLoopRef represent RunLoop objects
  • NSRunLoop is an OC wrapper based on CFRunLoopRef, so to understand the internal structure of RunLoop, you need to study the API at the CFRunLoopRef level (Core Foundation level)

RunLoop Information

  • Apple official documentation

https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html

  • CFRunLoopRef is open source

http://opensource.apple.com/source/CF/CF-1151.16/

RunLoop and Threads

Each thread has a unique corresponding RunLoop object

The RunLoop of the main thread has been automatically created, and the RunLoop of the child thread needs to be created actively

RunLoop is created when it is first acquired and destroyed when the thread ends.

Get the RunLoop object

  • Foundation
  1. [NSRunLoop currentRunLoop]; // Get the RunLoop object of the current thread
  2.  
  3. [NSRunLoop mainRunLoop]; // Get the RunLoop object of the main thread
  • Core Foundation
  1. CFRunLoopGetCurrent(); // Get the RunLoop object of the current thread
  2.  
  3. CFRunLoopGetMain(); // Get the RunLoop object of the main thread

RunLoop related classes

  • Five classes about RunLoop in Core Foundation

CFRunLoopRef

CFRunLoopModeRef

CFRunLoopSourceRef

CFRunLoopTimerRef

CFRunLoopObserverRef

Note: RunLoop will exit directly if it does not have these things

CFRunLoopModeRef

  • CFRunLoopModeRef represents the running mode of RunLoop

A RunLoop contains several Modes, and each Mode contains several Source/Timer/Observer

Each time the RunLoop is started, only one of the modes can be specified, and this mode is called CurrentMode.

If you need to switch modes, you can only exit the loop and then re-assign a mode to enter.

This is mainly done to separate different groups of Source/Timer/Observer so that they do not affect each other.

Related Classes

The system registers 5 modes by default: (the first two and the last one are commonly used)

  • kCFRunLoopDefaultMode: The default mode of the App. Usually the main thread runs in this mode.
  • UITrackingRunLoopMode: Interface tracking Mode, used for ScrollView to track touch sliding, to ensure that the interface sliding is not affected by other modes
  • UIInitializationRunLoopMode: The first mode entered when the App is just started. It is no longer used after the startup is completed.
  • GSEventReceiveRunLoopMode: Internal Mode for receiving system events, usually not used
  • kCFRunLoopCommonModes: This is a placeholder mode, not a real mode

CFRunLoopSourceRef

  • CFRunLoopSourceRef is the event source (input source)
  • According to the classification of official documents

Port-Based Sources (based on ports, interacting with other threads, through messages published by the kernel)

Custom Input Sources

Cocoa Perform Selector Sources (performSelector… method)

  • Classification by function call stack

Source0: Non-Port-based

Source1: Port-based

Source0: event event, which only contains callbacks. You need to call CFRunLoopSourceSignal(source) first, mark this Source as pending, and then manually call CFRunLoopWakeUp(runloop) to wake up RunLoop.

Source1: Contains a mach_port and a callback, which is used to send messages between the kernel and other threads, and can actively wake up the RunLoop thread.

Function call stack

Function call stack

CFRunLoopTimerRef

  • CFRunLoopTimerRef is a time-based trigger
  • Basically, it is about NSTimer (CADisplayLink is also added to RunLoop), which is affected by the Mode of RunLoop
  • GCD's timer is not affected by RunLoop's Mode

CFRunLoopObserverRef

  • CFRunLoopObserverRef is an observer that can monitor the state changes of RunLoop
  • The following are the time points that can be monitored:

use

  1. - (void)observer
  2.  
  3. {
  4.  
  5. // Create observer
  6.  
  7. CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(),kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
  8.  
  9. NSLog(@ "----Monitoring RunLoop status changes---%zd" , activity);
  10.  
  11. });
  12.  
  13.   
  14.  
  15. // Add observer: monitor the status of RunLoop
  16.  
  17. CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
  18.  
  19.   
  20.  
  21. // Release Observer
  22.  
  23. CFRelease(observer);
  24.  
  25. }

Special attention

  1. /*
  2.  
  3. Memory Management in CF (Core Foundation)
  4.  
  5. 1. All objects created by functions with words such as Create , Copy, and Retain need to be released once at the beginning
  6.  
  7. * For example, CFRunLoopObserverCreate
  8.  
  9. 2.release function: CFRelease(object);
  10.  
  11. */

RunLoop processing logic

– Official version

logic

– Netizens’ edited version

Netizen version

Note: Before entering RunLoop, it will determine whether the mode is empty, and exit directly if it is empty

RunLoop Application

  • NSTimer
  • ImageView display
  • PerformSelector
  • Resident Thread
  • Autorelease pool

1.NSTimer (most common RunLoop usage)

  1. - (void)timer
  2.  
  3. {
  4.  
  5. NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
  6.  
  7. // The timer only runs in NSDefaultRunLoopMode. Once RunLoop enters other modes, the timer will not work.
  8.  
  9. // [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
  10.  
  11.   
  12.  
  13. // The timer only runs in UITrackingRunLoopMode. Once RunLoop enters other modes, the timer will not work.
  14.  
  15. // [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
  16.  
  17.   
  18.  
  19. // The timer will run in the mode marked as common modes
  20.  
  21. // Modes marked as common modes: UITrackingRunLoopMode and NSDefaultRunLoopMode are compatible
  22.  
  23. [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
  24.  
  25. }
  1. - (void)timer2
  2.  
  3. {
  4.  
  5. // The timer returned by scheduledTimer has been automatically added to the current runLoop, and it is NSDefaultRunLoopMode
  6.  
  7. NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nilrepeats:YES];
  8.  
  9.   
  10.  
  11. // Modify mode
  12.  
  13. [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
  14.  
  15. }

Scene restoration

When dragging, the mode changes from NSDefaultRunLoopMode to UITrackingRunLoopMode

At this time, it looks like the following: NSTimer no longer responds to the image rotation.

NSDefaultRunLoopMode

Both modes can run in NSRunLoopCommonModes mode

At this time, it is as follows: NSTimer can run normally in both modes

2. ImageView

Requirement: Do not display the image when the user is dragging (UI interaction), and display the image when the dragging is completed

Method 1: Monitor UIScrollerView scrolling (monitor through UIScrollViewDelegate, no example here)

Method 2 RunLoop Set the running mode

  1. // Only display images in NSDefaultRunLoopMode mode
  2.  
  3. [self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@ "placeholder" ]afterDelay:3.0 inModes:@[NSDefaultRunLoopMode]];

3.PerformSelector

inModes: Set the operating mode

4. Resident thread (important)

Application scenario: often perform time-consuming operations in the background, such as monitoring network status, scanning sandbox, etc., and do not want the thread to be destroyed after processing the event and keep it in a resident state

*** Type (recommended)

Open

  1. - (void)run
  2.  
  3. {
  4.  
  5. //addPort: add port (source) forMode: set mode
  6.  
  7. [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
  8.  
  9. //Start RunLoop
  10.  
  11. [[NSRunLoop currentRunLoop] run];
  12.  
  13.   
  14.  
  15. /*
  16.  
  17. //Two other startup methods
  18.  
  19. [NSDate distantFuture]: distant future This is the same as run above.
  20.  
  21. [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
  22.  
  23. Do not set mode
  24.  
  25. [[NSRunLoop currentRunLoop] runUntilDate:[NSDate distantFuture]];
  26.  
  27. */
  28.  
  29. }

Exit - Exit the current thread

  1. [NSThread exit];

The second method (weird method)

Advantages: It is convenient to exit RunLoop - define a flag while(flag){…}

  1. - (void)run
  2.  
  3. {
  4.  
  5. while (1) {
  6.  
  7. [[NSRunLoop currentRunLoop] run];
  8.  
  9. }
  10.  
  11. }

5. Autorelease pool

Release before sleeping (kCFRunLoopBeforeWaiting), create a release pool before processing events, and the objects created in the middle will be put into the release pool

Special Note:

It is recommended to wrap it with @autoreleasepool {...} before starting RunLoop

Significance: Create a large release pool to release temporary objects created during {}. Generally, good framework authors will do this.

  1. - (void) execute  
  2.  
  3. {
  4.  
  5. @autoreleasepool {
  6.  
  7. NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
  8.  
  9. [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
  10.  
  11. [[NSRunLoop currentRunLoop] run];
  12.  
  13. }
  14.  
  15. }

Off topic:

In order to improve the user experience in the future, we can put the operations that need to be done into NSDefaultRunLoopMode without event processing when the user interacts with the UI.

Added: GCD timer

The general NSTimer timer may be inaccurate due to RunLoop.

As mentioned above, GCD is not affected by RunLoop. Let's briefly talk about its use.

  1. /** Timer (no need to bring * here, because dispatch_source_t is a class, which already contains *) */
  2.  
  3. @property (nonatomic, strong) dispatch_source_t timer;
  4.  
  5.   
  6.  
  7. int   count = 0;
  8.  
  9. - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
  10.  
  11. {
  12.  
  13. // Get the queue
  14.  
  15. // dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
  16.  
  17. dispatch_queue_t queue = dispatch_get_main_queue();
  18.  
  19.   
  20.  
  21. // Create a timer (dispatch_source_t is essentially an OC object)
  22.  
  23. self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
  24.  
  25.   
  26.  
  27. // Set various properties of the timer (when to start the task, how often to execute it)
  28.  
  29. // GCD time parameter, usually nanoseconds NSEC_PER_SEC (1 second == 10 to the 9th power nanoseconds)
  30.  
  31. // When to start executing the first task
  32.  
  33. // dispatch_time(DISPATCH_TIME_NOW, 3.0 * NSEC_PER_SEC) is 3 seconds later than the current time
  34.  
  35. dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC));
  36.  
  37. uint64_t interval = (uint64_t)(1.0 * NSEC_PER_SEC);
  38.  
  39. dispatch_source_set_timer(self.timer, start, interval, 0);
  40.  
  41.   
  42.  
  43. // Set callback
  44.  
  45. dispatch_source_set_event_handler(self.timer, ^{
  46.  
  47. NSLog(@ "------------%@" , [NSThread currentThread]);
  48.  
  49. count ++;
  50.  
  51.   
  52.  
  53. // if ( count == 4) {
  54.  
  55. // // Cancel the timer
  56.  
  57. // dispatch_cancel(self.timer);
  58.  
  59. // self.timer = nil;
  60.  
  61. // }
  62.  
  63. });
  64.  
  65.   
  66.  
  67. // Start the timer
  68.  
  69. dispatch_resume(self.timer);
  70.  
  71. }

RunLoop Interview Questions

There are often interviewers who like to show off and ask about RunLoop during the interview. But does he really know how to use it? Maybe he himself doesn’t quite understand it.

Below I will give a brief summary of the interview about RunLoop, which is also a summary of the whole article

  • What is RunLoop?

Literally: running loop, running in circles

In fact, it is a do-while loop inside, which continuously processes various tasks (such as Source, Timer, Observer)

One thread corresponds to one RunLoop. The RunLoop of the main thread is started by default, and the RunLoop of the child thread needs to be started manually (call the run method)

RunLoop can only select one Mode to start. If there is no Source, Timer, or Observer in the current Mode, then RunLoop will exit directly.

  • How to use RunLoop in development? What are the application scenarios?
  • Start a resident thread (to prevent a child thread from entering the extinction state, waiting for messages from other threads and processing other events)

Start a timer in the child thread

Do some long term monitoring in the child thread

  • The timer can be controlled to execute in a specific mode
  • Allows certain events (behaviors, tasks) to be executed in a specific mode
  • You can add an Observer to monitor the status of the RunLoop, such as monitoring the processing of click events (doing something before all click events)

***

The article I published before was not very complete. I spent two days to sort it out again. If there are any deficiencies, please point them out. I will update it as soon as possible.

<<:  Don't worry about MVC or MVP. Listen to me.

>>:  Summary of Android methods to avoid memory overflow (Out of Memory)

Recommend

7 simple ways to quickly understand user dads through online data

No matter what kind of marketing we do, we need t...

WWDC2015 animation effects

Every year, Apple holds a major conference. WWDC ...

How to deal with the sluggish growth of TikTok accounts?

User visual fatigue, slow content iteration, and ...

The "involution" of the 618 content marketing war

This year's 618 is becoming more and more &qu...

What does it mean when beauty relies on beauty?

Beauty leans on beauty, what exactly is beauty le...

Why is the shrub-covered "Roof of the World" a "forbidden zone" for trees?

Friends who have traveled to the Qinghai-Tibet Pl...

How much does it cost to make a rain gear app in Suzhou?

Suzhou rain gear applet production price 1. Displ...