preface

The basic function

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

RunLoop in main

The bottom UIApplicationMain function starts a RunLoop inside, so UIApplicationMain never returns, keeping the program running

  • The default RunLoop starts relative to the main thread

RunLoop

IOS has two sets of apis to access and use RunLoop

  1. Foundation
  • NSRunLoop
  1. Core Foundation
  • CFRunLoopRef

NSRunLoop and CFRunLoopRef both represent RunLoop objects but NSRunLoop is based on a layer of OC wrapper around CFRunLoopRef, so the bottom layer is CFRunLoopRef

RunLoop with thread

  • Each thread has a unique RunLoop object corresponding to it
  • Now that the main thread RunLoop is created automatically, the child thread RunLoop needs to be created actively
  • The RunLoop is created on the first fetch and destroyed at the end of the thread

Get the RunLoop object

How do you ensure that each thread has a unique corresponding RunLoop object?

  • The system checks to see if a RunLoop exists. If not, it creates a RunLoop and creates a dictionary containing thread-thread-corresponding runloops

Note that the RunLoop obtained by NSRunLoop and CFRunLoopRef are still different objects, but you can convert NSRunLoop to CFRunLoopRef by using.getcfrunloop

Runloops for child threads are created directly using the [NSThread currentThread] method, which is lazily loaded

RunLoop related classes

The five classes of Runloop

  1. CFRunLoopRef: Object representing RunLoop
  2. CFRunLoopModeRef: indicates the running mode of RunLoop
  3. CFRunLoopSourceRef: is the input/event source mentioned in the RunLoop diagram
  4. CFRunLoopTimerRef: is the timing source mentioned in the RunLoop model diagram
  5. CFRunLoopObserverRef: Observer that listens for state changes in RunLoop

The five categories of relationships:

  • In runloop (CFRunLoopRef) there are multiple operating modes (CFRunLoopModeRef), but runloop can only select one operating mode. This mode is called CurrentMode. However, only one mode can be selected each time the air conditioner is turned on.)
  • To switch mode, exit runloop and specify another mode
    • Exist mainly for separating the different groups of source/timer/observer (CFRunLoopSourceRef/CFRunLoopTimerRef/CFRunLoopObserverRef), and let it do not influence each other

  • Each mode must have at least one timer or source, not one observer
  • Each mode can contain several ource/timer/observer

CFRunLoopModeRef

The system registers five modes by default

Application Scenario 1

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ [self timer]; } - (void) timer {/ / 1. Create a timer NSTimer * timer = [NSTimer timerWithTimeInterval: 2.0 target: self selector: @ the selector (run) userInfo:nil repeats:YES]; // The first argument is the timer, the second argument is the mode of runloop, Select the default mode here [[NSRunLoop currentRunLoop] addTimer: timer forMode: NSDefaultRunLoopMode]; } -(void)run{ NSLog(@"run---%@----%@",[NSThread currentThread],[NSRunLoop currentRunLoop].currentMode); }Copy the code

When you click on the emulator, the print is as follows, and you can see that it prints every 2s

The 2021-03-10 10:37:05. 571371 + 0800 runloop1 [1771-51979] run - < NSThread: 0x600001f44a00>{number = 1, Name = main}----kCFRunLoopDefaultMode 2021-03-10 10:37:07.572174+0800 runloop1[1771:51979] run-- <NSThread: 0x600001f44a00>{number = 1, Name = main}----kCFRunLoopDefaultMode 2021-03-10 10:37:09.571467+0800 runloop1[1771:51979] run-- <NSThread: 0x600001f44a00>{number = 1, name = main}----kCFRunLoopDefaultModeCopy the code

But what happens when you add a textView to your storyboard?

Let’s look at scenario two

Application Scenario 2

You’ll notice that when you click on the background, it normally prints every 2 seconds, but when you slide the textView, printing stops, and you stop sliding again, printing starts again

Why is that?

  • Because when you drag the textView, runloop automatically goes into page tracking mode, and when it goes into page tracking mode, it doesn’t bother with the timer anymore
  • When you stop dragging, runloop automatically goes back to default mode and timer keeps running, so it keeps printing

Change the mode of runloop to UITrackingRunLoopMode

- (void) timer {/ / 1. Create a timer NSTimer * timer = [NSTimer timerWithTimeInterval: 2.0 target: self selector: @ the selector (run) userInfo:nil repeats:YES]; / / 2. Add a timer to the runloop in [[NSRunLoop currentRunLoop] addTimer: timer forMode: UITrackingRunLoopMode]; }Copy the code

Run it again and you’ll see that when you swipe textView instead, it will print the following

The 2021-03-10 10:55:22. 039501 + 0800 runloop1 [2084-70476] run - < NSThread: 0x600002eb4880>{number = 1, Name = main}----UITrackingRunLoopMode 2021-03-10 10:55:22.367009+0800 runloop1[2084:70476] run-- <NSThread: 0x600002eb4880>{number = 1, name = main}----UITrackingRunLoopModeCopy the code

You can see that the mode at this point is interface trace mode

So how do you start the timer printing when you click on the View, and print when you drag the TextView?

Let’s move on to the next scene

Application Scenario 3

There are two ways to start the timer printing when you click on the View and print when you drag the TextView:

  1. Add two ways
- (void) timer {/ / 1. Create a timer NSTimer * timer = [NSTimer timerWithTimeInterval: 2.0 target: self selector: @ the selector (run) userInfo:nil repeats:YES]; / / 2. Add a timer to the runloop in [[NSRunLoop currentRunLoop] addTimer: timer forMode: UITrackingRunLoopMode]; [[NSRunLoop currentRunLoop]addTimer:timer forMode:NSDefaultRunLoopMode]; }Copy the code

The print result is as follows

The 2021-03-10 11:01:20. 588388 + 0800 runloop1 [2269-79479] run - < NSThread: 0x60000038c240>{number = 1, Name = main}----kCFRunLoopDefaultMode 2021-03-10 11:01:22.588833+0800 runloop1[2269:79479] run-- <NSThread: 0x60000038c240>{number = 1, Name = main}----kCFRunLoopDefaultMode 2021-03-10 11:01:24.588094+0800 runloop1[2269:79479] run-- <NSThread: 0x60000038c240>{number = 1, Name = main}----UITrackingRunLoopMode 2021-03-10 11:01:26.588974+0800 runloop1[2269:79479] run-- <NSThread: 0x60000038c240>{number = 1, name = main}----UITrackingRunLoopModeCopy the code
  1. Use placeholder mode: NSRunLoopCommonModes

NSRunLoopCommonModes = kCFRunLoopDefaultMode + UITrackingRunLoopMode

  • Occupancy is a tag, and events added to NSRunLoopCommonModes are also added to the operation mode of the common tag
[[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];
Copy the code

Timer creation, there are two ways used in the scenario above is the first one, also is the way of timerWithTimeInterval, the next scene we use the second way scheduledTimerWithTimeInterval

Application Scenario 4:

Recall we just created above the timer method also need to add a timer to the runloop, but scheduledTimerWithTimeInterval created timers is don’t need this, the system will help you to do, and set the run mode bits mode by default

- (void) timer2 {NSTimer * timer = [NSTimer scheduledTimerWithTimeInterval: 2.0 target: self selector: @ the selector (run) userInfo:nil repeats:YES]; }Copy the code

One more question, what happens if our timer is created in a child thread?

  • Obviously, there will be no reaction

Why is that? The reason is simple

  • Because your timer runs in a child thread, but your child thread does not have a corresponding runloop (because you did not create one).
  • So we just need to manually create the runloop for the child thread
  • Runloops for the main thread are created by default. Runloops for child threads need to be created manually
-(void)timer2{//1. Create runloop NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop]; / / 2. Create a timer [NSTimer scheduledTimerWithTimeInterval: 2.0 target: self selector: @ the selector (run) the userInfo: nil repeats: YES]; //3. Start runloop [currentRunLoop run]; }Copy the code

RunLoop application

  • NSTimer
  • Permanent thread
  • ImageView display
  • Automatic release tank
  • performSelector

Focus on the resident thread

  • A normal child thread enters the death state after all the tasks in the thread are completed
  • Even start cannot be restarted
  • In order for our threads to be able to use when we need them and wait when we don’t need them, ready to be reused?

This is where our resident thread comes in

Note that what we want to achieve is for a thread not to die when it completes its task, but to go into wait mode and reuse the thread when needed

Solutions:

  • Enable the thread runloop
  • We know that runloop is designed to work when it’s time to work and rest when it’s time to rest

Let’s move on to specific applications

Define three buttons as follows

  • First, we define a thread property
@property(nonatomic,strong) NSThread *thread;
Copy the code
  • The thread is created when the Create Thread button is clicked
    • Notice that we call the createRunLoop method to create the runloop while creating the thread
- (IBAction)createClickBtn:(id) Sender {// Create thread self.thread = [NSThread alloc]initWithTarget:self selector:@selector(createRunLoop) object:nil]; [self.thread start]; }Copy the code

There are two ways to set up runloops

  1. One is to set a timer
NSRunLoop *currentLoop = [NSRunLoop currentRunLoop]; - (void)createRunLoop{//1. / / 2. Set a timer in the runloop NSTimer * timer = [NSTimer timerWithTimeInterval: 2.0 target: self selector: @ the selector (run) userInfo:nil repeats:YES]; / / 3. Add the timer to the runloop in [currentLoop addTimer: timer forMode: NSDefaultRunLoopMode]; Runloop [currentLoop run]; }Copy the code
  1. One is to set the source (this is preferable because we don’t have to set a timer)
NSRunLoop *currentLoop = [NSRunLoop currentRunLoop]; - (void)createRunLoop{//1. / / 2. Set a source in the runloop [currentLoop addPort: [NSPort port] forMode: NSDefaultRunLoopMode]; Runloop [currentLoop run]; }Copy the code
  • Define the task
- (IBAction)task1ClickBtn:(id)sender { [self performSelector:@selector(task1) onThread:self.thread withObject:nil waitUntilDone:YES]; } - (IBAction)task2ClickBtn:(id)sender { [self performSelector:@selector(task2) onThread:self.thread withObject:nil waitUntilDone:YES]; } -(void)task1{ NSLog(@"task1---%@",[NSThread currentThread]); } -(void)task2{ NSLog(@"task2---%@",[NSThread currentThread]); } / / the run function is used for the timer event - (void) run {NSLog (@ "% s", __func__); }Copy the code

The result is as follows

You can see that when you click two buttons interchangeably, tasks are executed alternately, and on the same thread

RunLoop interview questions

  1. What is runloop?

  1. The processing logic of runloop

  1. When will the automatic release pool be released
  • First creation: Runloop starts
  • Last destruction: Runloop exits
  • Create and destroy at other times: Destroy the previous release pool when runloop is about to sleep and create a new one when it wakes up
  1. What can the Observer be used for?
  • Listen for runloop status
  1. How to use Runloop in development? What are the application scenarios?
  2. Start a resident thread (to keep a child thread from dying, waiting for messages from other threads, and processing other events)

For example, a network request, because the network request is asynchronous and time-consuming, we can create a child thread to take care of the network request function

  • Start a timer in a child thread
  • Do some long-term monitoring in child threads