UITableView Performance Optimization – Intermediate Chapter

Time:2019-3-27

Honestly,UITableView performance optimizationThis topic, most often encountered in the interview, common answers such as:

  • Cell Reuse Mechanism
  • Cell height pre-calculation
  • Cache Cell Height
  • Fillet cutting
  • And so on.

UITableView Performance Optimization - Intermediate Chapter

Advanced

There’s a need recently, right?tableViewIntermediate optimization requirements

  1. RequirementtableViewWhen scrolling, which line of pictures will be loaded and displayed, and the pictures will not be loaded and displayed during scrolling.
  2. When the page jumps, cancel the image loading request of the current page;

Take the most common example of cell loading webImage:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
    if (!cell) {
        cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
    }
    
    DemoModel *model = self.datas[indexPath.row];
    cell.textLabel.text = model.text;
   
    [cell.imageView setYy_imageURL:[NSURL URLWithString:model.user.avatar_large]];
    
    return cell;
}

Explain the reuse mechanism of cell:

  • IfcellNot entering the interface (not yet visible), not calling- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPathTo render the cell, if set in the cellloadImageNo call;
  • And whencellCell rendering (either init or from the multiplexing pool) is performed when entering the interface.

Explain the YY Web Image mechanism:

  • InternalYYCachePictures are cached tokey:valueForm, here’s thekey = imageUrlValue = image image image downloaded
  • Judgment when readingYYCacheWhether there is the URL in it, or if there is, read the cached image data directly, and if not, use the image download logic and cache the image.

The problem lies in:

As mentioned above, if we have 20 lines in the cell line, when the page starts, we slide directly to the bottom and 20 cells enter the interface.- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPathTwenty calls were made, which did not conform.Demand 1Requirements

Terms of settlement:

  1. cellEach time rendered, judge the currenttableViewWhether it’s scrolling or not, yes, no images are loaded;
  2. cellAt the end of the scroll, get all that is visible in the current interfacecell
  3. stay2On the basis of that, let allcellRequest image data and display it
  • Step 1:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
    if (!cell) {
        cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
    }
    
    DemoModel *model = self.datas[indexPath.row];
    cell.textLabel.text = model.text;
   
    // No cell. imageView loadYYWebImage directly
    if (model.iconImage) {
        cell.imageView.image = model.iconImage;
    }else{
        cell.imageView.image = [UIImage imageNamed:@"placeholder"];
        
        // Core Judgment: Only when tableView is not scrolling, can the picture be downloaded and rendered.
        if (!tableView.dragging && !tableView.decelerating) {
            // Download Picture Data - and Cache
            [ImageDownload loadImageWithModel:model success:^{
                
                // The main thread refreshes the UI
                dispatch_async(dispatch_get_main_queue(), ^{
                    cell.imageView.image = model.iconImage;
                });
            }];
        }
}
  • Step 2:
- (void)p_loadImage{

    // Get inside the interface - all cell indexpaths
    NSArray *visableCellIndexPaths = self.tableView.indexPathsForVisibleRows;

    for (NSIndexPath *indexPath in visableCellIndexPaths) {

        DemoModel *model = self.datas[indexPath.row];

        if (model.iconImage) {
            continue;
        }

        UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];

        [ImageDownload loadImageWithModel:model success:^{
            // The main thread refreshes the UI
            dispatch_async(dispatch_get_main_queue(), ^{
 
                cell.imageView.image = model.iconImage;
            });
        }];
    }
}
  • Step 3:
// Hands are dragging controls all the time
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{

    [self p_loadImage];
}

// Hand release - use inertia - generated animation effect
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{

    if(!decelerate){
        // Direct Stop - No Animation
        [self p_loadImage];
    }else{
        // Inertial - will walk the `scrollViewDidEndDecelerating'method, where no settings are required
    }
}

dragging:returns YES if user has started scrolling. this may require some time and or distance to move to initiate dragging
It can be understood that the user is dragging the current view and scrolling (hand holding)

deceleratingreturns:returns YES if user isn't dragging (touch up) but scroll view is still moving
It can be understood that the user’s hand has been released, trying to scroll (whether inertial effect)

ScrollView drag-and-drop proxy method execution process:

UITableView Performance Optimization - Intermediate Chapter

The effect of the current code is as follows:
UITableView Performance Optimization - Intermediate Chapter

RunLoop small operation

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
    if (!cell) {
        cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
    }
    
    DemoModel *model = self.datas[indexPath.row];
    cell.textLabel.text = model.text;
   
    
    if (model.iconImage) {
        cell.imageView.image = model.iconImage;
    }else{
        cell.imageView.image = [UIImage imageNamed:@"placeholder"];

        /**
         Runloop - Scroll - Tracking Mode,
         - Default - defaultRunLoopMode
         ==> When scrolling, enter `tracking mode', and tasks under default mode will be suspended
         When the scroll stops - enter `default mode'- continue to perform tasks under `trackingmode' - for example, the loadImage here
         */
        [self performSelector:@selector(p_loadImgeWithIndexPath:)
                   withObject:indexPath
                   afterDelay:0.0
                      inModes:@[NSDefaultRunLoopMode]];
}

// Download the picture and render it on the cell
- (void)p_loadImgeWithIndexPath:(NSIndexPath *)indexPath{
    
    DemoModel *model = self.datas[indexPath.row];
    UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
    
    [ImageDownload loadImageWithModel:model success:^{
        // The main thread refreshes the UI
        dispatch_async(dispatch_get_main_queue(), ^{
            cell.imageView.image = model.iconImage;
        });
    }];
}

Effect anddemo.gifThe effect is the same.

Runloop – Introduction to two common modes:trackingMode && defaultRunLoopMode

  • Default – defaultRunLoopMode
  • When scrolling – tracking mode
  • When rolling, entertrackingModeLead todefaultModeThe next task is suspended, and when the scroll stops, => entersdefaultMode– Continue implementationdefaultModeNext task – like heredefaultMode

Big tips: Here, if RunLoop is used, it does not perform when scrolling.defaultModeBut as soon as the scroll ends, it’s in the cell beforep_loadImgeWithIndexPathIt will all be called again, resulting in a similarYYWebImageIn fact, the effect is not to meet the demand.

  • The code prompted to be invoked is as follows:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    
    // p_loadImgeWithIndexPath executes as soon as it enters `NSDefaultRunLoopMode'.
    [self performSelector:@selector(p_loadImgeWithIndexPath:)
               withObject:indexPath
               afterDelay:0.0
                  inModes:@[NSDefaultRunLoopMode]];
}

UITableView Performance Optimization - Intermediate Chapter

The effect is as good as above.

  • When scrolling, no pictures are loaded, and when scrolling ends, the pictures are loaded – Satisfaction
  • The end of the scroll, before the end of the scrollcellWill load pictures => not meet the requirements

Version rollback before Runloop-Git reset -- before hard runloop

Solution: Requirement 2. Cancel the image loading request of the current page when the page jumps;

- (void)p_loadImgeWithIndexPath:(NSIndexPath *)indexPath{
    
    DemoModel *model = self.datas[indexPath.row];
    
    // Save the operation currently being downloaded
    ImageDownload *manager = self.imageLoadDic[indexPath];
    if (!manager) {
        
        manager = [ImageDownload new];
        // Start Loading - Save to the Current Download Operations Dictionary
        [self.imageLoadDic setObject:manager forKey:indexPath];
    }
    
    [manager loadImageWithModel:model success:^{
        // The main thread refreshes the UI
        dispatch_async(dispatch_get_main_queue(), ^{
            UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
            cell.imageView.image = model.iconImage;
        });
        
        // Load Successfully - Remove from the saved current download operation dictionary
        [self.imageLoadDic removeObjectForKey:indexPath];
    }];
}

- (void)viewWillDisappear:(BOOL)animated{
    [super viewWillDisappear:animated];
  
    NSArray *loadImageManagers = [self.imageLoadDic allValues];
    // The current image download operation has been cancelled
    [loadImageManagers makeObjectsPerformSelector:@selector(cancelLoadImage)];
}


@implementation ImageDownload
- (void)cancelLoadImage{
    [_task cancel];
}
@end

Train of thought:

  1. Create a variable dictionary toindexPath:managerSave the current image download operation in the format
  2. Before each download, save the current download thread and remove it after successful download.
  3. stayviewWillDisappearWhen the current thread dictionary is taken out, all thread objects are traversed.cancelOperations to fulfill requirements

Outside: Presentation of interview questions

Recently, there has been a great deal of information about the layoffs of various Internet companies on the Internet, including all kinds of front-line companies. -) iOS is going into the cold winter ahead of time, so iOS whites can try to think about it.

Question: How to optimize the rounded corner performance of UITableView

Answer:

  1. Let the server pass round corner pictures directly;
  2. Bessel Cut Control Layer;
  3. YYWebImageFor example, you can download the picture first, then rounded the image, and then set it tocellUp display

Question: How does YYWebImage set rounded corners? In the callback after download? If you cut it when download is completed, is the image in YYWebImage cache the original image or the rounded corner image? (Finally, it’s 3!! )

Answer: If you download it, cut the rounded corners in the callback. In fact, the cached image is the original image, which is equal to the rectangular image in the cache every time you take it out.setThey have to do cutting operations.

Question: Is there a solution?

Answer: Actually, there are, in a nutshell.YYWebImageIt can be split into two parts. By default, the callback we get is gone.download && cacheHere we take one more step and take it out.cacheShould beurlThe image corresponding to the path can be cut into rounded corners and stored in cache to ensure that every time you get it, it will be the same.cachaCut-out rounded corners

Details are as follows:

NSString *path = [[UIApplication sharedApplication].cachesPath stringByAppendingPathComponent:@"weibo.avatar"];
YYImageCache *cache = [[YYImageCache alloc] initWithPath:path];
manager = [[YYWebImageManager alloc] initWithCache:cache queue:[YYWebImageManager sharedManager].queue];
manager.sharedTransformBlock = ^(UIImage *image, NSURL *url) {
    if (!image) return image;
    return [image imageByRoundCornerRadius:100]; // a large value
};

SDWebImageSimilarly, it exposes a way to save pictures directly to disk without modifying the source code.

“Winner is coming.” If the interview happens to encounter the above problems, please call me Lei Feng.~
I sincerely hope you iOS partners can survive this winter?

Demo source code

Reference material

IOS Skills for Maintaining Smooth Interface

VVeboTableViewDemo

YYKitDemo

UIScrollView Practical Experience