The main idea of Catton’s solution

  • (1) Reduce CPU and GPU resource consumption as much as possible
  • (2) according to the brush rate of 60FPS, there will be a VSync signal every 16ms

From the CPU and GPU two aspects of the start of the stuck optimization

Reduce CPU load

As we know, CPU is mainly responsible for fast scheduling tasks and a large amount of computing work, so it is the direction that should be considered for optimization to reduce CPU computation in the process of tableView fast scrolling. Here are three ways to minimize CPU computation:

The height of the cell is calculated in advance and cached in the corresponding data source model

We know that in tableView’s proxy callback method, it calls the method that returns the height of the cell, and then returns the method that instantiates the cell. We can calculate the cell height in advance and cache it in the data source model when returning the cell height.

Minimize storyboards and XIBs as much as possible

If you know that xiB or storyboard is an XML file by Interface, adding and deleting controls must involve an encode/decode process, which increases CPU computation. Also avoid bloated XIB files, which load the layout in the main thread. When a custom View or XIB file is used, the XIB load will load all the content in the XIB. If some controls in the XIB are not used, this can cause some waste of resources.

For example, use code only layout, use navigation constraint layout, or calculate layout manually. I have completely abandoned storyboard and XIB.

The slide process minimizes relayout

Automatic layout is simply adding constraints to a control, which ultimately translates to a frame. Therefore, to meet business requirements, if the layers are complex, the automatic layout constraints should be reduced as much as possible, and the manual layout should be changed. A large number of overlapping constraints will also increase CPU calculation.

For example, if the content is relatively fixed, you can write out the width and height of the UILabel and simply update its text content

According to the need to load

When sliding the UITableView, load the corresponding content on demand

#pragma mark - UIScrollViewDelegate // Load on demand - If the target line differs from the current line by more than the specified number of lines, only specify 3 lines before and after the target scroll range. - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset { NSIndexPath *ip = [self.tableView indexPathForRowAtPoint:CGPointMake(0, targetContentOffset->y)]; NSIndexPath *cip = [[self.tableView indexPathsForVisibleRows] firstObject]; NSInteger skipCount = 8; NSLog(@"targetContentOffset = %f",targetContentOffset->y); NSLog(@"indexPathForRowAtPoint = %@",ip); NSLog(@"visibleRows = %@",[self.tableView indexPathsForVisibleRows]); If (LABS (cip. Row-ip. row) > skipCount) {// LABS - returns the absolute value of x Drag and drop stop sliding, the index of the cell group NSArray * temp = [self. The tableView indexPathsForRowsInRect: CGRectMake (0, targetContentOffset - > y, self.tableView.width, self.tableView.height)]; NSMutableArray *arrM = [NSMutableArray arrayWithArray:temp]; NSLog(@"temp = %@",temp); If (velocity. Y < 0) {NSIndexPath *indexPath = [temp lastObject]; If (indexPath. Row + 3 < self.datasource.count) {// the index that appears after the slide stops is still in the scope of the dataSource [arrM addObject:[NSIndexPath indexPathForRow:indexPath.row + 1 inSection:0]]; [arrM addObject:[NSIndexPath indexPathForRow:indexPath.row + 2 inSection:0]]; [arrM addObject:[NSIndexPath indexPathForRow:indexPath.row + 3 inSection:0]]; }} else {// slide down - load the previous data NSIndexPath *indexPath = [temp firstObject]; if (indexPath.row > 3) { [arrM addObject:[NSIndexPath indexPathForRow:indexPath.row - 3 inSection:0]]; [arrM addObject:[NSIndexPath indexPathForRow:indexPath.row - 2 inSection:0]]; [arrM addObject:[NSIndexPath indexPathForRow:indexPath.row - 1 inSection:0]]; } } [self.needLoadArray addObjectsFromArray:arrM]; }}Copy the code

Asynchronous rendering

When the view hierarchy is large, it can be drawn asynchronously, using UIGraphics to draw the content and then generate an image for presentation.

The implementation steps are as follows

  • Processing data source When we request data, we need to calculate the layout according to the data returned by the back end, and then draw.
  • Asynchronously rendering data, rendering content in the assigned data model

Delayed loading image

Runloop draws the UI on your screen each time. The main reason for this is that it is fast, but we don’t see how slow it is to draw a high definition image, which causes a lag. So we listen for the state of the runloop and draw the load image every time it is about to sleep, in the kCFRunLoopBeforeWaiting state.

- (void)addRunloopObserver {// Get the current runloop. // Get the current runloop. CFRunLoopRef runloop = CFRunLoopGetCurrent(); CFRunLoopObserverContext Context = {0, (__bridge void *)(self), &CFRetain, &CFRelease, NULL}; // Define an observer static CFRunLoopObserverRef defaultModeObsever; DefaultModeObsever = CFRunLoopObserverCreate(NULL, kCFRunLoopBeforeWaiting, Nsintegermax-999, &callback, // if the runloop is in NSDefaultRunLoopMode NSDefaultRunLoopMode (context); // Add the observer of the current RunLoop CFRunLoopAddObserver(RunLoop, defaultModeObsever, kCFRunLoopDefaultMode); Release CFRelease(defaultModeObsever); } static void Callback(CFRunLoopObserverRef Observer, CFRunLoopActivity activity, void *info) { DelayLoadImgViewController *vc = (__bridge DelayLoadImgViewController *)(info); If (vc.tasks.count == 0) {return; } BOOL result = NO; While (result == NO && vc.tasks.count) {NSLog(@" start loading task %d",vc.tasks.count); RunloopBlock unit = vc.tasks.firstObject; // Execute task result = unit(); [vc.tasks removeObjectAtIndex:0]; }Copy the code

Drop the load drawing image into the task

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { NewsModel *model = [self.dataSource objectAtIndex:indexPath.row]; DelayLoadImgCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId]; cell.selectionStyle = UITableViewCellSelectionStyleNone; cell.model = model; [self addTask:^BOOL{[cell drawImg]; return YES;}]; return cell; Void drawImg {if (_model.imgs. Count > 0) {__block float posX = 0; [_model.imgs enumerateObjectsUsingBlock:^(NSString *obj, NSUInteger idx, BOOL *stop) { UIImageView *imgView = [[UIImageView alloc] initWithFrame:CGRectMake(posX, 0, kImgViewWH, kImgViewWH)]; [imgView sd_setImageWithURL:[NSURL URLWithString:obj]]; imgView.layer.cornerRadius = 5; imgView.layer.masksToBounds = YES; [self.imgListView addSubview:imgView]; posX += (5 + kImgViewWH); if (idx >= 3) { *stop = YES; } }]; }}Copy the code
  • Listen for the runloop state and execute the code block that needs to be executed if the runloop is in kCFRunLoopBeforeWaiting
  • Every time we swipe, the image is not displayed because the runloop is not in kCFRunLoopBeforeWaiting state. When we stop dragging and sliding, the runloop is in the kCFRunLoopBeforeWaiting state and then loads the draw image.