Detail the performance optimization of UITableView in iOS

  • 2021-12-05 07:34:46
  • OfStack

1. Introduction

In the development of iOS, UITableView may be one of the UI controls that we deal with most at ordinary times, and its importance is self-evident. The same is true of Android. ListView and UITableView in Android are a control with the same function, but UITableView in iOS is more powerful by 1 point, so the reason is not mentioned. If you have learned Android, you will know that UITableView in iOS is very simple to use, which is one of the reasons why Feng Ge likes iOS better than Android. Today's research content is the optimization of UITableView.

Before you begin, can you name several optimizable items of UITableView? cell reuse (Android is often called ListView reuse. In fact, reuse and reuse all mean one. Because Feng Ge has done Android before, sometimes I often say "reuse", and later Wan 1 said "reuse", which everyone knows means "reuse")! Besides cell reuse?

2. Performance optimization of UITableView

1. cell multiplexing

Reuse is very simple, which is probably the most familiar optimization content for all iOS developers, as follows:


-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
 static NSString *Identifier = @"cell";
 UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ID];
 if (!cell) {
 cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:ID];
 }
 
 return cell;
}

But is this reuse perfect?

We often pay attention to binding data for every cell in cellForRowAtIndexPath:. In fact, cell has not been displayed when calling cellForRowAtIndexPath:. In order to improve efficiency, we should put the data binding operation after cell is displayed, and we can bind data in tableView: willDisplayCell: forRowAtIndexPath: (hereinafter referred to as willDisplayCell).

Note that willDisplayCell will be called before cell is displayed in tableview, and the cell instance has been generated at this time, so the structure of cell cannot be changed, only one attribute of UI on cell can be changed (for example, the content of label, etc.).

2. Calculation of cell height

Here, we are divided into two types of cell, one is cell with fixed height, and the other is cell with dynamic height.

(1) For cell with fixed height, the following methods should be adopted:


self.tableView.rowHeight = 88;

This method specifies that all cell heights are 88 tableview, and the default value for rowHeight is 44, so an empty TableView will appear like this. For the fixed height cell, the above method is directly used to give the height, and tableView: heightForRowAtIndexPath: is not needed to save unnecessary calculation and overhead.

(2) cell with dynamic height

We need a proxy to implement it to give the height:


- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
 // return xxx
}

After this proxy method is implemented, the above rowHeight settings will become invalid. In this method, we need to improve the computational efficiency of cell to save time.

Since the concept of self-sizing cell came into being after iOS8, cell can calculate its own height. To use self-sizing cell, the following three conditions need to be met:

(1) Use Autolayout for UI layout constraints (require cell. contentView all four sides to have constraint relationships with internal elements).

(2) Specify the default value for the estimatedRowHeight property of TableView.

(3) Specify the rowHeight attribute of TableView as UITableViewAutomaticDimension.


- (void)viewDidload {
 self.myTableView.estimatedRowHeight = 44.0;
 self.myTableView.rowHeight = UITableViewAutomaticDimension;
}

In addition to improving the computational efficiency of cell heights, we need to cache the calculated heights, and there is no need to calculate the calculated heights for the second time.

3. Render

In order to keep the TableView flowing, the cell must be rendered quickly when sliding fast. Therefore, the rendering speed of cell must be fast. How to improve the rendering speed of cell?

(1) When there is an image, pre-render the image, draw it once in bitmap context, export it to UIImage object, and then draw it to the screen, which will greatly improve the rendering speed. For specific content, you can find the relevant information of "using pre-rendering to accelerate the display of iOS images".

(2) One of the best operations for rendering is blending (blending), so we don't use transparent background, set opaque value of cell to Yes, don't use clearColor for background color, try not to use shadow gradient, etc.

(3) Since the blending operation is performed using GPU, we can render using CPU, so the blending operation is no longer performed. You can customize the drawing in the drawRect method of UIView.

4. Reduce the number of views

When we add system controls on cell, in fact, the system will call the underlying interface to draw. When adding a large number of controls, it will consume a lot of resources and affect the rendering performance. When you use the default UITableViewCell and add controls on top of its ContentView, it consumes a lot of performance. So the best approach is to inherit the UITableViewCell and override the drawRect method.

5. Reduce redundant drawing operations

When implementing the drawRect method, its parameter rect is the area we need to draw, and we don't need to draw the area outside the scope of rect, otherwise it will consume considerable resources.

6. Do not dynamically add subView to cell

When initializing cell, add everything that needs to be displayed, and then set hide attributes to show and hide as needed.

7. Asynchronous UI, do not block the main thread

We often see such a phenomenon, that is, when loading, the whole page is stuck, and it is useless to point it, as if it crashed. The reason is that the main thread is blocked. Therefore, for the request of network data or the loading of pictures, we can start multithreading and put time-consuming operations into sub-threads for asynchronous operations. Maybe every iOS developer knows this knowledge, so there is no need to talk about it.

8. Load the corresponding contents as needed when sliding

If the difference between the target line and the current line exceeds the specified number of lines, only 3 lines are specified to load before and after the target scroll range.


- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset{
 NSIndexPath *ip = [self indexPathForRowAtPoint:CGPointMake(0, targetContentOffset->y)];
 NSIndexPath *cip = [[self indexPathsForVisibleRows] firstObject];
 NSInteger skipCount = 8;
 if (labs(cip.row-ip.row)>skipCount) {
  NSArray *temp = [self indexPathsForRowsInRect:CGRectMake(0, targetContentOffset->y, self.width, self.height)];
  NSMutableArray *arr = [NSMutableArray arrayWithArray:temp];
  if (velocity.y<0) {
   NSIndexPath *indexPath = [temp lastObject];
   if (indexPath.row+33) {
    [arr addObject:[NSIndexPath indexPathForRow:indexPath.row-3 inSection:0]];
    [arr addObject:[NSIndexPath indexPathForRow:indexPath.row-2 inSection:0]];
    [arr addObject:[NSIndexPath indexPathForRow:indexPath.row-1 inSection:0]];
   }
  }
  [needLoadArr addObjectsFromArray:arr];
 }
}

Remember to add judgment in tableView: cellForRowAtIndexPath: Method:


if (needLoadArr.count>0&&[needLoadArr indexOfObject:indexPath]==NSNotFound) {
 [cell clear];
 return;
}

When sliding quickly, only the cell in the target range is loaded, so that it is loaded on demand (with SDWebImage), which greatly improves the smoothness.

Summarize


Related articles: