当前位置:网站首页>Understanding runloop in OC

Understanding runloop in OC

2020-11-09 10:56:00 osc_7dn4hojn

understand OC in RunLoop

What is? RunLoop?

It can be simply understood as , One that keeps the program running while loop , This loop listens for various events ( Such as touching events 、performSelector、 Timer NSTimer etc. ), Sleep when there are no events , In order to make effective use of CPU( Use only when there is an event CPU, Sleep when there is no event )

No matter RunLoop How complex is it? , Its essence is the above-mentioned : A cycle , Handle events when there is an event , Sleep when there is no event ( Sleep here refers to switching from user mode to kernel mode , Such a dormant thread is suspended , No more occupation cpu resources ).

RumLoop It has the following relationship with threads :

  • There is only one thread RunLoop object
  • Main thread RunLoop The default has been created , Child threads need to be created manually .
  • RunLoop Create... On first acquisition , Destroy at the end of the thread .

Let's verify , stay main Before the function returns , A print :

int main(int argc, char *argv[])
{
    NSString *appDelegateClassName;
    @autoreleasepool {
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    }
    int ret = UIApplicationMain(argc, argv, nil, appDelegateClassName);
    NSLog(@"after ret");
    return ret;
}

It turns out there's no print , This shows that the main process has entered a RunLoop Lord , The main process doesn't end , You can't jump out RunLoop, You can't print later .

Let's print the main thread's RunLoop try :

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"%@", [NSRunLoop currentRunLoop]);
}

//  Print the results ( Just take the key information ):
// CFRunLoop 0x600001704700
// current mode = kCFRunLoopDefaultMode,

This shows that the main thread is in a RunLoop in , And the current mode of operation is kCFRunLoopDefaultMode

It feels like this RunLoop It's simple , But it's complicated , Because there are many factors to consider , For example, the processing sequence of various events , Timer 、 Multithreading and so on

For a complex problem , One of the solutions is abstraction , Apple to solve the above problem , Abstracted RunLoop object ,RunLoop Contains multiple Mode class , Every mode Class contains several Source,Observer and Timer class , Relations are as follows :

 Insert picture description here

Mode yes RunLoop The mode of operation of , There are five kinds of :

kCFRunLoopDefaultMode //App Default Mode, Usually the main thread is in this Mode Run under 
UITrackingRunLoopMode // Interface tracking  Mode, be used for  ScrollView  Track touch slide , Make sure that the interface doesn't slide under any other  Mode  influence 
UIInitializationRunLoopMode //  Just started  App  The first to enter  Mode, It will not be used after startup 
GSEventReceiveRunLoopMode //  Accept the inside of the system event  Mode, It's not usually used 
kCFRunLoopCommonModes //  This is a placeholder Mode, It's not a real Mode, It can be simply understood as kCFRunLoopDefaultMode and UITrackingRunLoopMode The combination of 

there Source It's the source of the event , For example, touch events .

Observer Is the observer , Events that listen to the event source , It can be simply understood as thread , For example, the main thread RunLoop Of course Observer It's the main thread .

There are also some rules :

  • RunLoop Although there are many Mode, but RunLoop Function execution time , Only one... Can be specified Mode
  • If you want to switch Mode, You need to wait for a Loop The loop ends , And let the new Mode Get into

It says a RunLoop only one Mode In execution , Let's do an experiment to see :

@interface ViewController ()

@property (weak, nonatomic) IBOutlet UITextView *textView;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerTest) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
}

- (void)timerTest {
    NSLog(@"%s", __func__);
}

@end

Here we are ViewControlller It creates a timer, Add him to NSDefaultRunLoopMode in , This ViewControlller There's a scroll UITextView( Inherit UIScrollView,UIScrollView default Mode yes UITrackingRunLoopMode

When we slide UITextView When ,timer Stop triggering events , explain RunLoop Of Mode from Default Switch to the UITrackingRunLoopMode

The solution is to put timer Put in kCFRunLoopCommonModes in , This Mode It's equivalent to being at the same time kCFRunLoopDefaultMode and UITrackingRunLoopMode:

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

This is a classic example , Can be solved in UIScrollView( Including its subclasses ) There is NSTimer Timing scene .

Inspired by this , We can use RunLoop Solve the stuck problem , There's a kind of stuck problem, which is UITableView There are a lot of big HD images to load in , When you slide the screen, you get stuck .

Let's first analyze the cause of the jam : The most fundamental reason is RunLoop It took too long to make a turn , Because once RunLoop The loop needs to parse a lot of big HD images , The system needs a certain amount of time to render each large high-definition image , This needs to wait until the rendering RunLoop After that , To switch between sliding screens RunLoop Of Mode(UITrackingRunLoopMode), The solution is :

  • Create a timer : At regular intervals ( It can be 0.01s) Execute an empty method to wake up RunLoop
  • Load the image loading method into block, take block Add a limited number of arrays , When block Exceeding the maximum number limit , Remove the first added block
  • monitor RunLoop The awakening of , When you wake up and go back, you execute it once and take one out of the array block Event to perform , The finished event is removed from the array

This design makes RunLoop Only one loading image is executed in each loop block( Reduce RunLoop The time of a single cycle ). Set a maximum limit on the number of arrays , It can prevent too many pictures to be rendered at the same time ( Reduce RunLoop Total time to render images ).

Let's take a look at RunLoop What does it look like :

RunLoop Internal logic

 Insert picture description here

A new concept is introduced here :source0 It's touch events and all execution performSelector Method ,source1 Is based on port Communication between threads of .

Here we can roughly see that RunLoop The order in which events are handled in , It can be summarized briefly as follows :

  1. Let me know first Timer,Sources It's time to deal with the incident
  2. Handle source0
  3. See if there's any source1, Sleep if you don't , There is no sleep
  4. In sleep sources,timer,dispatch, You can wake it up manually
  5. 3 End or 4 After wake up , I started to deal with all kinds of other events (timer,source1,dispatch)
  6. If step five deals with at least one event , Then a new round of RunLoop, Otherwise quit RunLoop

The above logic can lead to , stay RunLoop in , As long as there is any one event ,RunLoop Will not quit , Unless it is RunLoop Awakened or forced to stop on sleep timeout , You're going to quit .

Let's use an example to feel RunLoop Logic in :

- (void)viewDidLoad {
    [super viewDidLoad];

    [self createObserver];
    
    [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerFired) userInfo:nil repeats:YES];
}

- (void)timerFired
{
    NSLog(@"---- timer fired ----");
}

- (void)createObserver
{
    // Create listener 
    /*
     The first parameter  CFAllocatorRef allocator: Allocate storage space  CFAllocatorGetDefault() Default assignment 
     The second parameter  CFOptionFlags activities: The status to be monitored  kCFRunLoopAllActivities  Listen to all States 
     The third parameter  Boolean repeats:YES: Continuous monitoring  NO: It doesn't last 
     Fourth parameter  CFIndex order: priority , General filling 0 that will do 
     Fifth parameter  : Callback   Two parameters observer: monitor  activity: Listening events 
    */

    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        switch (activity) {
            case kCFRunLoopEntry:
                NSLog(@"RunLoop Get into ");
                break;
            case kCFRunLoopBeforeTimers:
                NSLog(@"RunLoop To deal with Timers 了 ");
                break;
            case kCFRunLoopBeforeSources:
                NSLog(@"RunLoop To deal with Sources 了 ");
                break;
            case kCFRunLoopBeforeWaiting:
                NSLog(@"RunLoop It's time to rest ");
                break;
            case kCFRunLoopAfterWaiting:
                NSLog(@"RunLoop Wake up ");
                break;
            case kCFRunLoopExit:
                NSLog(@"RunLoop Out of the ");
                break;

            default:
                break;
        }
    });

    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);  //  Add monitor , The key !
    CFRelease(observer); //  Release 
}

Here to RunLoop Created an observer , The viewer's callback print RunLoop Logic in , There's another one Timer every other 1.0 Second trigger . give the result as follows :

// 23:26:30 RunLoop Wake up 
// 23:26:30 ---- timer fired ----
// 23:26:30 RunLoop To deal with Timers 了 
// 23:26:30 RunLoop To deal with Sources 了 
// 23:26:30 RunLoop It's time to rest 
// 23:26:31 RunLoop Wake up 
// 23:26:31 ---- timer fired ----
// 23:26:31 RunLoop To deal with Timers 了 
// 23:26:31 RunLoop To deal with Sources 了 
// 23:26:31 RunLoop It's time to rest 

You can see ,Timer When it comes to triggering , awakened RunLoop,RunLoop Wake up and deal with it Timer, Yes Timer Methods ( Print ---- timer fired ----), then RunLoop Back to the beginning of the loop , Inform the observer to deal with Timers and Sources 了 , It turns out there's nothing to deal with , Then I went to have a rest , So circular ... Basically consistent with the above logic .

Here is an introduction RunLoop Application :

Create a resident thread

First, we create an inheritance from NSThread Class BZThread, Used to print information about destruction , And then in viewDidLoad Create a thread :

@interface BZThread : NSThread
@end
  
@implementation BZThread
- (void)dealloc {
    NSLog(@"BZThread is dealloced");
}
@end

@interface ViewController ()

@property NSThread *thread;

@end

@implementation ViewController


- (void)viewDidLoad {
    [super viewDidLoad];
    NSThread *thread = [[BZThread alloc] initWithTarget:self selector:@selector(threadTest) object:nil];
    self.thread = thread;
    [self.thread start];
}

- (void)threadTest {
    NSLog(@"thread is created");
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [self performSelector:@selector(doSomethingInThread) onThread:self.thread withObject:nil waitUntilDone:NO];
}

- (void)doSomethingInThread {
    NSLog(@"doSomethingInThread is fired");
  
}

@end

// BZThread is created

We found that , Threads are created , Also by ViewControlelr Hold on to ( Not immediately destroyed ), But there is no response when we execute methods in this thread , This shows that the thread has RunLoop It doesn't work .

The solution is in this thread method , For this thread RunLoop Create a Mode:

- (void)threadTest {
    [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] run];
    NSLog(@"thread is created");
}

Click on the screen , We will execute the thread method :

// doSomethingInThread is fired

This is because , Although a thread corresponds to a RunLoop, But a RunLoop At least one is needed Mode, To run , By default, the main thread has Mode 了 , New threads require us to create new ones manually Mode.

Finally, I'd like to introduce a RunLoop Application :

Test for carton :

If RunLoop The thread of , The execution time of the method before entering sleep is too long to enter sleep , Or when the thread wakes up, it takes too long to receive messages and cannot go to the next step , The thread is blocked . If this thread is the main thread , What it shows is that there is a carton .

How to check for stuck ? You need to create a persistent child thread to monitor the main thread's RunLoop state . Once you find that before you go to sleep state , Or wake up state , There has been no change within the set time threshold , It can be judged as stuck . Next , We can dump Information out of the stack , Which method is too long to carry out .

版权声明
本文为[osc_7dn4hojn]所创,转载请带上原文链接,感谢