Recently, I met a problem. There was a controller that crashed as soon as I entered, and the phone was very hot. I ran with the instrument and found that the memory increased by hundreds of megabytes. As shown in figure:

As can be seen from the figure, the culprit of memory explosion is YYImage. Further locate the problem, as shown in the figure:

Is now very clear know, what are the specific code cause memory surge, “YYCGImageCreateDecodeCopy”, this method is mainly for the decompression operation; Similarly, the same situation occurs when SDWebImage is replaced. For some reasons, the following analysis will take SDWebImage as an example.

Paste the code for the SDWebImage call first:

[self sd_setImageWithURL:[NSURL URLWithString:imageUrl] placeholderImage:[UIImage imageNamed:@"defaulImage"] options:SDWebImageProgressiveDownload completed:nil];

Copy the code

Instrument Analysis diagram:

Code location:

Again, there is a memory surge during decompression, but why? First of all, I thought that “Create” must correspond to a “Release”, so I carefully looked at each line of code, whether YYImage or SDWebImage, and strictly followed this rule. Since they all had Release, it was not a memory leak, it should be in the process of executing these lines of code. Memory consumption generated. But how did it cost so much? A picture is only a few megabytes in size, so what’s the point of decompression? I from “H_ WeiHua bo le home” found in a blog know compressed existence (http://blog.163.com/huang1988519@126/blog/static/737875752013101803137445/)

There is also a slight lag when images are finished loading or loaded locally. Because UIKit only does extra lazy initialization and expensive decoding when displaying or drawing. The following code snippet is extracted from the background thread into a suitable format so that the system does not have to do additional transformations. It is then displayed on the main threadCopy the code

I wonder again, since it is for optimization, why is it counterproductive? A mystery, I finally found it in the SDWebImage issues related discussion: https://github.com/rs/SDWebImage/issues/538 one harishkashyap great god is so answer:

harishkashyap commented on Dec 23, 2014 Its the memory issue again. decodedImageWithImage takes up huge memory and causes the app to crash. I have added an option to put this off in the library but defaulting to YES so there aren’t any breaking changes. If you put off the decodeImageWithImage method in both image cache and image downloader then you shouldn’t be seeing the VM: CG Raster data on the top consuming lots of memory

decodeImageWithImage is supposed to decompress images and cache them so the loading on tableviews/collectionviews become better. However, with large set of images being loaded, the experience worsened and the memory of uncompressed images even with thumbnails can consume GBs of memory. Putting this off only improved performance.

[[SDImageCache sharedImageCache] setShouldDecompressImages:NO];
[[SDWebImageDownloader sharedDownloader] setShouldDecompressImages:NO];

Copy the code

https://github.com/harishkashyap/SDWebImage/tree/fix-memory-issues

Mentioned the great god, decodeImageWithImage this method used to unzip and cached images, in order to make sure tableviews/collectionviews interaction more fluent, but if the load is high resolution images, backfire, may cause on G memory consumption. For high resolution images, decompression should be disabled, and the relevant code processing is as follows:

[[SDImageCache sharedImageCache] setShouldDecompressImages:NO];
[[SDWebImageDownloader sharedDownloader] setShouldDecompressImages:NO];

Copy the code

The great god said yes, but why? I checked the official Apple documentation for the CGBitmapContextCreate function:

The parameters in the red box caught my attention:

BitsPerComponent represents the number of bits of each component in each pixel stored in memory; BytesPerRow represents the number of bytes in each line of the bitmap stored in memory.Copy the code

My guess is that each pixel is allocated a space to store the relevant values in the decompression operation, so a higher resolution image means more pixels, which means more space to allocate! Therefore, for high resolution images, decompression can cause memory surge. Even for a few megabytes of images, decompression can consume gigabytes of memory! In this case, I decided to follow the method of harishkashyap and directly make the place where the high resolution images are downloaded, no decompression operation! All aspects of the project that are involved in hd have been encapsulated, so it’s a lot easier. In order to ensure that the encapsulated class has no impact on the outside world, I only disable decompression when the encapsulated class is called, and then restore the original setting after the call. In this way, it can not only ensure that the high-resolution images do not crash, but also ensure that ordinary images can still be optimized through decompression in other places. Since it is a controller that is encapsulated, I decided to disable uncompression in the loadView method of the controller and restore the original setting in the delloc method:

static BOOL SDImageCacheOldShouldDecompressImages = YES;
static BOOL SDImagedownloderOldShouldDecompressImages = YES;

Copy the code

2. Save the original Settings in loadView and disable decompression:

SDImageCache *canche = [SDImageCache sharedImageCache];
SDImageCacheOldShouldDecompressImages = canche.shouldDecompressImages;
canche.shouldDecompressImages = NO;

SDWebImageDownloader *downloder = [SDWebImageDownloader sharedDownloader];
SDImagedownloderOldShouldDecompressImages = downloder.shouldDecompressImages;
downloder.shouldDecompressImages = NO;

Copy the code

3. Restore the original Settings in dealloc:

- (void)dealloc {
    SDImageCache *canche = [SDImageCache sharedImageCache];
    canche.shouldDecompressImages = SDImageCacheOldShouldDecompressImages;
    
    SDWebImageDownloader *downloder = [SDWebImageDownloader sharedDownloader];
    downloder.shouldDecompressImages = SDImagedownloderOldShouldDecompressImages;
}

Copy the code

Instrument run again, the method is effective, memory completely down, as shown in the picture:

Of course, you can also set other parameters of SDWebImage, such as whether to cache to memory and maximum memory cache limit, to ensure memory security:

Whether shouldCacheImagesInMemory cache to the highest limit maxMemoryCost in-memory cache memoryCopy the code

Extra: Apple has officially released a demo for downloading large hd images with very low memory consumption. Interested friends can also look at: https://developer.apple.com/library/ios/samplecode/LargeImageDownsizing/Introduction/Intro.html


Remarks YY(20160921) : In view of the previous analysis, it is found that the reasons for flash retreat of YY and SD are the same. Finally, this paper provides a detailed solution for SD, while YY is overstated (reason: SD supported IPV6 at that time, but YY still used the abandoned NSURLConnection; In addition, based on the idea of drawing inferences from one example, I think those who need to use YY can use my ideas to solve the problems of YY. After all, the problems are the same. Later, I saw in the comments that many friends asked about the solution of YY, so I analyzed YY again and found that the decompression of YY was uncontrollable in many places. For example, many functions of YY will go through the decompression step, and in some functions, whether decompression is directly transferred is YES. For the time being, I have not come up with a particularly good solution, so when I have time, I will study YY carefully. I suggest that friends who need to download HD images can use SD. I will update this article when I find a better and rigorous solution to YY. The title did not do worthy of the name, kui everyone!