IOS list performance optimization - picture decoding performance optimization

preface

Why do I need to decode pictures?

In fact, both JPEG and PNG pictures are compressed bitmap graphics format. But PNG pictures are lossless compression and support alpha channel, while JPEG Pictures are lossy compression, and 0-100% can be specified Therefore, the original pixel data of the picture must be obtained before the picture in the disk is rendered to the screen, and then the subsequent drawing operation can be performed. This is why the picture needs to be decompressed. See more about it iOS Image format problem and performance optimization in IOS iOS development: image format and performance optimization

1. How many cards are there in picture decoding?

The test method is relatively simple. Pictures can be displayed in a tableView. Pictures are 10 pictures that have been placed locally, and each picture is greater than 1MB

The code is as follows:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    BannerTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"BannerTableViewCell" forIndexPath:indexPath];
    
    // Get picture
    NSInteger index = 0;
    index = indexPath.row%10;
    NSString *imageName = [NSString stringWithFormat:@"backImage%ld",(long)index];
    //UIImage *image = [UIImage imageNamed:imageName];
    NSString *path = [[NSBundle mainBundle] pathForResource:imageName ofType:@"jpg"];
    UIImage *image = [UIImage imageWithContentsOfFile:path];
    cell.contentImageView.image = image; 
    return cell;
}

Careful students may have noticed that I wrote two ways to load pictures in the code.

One is UIImage imageNamed:imageName

One is UIImage imageWithContentsOfFile:path

Later, I will explain why we need to compare the two loading methods. Let's start with the loading results.

1> Use UIImage imageWithContentsOfFile:path

image.png

2> Use UIImage imageNamed:imageName

image.png

Both methods actually slide for one minute. It can be clearly seen that the frames of both loading methods are very low at first, but imageNamed:

The number of frames will recover to 60 soon, but the use of imageWithContentsOfFile: will always get stuck because of the use of imageNamed:

Images will be cached, but imageWithContentsOfFile: will not, and imageWithContentsOfFile:

There is obvious Caton and frame loss. From the curve, we can clearly see the difference between the two methods.

Let's explain the two loading methods we use. Use imageWithContentsOfFile:

In fact, it simulates the process of downloading pictures from the network to the local and then loading display pictures from the local. imageNamed: simulates the process of downloading pictures from Assets.xcassets

When loading pictures in, it is obvious that Apple has optimized the loading of pictures from Assets.xcassets.

2. How to optimize the picture decoding part

The solution is simple:

The decoding process can be directly placed in the sub thread. After decoding, the image can be assigned to imageView.image in the main thread and cached. The next time the same image is found again, it can be read directly in the cache.

Does this process sound familiar? Yes, this process has been implemented by many third-party libraries, the most famous of which is SDWebImage. The decoding method of SDWebImage is decodedImageWithImage, which uses CGContextDrawImage. Interested partners can take time to have a look. I won't repeat it here, but directly optimize the code:

    [self queryImageCache:imageName block:^(UIImage *image) {
        cell.contentImageView.image = image;
    }];
- (void)queryImageCache:(NSString *)filename block:(void(^)(UIImage *image))block
{
    //Fetch from the memory. If you don't get it, read the file directly and cache it
    UIImage *image = [self.memCache objectForKey:filename];
    if(image)
    {
        dispatch_async(dispatch_get_main_queue(), ^{
            if(block)
                block(image);
        });
    }
    else
    {
        //Put the decompression operation into the child thread
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSString *path = [[NSBundle mainBundle] pathForResource:filename ofType:@"jpg"];
            UIImage *image = [UIImage imageWithContentsOfFile:path];
            
            image = [UIImage decodedImageWithImage:image];
            [self.memCache setObject:image forKey:filename];
            // Synchronous main thread
            dispatch_async(dispatch_get_main_queue(), ^{
                if(block)
                    block(image);
            });
       });
    }
}

After experimenting with the above methods, repeat the previous method to check the FPS and CPU usage

image.png

name

FPS (average)

CPU (average)

Experimental time

imageWithContentsOfFile:

47.8

28%

1min

imageNamed:

58.8

10%

1min3

After optimization

59.9

7%

1min9

Posted by jblack on Mon, 22 Nov 2021 09:21:56 -0800