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
- Foundation
- NSRunLoop
- 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
- CFRunLoopRef: Object representing RunLoop
- CFRunLoopModeRef: indicates the running mode of RunLoop
- CFRunLoopSourceRef: is the input/event source mentioned in the RunLoop diagram
- CFRunLoopTimerRef: is the timing source mentioned in the RunLoop model diagram
- 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:
- 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
- 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
- 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
- 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
- What is runloop?
- The processing logic of runloop
- 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
- What can the Observer be used for?
- Listen for runloop status
- How to use Runloop in development? What are the application scenarios?
- 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