Meng force

Let’s talk about the following

1 Runloop Overview 2 Runloop and Timer 3 Runloop Resident thread 4 Runloop performance optimization


Runloop Basic summary

  • Runloop is a do… The while loop
    function loop(a) {
      initialize();
      do {
          var message = get_next_message();
          process_message(message);
      } while(message ! = quit); }Copy the code
  • Only the main thread has Runloop enabled by default; child threads need to have Runloop enabled manually
  • There are five runloopsmode, of which three are for our use
    NSDefaultRunLoopModeThe default modeUITrackingRunLoopModeScrollView goes into the mode that it goes into when it dragsNSRunLoopCommonModesPlaceholder modeCopy the code
  • Internal implementation principle (picture found online)




Internal implementation principle

  • Runloop draws all the points on the screen in one loop


Timer Runloop –

Without further ado, go straight to the code and analyze the problem.

    NSTimer * timer = [NSTimer timerWithTimeInterval:1.f repeats:YES block:^(NSTimer * _Nonnull timer) {
        NSLog(@"%s",__func__);
    }];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];Copy the code
  • Little knowledge:
    [NSRunLoop currentRunLoop]
    [NSRunLoop mainRunLoop]The Runloop obtained above is the same in the main thread, but different in the child thread.[NSRunLoop mainRunLoop]Represents the Runloop object that gets the main thread[NSRunLoop currentRunLoop]Represents a Runloop object that gets the current threadCopy the code

    Running:





Mode problem

Runloop will enter UITrackingRunLoopMode when scrollView is dragged, and will return to NSDefaultRunLoopMode when scrollView is pulled out. NSRunLoopCommonModes placeholder mode is required. After switching modes, look at the picture.

    NSTimer * timer = [NSTimer timerWithTimeInterval:1.f repeats:YES block:^(NSTimer * _Nonnull timer) {
        static int count = 0;
        NSLog(@"%s - %d",__func__,count++);
    }];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];Copy the code

Running:





Placeholder mode


Runloop is a resident thread

In project development we usually run the time-consuming operator in a child thread, so we continue to simulate the time-consuming operation in the code above.

    NSTimer * timer = [NSTimer timerWithTimeInterval:1.f repeats:YES block:^(NSTimer * _Nonnull timer) {
        static int count = 0;
        [NSThread sleepForTimeInterval:1];
        // Rest for one second to simulate time-consuming operation
        NSLog(@"%s - %d",__func__,count++);
    }];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];Copy the code
  • At this time we are not in the child thread inside the processing, through the experimental effect that will be stuck. Effect:




Simulating time-consuming operation

And then you might write code that looks something like this.

    dispatch_async(dispatch_get_global_queue(0.0), ^ {NSTimer * timer = [NSTimer timerWithTimeInterval:1.f repeats:YES block:^(NSTimer * _Nonnull timer) {
            static int count = 0;
            [NSThread sleepForTimeInterval:1];
            // Rest for one second to simulate time-consuming operation
            NSLog(@"%s - %d",__func__,count++);
        }];
        [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    });Copy the code

And then you realize, there’s nothing, it’s not working… Analysis problem:

When you enter the block, you create the timer, and you add the timer to the runloop, but the important thing is that the runloop doesn’t run automatically, you have to run it manually, because there’s no runloop running, so the timer is released, So there was nothing.

Since we know the reason is not running Runloop, can’t we just run it ourselves?

    dispatch_async(dispatch_get_global_queue(0.0), ^ {NSTimer * timer = [NSTimer timerWithTimeInterval:1.f repeats:YES block:^(NSTimer * _Nonnull timer) {
            static int count = 0;
            [NSThread sleepForTimeInterval:1];
            // Rest for one second to simulate time-consuming operation
            NSLog(@"%s - %d",__func__,count++);
        }];
        [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

        // Child threads need to manually enable Runloop
        [[NSRunLoop currentRunLoop] run];
    });Copy the code
  • The little knowledge
      [[NSRunLoop currentRunLoop] run]; Run the runloop[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; Specifies runloop in the specified mode, set the start time, success will return YES[[NSRunLoop currentRunLoop] runUntilDate:[NSDate distantFuture]]; Run runloop until a certain timeCopy the code

Operation effect:





runloop-timer4.gif

This way we can implement time-consuming operations in the child thread and resident thread.


Runloop performance optimization

Case: There are multiple ImageViews in the tableView Cell, and large images are loaded at the same time, resulting in UI lag. Use Runloop to add one image at a time. Tools: Here we need to use the CFRunloop implementation process:

2. Listen to the Runloop (CFRunloopObserver) 3. Take one load image from the task array and execute it (execute block code)

Listening to the Runloop

// Add a runloop listener
- (void)addRunloopObserver{

    // Get the current Runloop ref - pointer
    CFRunLoopRef current =  CFRunLoopGetCurrent(a);// Define a RunloopObserver
    CFRunLoopObserverRef defaultModeObserver;

    / / context
    /* typedef struct { CFIndex version; // Version number long void * info; Const void *(*retain)(const void *info); // fill in &cfretain void (*release)(const void *info); // fill in &cgfrelease CFStringRef (*copyDescription)(const void *info); //NULL } CFRunLoopObserverContext; * /
    CFRunLoopObserverContext context = {
        0,
        (__bridge void(*)self),
        &CFRetain,
        &CFRelease.NULL
    };

    NULL 2 mode kCFRunLoopEntry = (1UL << 0), kCFRunLoopBeforeTimers = (1UL << 1), kCFRunLoopBeforeSources = (1UL << 2), kCFRunLoopBeforeWaiting = (1UL << 5), kCFRunLoopAfterWaiting = (1UL << 6), kCFRunLoopExit = (1UL << 7), KCFRunLoopAllActivities = 0x0FFFFFFFU 3 Repeat - YES 4 nil or NSIntegerMax - 999 5 Callback 6 context */
    // Create an observer
    defaultModeObserver = CFRunLoopObserverCreate(NULL,
                                                  kCFRunLoopBeforeWaiting, YES.NSIntegerMax - 999,
                                                  &Callback,
                                                  &context);

    // Add the observer of the current runloop
    CFRunLoopAddObserver(current, defaultModeObserver, kCFRunLoopDefaultMode);

    / / release
    CFRelease(defaultModeObserver);
}Copy the code


We want to implement the callback method

static void Callback(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){

    // Bridge to the current object via info
    PQRunloop * runloop = (__bridge PQRunloop *)info;

    // If there is no task, return directly
    if (runloop.tasks.count == 0) {
        return;
    }

    BOOL result = NO;
    while (result == NO && runloop.tasks.count) {

        // Retrieve the task
        RunloopBlock unit = runloop.tasks.firstObject;

        // Execute the task
        result = unit();

        // Delete the task
        [runloop.tasks removeObjectAtIndex:0]; }} Because there is no way to call OC objects in C methods, there is one in contextvoidTo solve this problem, pass the OC object into the callback method.Copy the code


  • Using the above two methods, we can listen for the Runloop loop and what needs to be done each time. In this case, we only need to provide a method to add tasks, which is stored in an array.
//add task Adds a task
- (void)addTask:(RunloopBlock)unit withId:(id)key{
    // Add tasks to array
    [self.tasks addObject:unit];
    [self.taskKeys addObject:key];

    // Delete the image to ensure that the maximum number of images to be loaded is 18
    if (self.tasks.count > self.maxQueue) {
        [self.tasks removeObjectAtIndex:0];
        [self.taskKeys removeObjectAtIndex:0]; }}Copy the code
  • If you end up like this, you are wrong. If the Runloop does not wake up after execution, it will go to sleep, which means it will not do anything. Therefore, to keep the Runloop running, we need to create a timer, but it is best not to do anything inside the timer (1 time, 2 power consumption) so we also need:
self.timer = [NSTimer scheduledTimerWithTimeInterval:0.001 repeats:YES block:^(NSTimer * _Nonnull timer) { }];Copy the code
  • The little knowledge
    NULLNull pointernilAn empty objectCopy the code

Finally, we send you the operation chart:

  • The optimized




The optimized

  • Before optimization




Before optimization


conclusion

  • From the above two obvious can be seen, after optimization, the obvious fluency is improved, and the memory will be reduced when sliding, and the memory consumption will not be reduced when sliding.


The Demo address

If there is a little help for you, please kill me with star, and welcome to exchange.





funny