https://www.jianshu.com/p/8662b2efbb23
In recent work, the memory footprint of APP has been optimized to reduce the number of memory footprint, this will sum up the experience and share, we also welcome to communicate.
In the process of optimization, the main use of the following tools:
- Instruments and Allocations
This tool can show the actual memory footprint applications, and can be sorted by size. As long as we identify those with high occupancy, analyze the causes and find appropriate solutions. - MLeaksFinder
Tencent open source tool to find a memory leak, you can use the APP process, instant reminder of a memory leak occurs. - Xcode's Memory Graph
this tool to find memory leaks aspects can complement MLeaksFinder for circulation among the analyzed object reference relationship.
Also by analyzing the Live Objects some point, you can analyze what is unreasonable.
To conclude, there are several reasons leading to high memory usage:
- We had used unreasonable API
- The image is too large to download network
- Caching third-party libraries
- Masonry Layout Framework
- No need permanent memory objects, implemented as a permanent memory
- Redundant fields in the data model
- Memory Leak
The following discussion from several aspects.
1. unreasonable API
1.1 For use only once or very low frequency of use of the resources of the big picture, using [UIImage imageNamed:] loading method
Pictures loaded, there are two ways, one is [UIImage imageNamed:]
, the system cache loaded, and there is no API can be cleaned; the other is [UIImage imageWithContentsOfFile:]
or [[UIImage alloc] initWithContentsOfFile:]
the system will not cache processing, when the picture is no longer referenced, it occupies the memory will be completely released.
Based on the above characteristics, for use only once or very low frequency of use of the resources of the big picture, you should use the latter. When using the latter, pay attention to the picture can not be put in Assets.
1.2 Some image itself is very suitable for stretching mechanism of FIG. 9, but no corresponding optimization
Image memory footprint is large, suitable for stretching treatment mechanism of FIG. 9 pictures, can cut out a little more than the actual size of the image, thereby substantially reducing the memory footprint. For example, the following picture:
The solid part is left between the two vertical lines, the design of FIG when cut, for which a small portion cut out as long as it. Then we can use the slicing feature of Xcode, set the picture which is not part of the stretch, which parts of the stretching. When the picture is loaded, or loaded in the normal way.
1.3 In the absence of the necessary cases, the use of -[UIColor colorWithPatternImage:]
this method
Projects have code uses UILabel, the background color of the label is set to a picture. In order to turn color pictures, using the above method. This method will refer to a loaded into memory in the picture, and then will create another image in memory, and memory footprint image is great.
Solution: In this scenario, it is reasonable to use UIButton, the picture is set to background. Although the use of UIButton generate two views will be more than UILabel, but from the image memory footprint compared, still totally worth it.
1.4 in the case where there is no need to use Core Graphics API, modifying a color of the object UIImage
Using this API, it will lead to additional generate an image in memory, large memory footprint. Reasonable approach is:
- Set tintColor property of UIView
- The picture is loaded in a way UIImageRenderingModeAlwaysTemplate
code examples:
view.tintColor = theColor;
UIImage *image = [[UIImage imageNamed:name] imageWithRenderingMode: UIImageRenderingModeAlwaysTemplate]
1.5 solid colors create a picture based on size is too large
Sometimes, we need to create a color-based UIImage, and used UIButton background images in different states. Because it is a solid color pictures, so we did not need to create and view the same image size, only need to create an image width and height are 1px size is enough.
Code Example:
//外部应该调用此方法,创建出1px宽高的小图像
+ (UIImage*)createImageWithColor:(UIColor *)color { return [self createImageWithColor: color andSize: CGSizeMake(1, 1)]; } + (UIImage*)createImageWithColor:(UIColor*)color andSize:(CGSize)size { CGRect rect=CGRectMake(0,0, size.width, size.height); UIGraphicsBeginImageContext(rect.size); CGContextRef context = UIGraphicsGetCurrentContext(); CGContextSetFillColorWithColor(context, [color CGColor]); CGContextFillRect(context, rect); UIImage *theImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return theImage; }
1.6 Create a gradient image level, oversize
Some local projects based on color, use Core Graphics, create a horizontal direction from left to right gradient image in memory. Image size is the size of view, which in the view of some larger cases, caused no small memory overhead. @ 3x device on to a size of 400x60 view of an example, which is a memory overhead:
400. 3 * * *. 4. 3 * 60/1024 = 210KB.
But in fact on the image, if 400px width, height 1px, fully achieve the same effects, rather it is only the memory overhead:
400 * *. 1. 4/1024 = 1.56KB
From 1.7 UIView subclass defined by the drawRect: method to draw
Custom drawRect APP will consume a lot of memory, the larger view, consume more. Its memory consumption is calculated as:
memory consumption = (width * scale * height * scale * 4/1024/1024) MB
In almost all cases, drawing demand can be achieved through CAShapeLayer this weapon. CAShapeLayer on the CPU and memory footprint are two indicators after blasting drawRect :.
It has the following advantages:
- Rendering fast. CAShapeLayer using hardware acceleration, the same graphics will be used to draw a lot faster than the Core Graphics.
- Efficient use of memory. A CAShapeLayer do not like the ordinary boarding CALayer as to create a graphic, so no matter how big, will not take up too much memory.
- It will not be clipped boundary layer.
- Pixelation does not occur.
From 1.8 CALayer subclass defined by - (void) drawInContext: Method draw
And on a similar, try to use CAShapeLayer do draw.
1.9 UILabel oversized
If the size of a UILabel, greater than intrinsicContentSize
, it will cause unnecessary memory consumption. So, when the layout view, we should try to make it equal to the size of UILabel intrinsicContentSize
.
On this point, the reader can write a simple example program, then use the Instruments analysis tool, you can see Allocations in, Core Animation this one occupation will be significantly increased.
1.10 set the background color for the UILabel
If you set the background color is not clearColor, whiteColor, can cause memory overhead.
Therefore, once encountered this case, the structure can be converted into the view UIView+UILabel
, the background color is set to the UIView, but is only used to display text UILabel.
It is also possible, using the Instruments tool to verify by writing a sample program.
2. Download the image is too large
Almost all iOS applications, will use this framework to load network SDWebImage picture. Sometimes encountered loaded picture is too large, in this case, need to be analyzed according to the specific scene, using different solutions.
2.1 view large images can not be scaled
If the big picture is reasonable, then we can only do is to download the pictures deleted from the cache memory when the view is released. Sample code is as follows:
- (void)dealloc { for (NSString *imageUrl in self.datas) { NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL: [NSURL URLWithString: imageUrl]]; [[SDImageCache sharedImageCache] removeImageForKey: key fromDisk: NO withCompletion: nil]; } }
The code will allow a higher memory usage will only appear on a page, the page is returned once since then, the memory will return to normal.
2.2 Viewer small, then the picture should be scaled
If the view is used to display a small picture, and download a lot of pictures, then we should image to zoom process, and then save the picture scaled to SDWebImage memory cache.
Sample code is as follows:
//为UIImage添加如下分类方法:
- (UIImage*)aspectFillScaleToSize:(CGSize)newSize scale:(int)scale { if (CGSizeEqualToSize(self.size, newSize)) { return self; } CGRect scaledImageRect = CGRectZero; CGFloat aspectWidth = newSize.width / self.size.width; CGFloat aspectHeight = newSize.height / self.size.height; CGFloat aspectRatio = MAX(aspectWidth, aspectHeight); scaledImageRect.size.width = self.size.width * aspectRatio; scaledImageRect.size.height = self.size.height * aspectRatio; scaledImageRect.origin.x = (newSize.width - scaledImageRect.size.width) / 2.0f; scaledImageRect.origin.y = (newSize.height - scaledImageRect.size.height) / 2.0f; int finalScale = (0 == scale) ? [UIScreen mainScreen].scale : scale; UIGraphicsBeginImageContextWithOptions(newSize, NO, finalScale); [self drawInRect:scaledImageRect]; UIImage* scaledImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return scaledImage; } - (UIImage*)aspectFitScaleToSize:(CGSize)newSize scale:(int)scale { if (CGSizeEqualToSize(self.size, newSize)) { return self; } CGRect scaledImageRect = CGRectZero; CGFloat aspectWidth = newSize.width / self.size.width; CGFloat aspectHeight = newSize.height / self.size.height; CGFloat aspectRatio = MIN(aspectWidth, aspectHeight); scaledImageRect.size.width = self.size.width * aspectRatio; scaledImageRect.size.height = self.size.height * aspectRatio; scaledImageRect.origin.x = (newSize.width - scaledImageRect.size.width) / 2.0f; scaledImageRect.origin.y = (newSize.height - scaledImageRect.size.height) / 2.0f; int finalScale = (0 == scale) ? [UIScreen mainScreen].scale : scale; UIGraphicsBeginImageContextWithOptions(newSize, NO, finalScale); [self drawInRect:scaledImageRect]; UIImage* scaledImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(