Leads and ideas

Runloop is a loop that renders all the points on the screen each time. When we are making a table with many images, dragging the table will cause a noticeable lag. This is because every time we drag the table, the runloop will load the rendering once, so it is very time-consuming. The idea is that we load it in batches, one image at a time, one image at a time. The demo is released at the bottom of the article.

The implementation process

Each time the code needs to load the cell, wrap it into a task (block) and put it in an array.

//MARK: addTask -(void)addTask:(RunloopBlock)unit withKey:(id)key{[self.tasks addObject:unit]; [self.tasksKeys addObject:key]; // Don't waste time loading tasks that aren't shown beforeif(self.tasks.count > self.max) { [self.tasks removeObjectAtIndex:0]; [self.tasksKeys removeObjectAtIndex:0]; }}Copy the code

We then write a method to listen for the runloop, taking only one task from the array at a time. We need to use the CFRunloop in the CoreFoundation framework and find its observers, each of which represents a state of the Runloop.

KCFRunLoopEntry enters a loop kCFRunLoopBeforeTimers Process timers before kCFRunLoopBeforeSources process sources before kCFRunLoopBeforeWaiting Going to sleep kCFRunLoopAfterWaiting Going to process events from sleep kCFRunLoopExit Exit kCFRunLoopAllActivities All eventsCopy the code

With that in mind, start listening for runloop and need to start the observer in ViewDidLoad.

// Add a listener -(void)addRunloopObserver{// Get the current RunLoop CFRunLoopRef RunLoop = CFRunLoopGetCurrent(); CFRunLoopObserverContext context = {0, (__bridge *)(self), &cfretain, &cfrelease, NULL}; // Define an observer static CFRunLoopObserverRef defaultModeObsever; DefaultModeObsever = CFRunLoopObserverCreate(NULL, kCFRunLoopBeforeWaiting, YES, nsintegermax-999, &Callback, &context ); // Add the observer of the current RunLoop CFRunLoopAddObserver(RunLoop, defaultModeObsever, kCFRunLoopDefaultMode); Release CFRelease(defaultModeObsever); }Copy the code

Since runloop falls asleep at the end of each trip, we need to keep it awake and executing, using an NSTimer bound to an event every 0.1 second. The event doesn’t need to do anything except keep the runloop awake all the time. Next, you need to define the callback function above and only call it once per runloop.

//MARK: Static void Callback(CFRunLoopObserverRef Observer, CFRunLoopObserverRef Observer, CFRunLoopObserverRef Observer, CFRunLoopActivity activity, void *info){ ViewController * vc = (__bridge ViewController *)(info);if (vc.tasks.count == 0) {
        return;
    }
    BOOL result = NO;
    while(result == NO && vc.tasks.count) {// Get the task RunloopBlock unit = vc.tasks.firstObject; // Execute task result = unit(); // Remove the first task [vc.tasks removeObjectAtIndex:0]; [vc.tasksKeys removeObjectAtIndex:0] }}Copy the code

The basic effect we want is already there. My little fart demo from abroad