Principle and application summary of IOS runloop

Runloop: run loop - dead loop

Main purpose: improve performance, do what you have and sleep when you have nothing.
reference resources https://blog.csdn.net/callauxiliary/article/details/107419854

Main application

1. Ensure that the thread runs all the time, and the processing of events, such as touch events and clock events, is completed by runloop.
2. Optimize Caton: put the tasks completed in one runloop into multiple runloops for execution.
3. For the problem of inaccurate timing when the UI slides, set the Mode of the timer to: NSRunLoopCommonModes.
4. The performSelector ***** method (runtime method) needs to be used on the thread. for example

Give Way UITableView,UICollectionView Wait for delay to load pictures

[imageView performSelector:@selector(setImage:) withObject:image afterDelay:0 inModes:@[NSDefaultRunLoopMode]];

This is an optimization point, which is used here defaultMode,Is to stop scrolling and loading pictures without loading pictures

Runloop operation principle

When you call CFRunLoopRun(), the thread will stay in this loop all the time; The function does not return until it times out or is stopped manually. Each time the thread runs RunLoop, it will automatically process the previously unprocessed message and send the message to the observer for the event to be executed.
The life cycle of Runloop: it is created at the first acquisition and destroyed at the end of the thread.

If Runloop wants to run, it must have a mode inside. There must be source\observer\timer in this mode, at least one of them.
Five mode s are registered by default

    a.kCFRunLoopDefaultMode: App Default Mode,Usually the main thread is in this Mode Run under

        b.UITrackingRunLoopMode: Interface tracking Mode,be used for ScrollView Track the touch sliding to ensure that the interface is not affected by other factors when sliding Mode influence

        c.UIInitializationRunLoopMode: Just started App The first time you enter Mode,It will not be used after startup

        d.GSEventReceiveRunLoopMode: Internal to accept system events Mode,Usually not

        e.kCFRunLoopCommonModes: This is a space occupying Mode,Not a real Mode

When RunLoop runs, first find the corresponding mode according to the modeName. If there is no source/timer/observer in the mode, it will return directly.

It is mainly divided into three steps:

1. First, find the corresponding Mode according to the modeName

2. If there is no Source/Timer/Observer in the model, return directly

3. If the model has Source/Timer/Observer, it will enter runloop

Detailed steps

In short, it is a do while loop, which wakes up when there is an input source and sleeps when there is no input source. The official explanation is link . The specific process is as follows:

  1. Notify the observer to start entering Runloop
  2. Notifies the observer to start processing the Timer event
  3. Notifies the observer that non port based events will be processed
  4. Start the prepared event
  5. If the port based event is ready, start it now. And go to step 9
  6. Notify the observer to go to sleep
  7. The thread goes into sleep until the following events occur
    • port based event source appears
    • Timer start
    • The set time has timed out
    • RunLoop is awakened
  8. Notifies the observer that the thread will wake up
  9. Handle events that have entered
    • If the user-defined timer starts, process the event and restart Runloop. Go to step 2
    • Input source startup and message delivery
    • If Runloop is shown to wake up and there is no timeout, restart Runloop. Go to step 2
  10. Notify the observer that Runloop has exited

Runloop composition

There are two kinds of objects: NSRunLoop and CFRunLoopRef.

CFRunLoopRef is within the core foundation framework. It provides API s for pure C functions, all of which are thread safe.
Nsrnloop is a package based on CFRunLoopRef and provides object-oriented APIs, but these APIs are not thread safe.

RunLoop contains five classes, but only three classes related to Source, Timer and Observer are exposed.
1. Timder, clock
2. Source: the source of all events. According to the function call stack, it is divided into source0: non system kernel events and source1: system kernel events.
3. Observer, observer, observe the cycle of runloop.

Apple doesn't allow you to create RunLoop directly. It only provides two automatic functions: CFRunLoopGetMain() and CFRunLoopGetCurrent().

APP startup process

1. When you click APP, the operating system starts a thread to execute the main function of the program. This thread is the main thread of the program. This thread is a resident thread because its Runloop is turned on and will not be released by the thread pool.

Relationship between thread and runloop

There is a one-to-one correspondence between threads and RunLoop, and the relationship is saved in a global Dictionary. When the thread was first created, there was no RunLoop. If you don't take the initiative to obtain it, it will never exist. The creation of RunLoop occurs at the first acquisition, and the destruction of RunLoop occurs at the end of the thread. You can only get its RunLoop inside a thread (except the main thread)

Role of Runloop

1. Ensure that the thread does not exit.
2. Monitor all events, such as touch events, clock events, network events, etc.
If a child thread wants to listen for events, it must open the runloop of the child thread.

Verify that listening can only be realized by adding the clock to runloop

1. The scheduledTimerWithTimeInterval initialization method is added to the runloop of the current thread by default, so it does not need to be added.
This method adds NSDefaultRunLoopMode mode, and UI events will interrupt the response.

-(void)test{

    [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timeerFunC) userInfo:nil repeats:YES];
    
}

-(void)timeerFunC{
    static int num;
    NSLog(@"%d",num);
    num++;
}

3. The sub thread ends runloop

-(void)test{
    //Set the runloop time to be placed on the child thread. If the time passes and the task is not executed, the child thread will end and be released
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSTimer *timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(timeerFunC) userInfo:nil repeats:YES];
        //Add the clock to the current runloop manually. Otherwise, the clock method will not be executed
        [[NSRunLoop currentRunLoop]addTimer:timer forMode:NSDefaultRunLoopMode];
        //When yes, cancel the RunLoop loop
        while (!_finish) {
            NSLog(@"Here comes 11");
            [[NSRunLoop currentRunLoop]runUntilDate:[NSDate dateWithTimeIntervalSinceReferenceDate:1]];
        }
        NSLog(@"coming");
    });
}

-(void)timeerFunC{
    static int num;
    NSLog(@"%d",num);
    num++;
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    NSLog(@"22222");
    _finish = YES;
}

2. The timerWithTimeInterval initialization method needs to manually add the clock to the runloop of the current thread, otherwise the clock cannot be monitored.

-(void)test{
    NSTimer *timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(timeerFunC) userInfo:nil repeats:YES];
    //Add the clock to the current runloop manually. Otherwise, the clock method will not be executed
    [[NSRunLoop currentRunLoop]addTimer:timer forMode:NSDefaultRunLoopMode];
}

-(void)timeerFunC{
    static int num;
    NSLog(@"%d",num);
    num++;
}

Three modes of runloop

1. When the object uses NSDefaultRunLoopMode (the default mode, which generally handles network events and Timer events), the events responding to the object will be suspended when runloop executes UI events.
2. When the object uses uitrackingrunnoop mode (UI mode, which generally handles UI events), the events responding to the object will be suspended only when runloop executes UI events.
3. When the object uses nsrunloop commonmodes (placeholder mode, which is not a real mode, but the above two modes are added at the same time), when the runloop executes UI events, it will also respond to the events of the object (the most ideal result).

Open runloop of current thread

The sub thread handles the events that need to be listened to (when the sub thread needs to be alive and listen all the time). The runloop of the sub thread can be started to make the sub thread a resident thread to avoid being released, so that it can respond to the monitored events. The only way to keep the thread from being released is to start its runloop.
Solution to the problem that NSTimer stops working when sliding
1. Set the Mode of the timer as: nsrunnoopcommonmodes
2. Put NSTimder into the sub thread and start runloop. name is NSDefaultRunLoopMode, and sliding the UI will not affect the Timer.
The events handled by NSTimder are generally placed in the sub thread, and the runloop of the sub thread is started to avoid affecting the main thread to handle events.

-(void)test{

    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSTimer *timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(timeerFunC) userInfo:nil repeats:YES];
        //Add the clock to the current runloop manually. Otherwise, the clock method will not be executed
        [[NSRunLoop currentRunLoop]addTimer:timer forMode: NSDefaultRunLoopMode ];
        //Manually start the runloop of the current thread, which is an endless loop
        [[NSRunLoop currentRunLoop]run];
        NSLog(@"It won't go here");
    });
}

-(void)timeerFunC{
    static int num;
    NSLog(@"%d",num);
    num++;
}

GCD implementation timer

Dispatch Source creates a timer, which is better than NSTimer
The timer attribute must be declared, or it will be released

@property (nonatomic, strong) dispatch_source_t timer;
-(void)test2{
    NSLog(@"start-up");
    _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));
    dispatch_source_set_timer(_timer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 0);
    dispatch_source_set_event_handler(_timer, ^{
        NSLog(@"Execute every one second%@",[NSThread currentThread]);
    });
    dispatch_resume(_timer);
    NSLog(@"start-up");
}

Optimization exercise

When tableview slides fast, high-definition pictures cause Caton optimization.
Reason: when tableview slides quickly, runloop must render all pictures in one loop.
Idea: runloop loads only one picture each time. For example, a screen can display 15 pictures in total, so add them to runloop for loading in 15 times. It is equivalent to dividing the work done at one time into 15 times. For example, listen to the kcrunloopbeforewaiting event, the callback function loads the picture, and delete the task after loading. Each time the image is loaded, it will reach kcrunloopbeforewaiting again, and the image task will continue to be executed.
Monitor the status of runloop through CFRunLoopObserverRef. The function pointer callback. Ref means reference and pointer.
step
1. Add a runloop observer to observe the state change of runloop.
2. Put the code block of the loaded picture into the array.
3. In the observer callback, take out the code of loading pictures in the array for execution.

Keywords: iOS objective-c

Added by ShanesProjects on Mon, 31 Jan 2022 11:39:38 +0200