文长不看
- 分析内存虚拟内存和堆内存(All Heap Allocagtions)都需要关注, VM 也是有区别的,有些会导致 OOM
- VM: CG raster data 会导致崩溃
- 创建22小位图,Instruments 标记的是堆内存,100100 的位图,Instruments 标记的是 VM: CG raster data
- AutoLayout 用多了也要占用内存,CollectionCell 上最好都使用手动布局(不光内存更优,性能也更好)
- 屏幕中 Cell 数量越多,占用内存越多。
- Cell 越复杂,占用的内存越多
背景
有人反馈了打开一个面板内存上涨了 150m。instruments 分析以后得到如下数据:
150 M 其中大头都是 VM 内存, 勾选只看堆内存(All Heap Allocagtions) 创建的内存为 46M
30.88 MB 66.3% 262648 start
8.96 MB 19.2% 2845 thread_start
6.73 MB 14.4% 55930 start_wqthread
VM 内存会把机器搞挂么?
Case 1
我写了个 demo, 设置 kCGImageSourceShouldCacheImmediately = YES, kCGImageSourceShouldCache = YES。
在 iPad pro 上暴力测试,VM 上涨到 70 G (有点心疼我自己的机器,没有继续测试了),进程也一直没有挂。

Case 2
但是如果不断调用 UIGraphicsBeginImageContextWithOptions 创建位图并且不释放, CG raster data vm bu




Case 3
malloc 分配 堆内存 2.8G 时 OOM
这里我们得出结论:分析内存虚拟内存和堆内存(All Heap Allocagtions)都需要关注, VM部分也是有区别的,有些会导致 OOM,有些不会

Case 4
创建2*2小位图,Instruments 标记的是堆内存
1 2 3 4 5 6 7 8
| - (void)testCGRasterDataOOM { // 小图用的是堆内存 UIGraphicsBeginImageContextWithOptions(CGSizeMake(2, 2), NO, 1); NSMutableArray *arr = [NSMutableArray new]; while (1) { [arr addObject:UIGraphicsGetImageFromCurrentImageContext()]; } }
|

Case 5
创建 100*100 的位图,Instruments 标记的是 VM: CG raster data
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| - (void)viewDidLoad { [super viewDidLoad]; _imageView = [[UIImageView alloc] initWithFrame:self.view.bounds]; [self.view addSubview:_imageView]; UIGraphicsBeginImageContextWithOptions(CGSizeMake(4000, 4000), NO, 1); [[UIColor redColor] setFill]; CGContextFillRect(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, 4000, 4000)); UIImage *img = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); NSData *data = UIImageJPEGRepresentation(img, 0.9); NSString *cachePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"abc.jpeg"]; [data writeToFile:cachePath atomically:YES]; while (1) { @autoreleasepool { [self.class loadCachedImageWithoutDecode]; } } }
+ (nullable UIImage *)loadCachedImageWithoutDecode { NSString *cachePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"abc.jpeg"]; if ([[NSFileManager defaultManager] fileExistsAtPath:cachePath]) { NSURL *imageUrl = [NSURL fileURLWithPath:cachePath]; NSDictionary *imageSourceOptions = @{(__bridge id)kCGImageSourceShouldCache : @NO , (__bridge id)kCGImageSourceShouldCacheImmediately : @NO}; CGImageSourceRef imageSource = CGImageSourceCreateWithURL((CFURLRef)imageUrl, (__bridge CFDictionaryRef)imageSourceOptions); if (imageSource) { NSDictionary *options = @{ (__bridge id)kCGImageSourceShouldCache : @YES , (__bridge id)kCGImageSourceTypeIdentifierHint : (id)kUTTypeJPEG, (__bridge id)kCGImageSourceShouldCacheImmediately : @YES}; CGImageRef imageRef = CGImageSourceCreateImageAtIndex(imageSource, 0, (__bridge CFDictionaryRef)options); CFRelease(imageSource); if (imageRef) { return [UIImage imageWithCGImage:imageRef]; } } } return nil; }
|

AutoLayout 也会是吃内存
一个奇怪的问题:AutoLayout 也会是吃内存的,特别是约束特别多的时候(贴纸面板的这个场景一口气加载了十几页的数据,创建了非常多的 Cell。
删除了约束改成手写 frame ,删除无用的控件以后,内存下降了14 M。

优化 6.73M 解码后的图片
图片缓存的问题:只在必要的时候解码,贴纸的场景,在绘制完本地的贴纸(因为有时间、用户名信息,需要经常重绘,比较难复用),可以通过创建完之后先写入磁盘,生成一个没有解码的 UIImage来减少内存的峰值
可以利用 imageIO 实现一个 loadCachedImageWithoutDecode,也可以直接用 [UIImage imageWithData:data] 来实现一样的效果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| - (void)viewDidLoad { [super viewDidLoad]; _imageView = [[UIImageView alloc] initWithFrame:self.view.bounds]; [self.view addSubview:_imageView]; UIGraphicsBeginImageContextWithOptions(CGSizeMake(4000, 4000), NO, 1); [[UIColor redColor] setFill]; CGContextFillRect(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, 4000, 4000)); UIImage *img = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); NSData *data = UIImageJPEGRepresentation(img, 0.9); NSString *cachePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"abc.jpeg"]; [data writeToFile:cachePath atomically:YES]; // self.image = [self.class loadCachedImageWithoutDecode]; // self.image = img; // self.image = [UIImage imageWithData:data]; }
+ (nullable UIImage *)loadCachedImageWithoutDecode { NSString *cachePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"abc.jpeg"]; if ([[NSFileManager defaultManager] fileExistsAtPath:cachePath]) { NSURL *imageUrl = [NSURL fileURLWithPath:cachePath]; NSDictionary *imageSourceOptions = @{(__bridge id)kCGImageSourceShouldCache : @NO , (__bridge id)kCGImageSourceShouldCacheImmediately : @NO}; CGImageSourceRef imageSource = CGImageSourceCreateWithURL((CFURLRef)imageUrl, (__bridge CFDictionaryRef)imageSourceOptions); if (imageSource) { NSDictionary *options = @{ (__bridge id)kCGImageSourceShouldCache : @NO , (__bridge id)kCGImageSourceTypeIdentifierHint : (id)kUTTypeJPEG, (__bridge id)kCGImageSourceShouldCacheImmediately : @NO}; CGImageRef imageRef = CGImageSourceCreateImageAtIndex(imageSource, 0, (__bridge CFDictionaryRef)options); CFRelease(imageSource); if (imageRef) { return [UIImage imageWithCGImage:imageRef]; } } } return nil; }
|
【10 屏消耗内存】
|
20*20 |
50*50 |
100*100 |
空 |
17 m |
4.6m |
1.66m |
贴一个 imageView |
20.9m |
5.5m |
1.97m |
贴一个 UIView |
20.5m |
5.4m |
1.96m |
本文测试使用的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
| #import "ViewController.h" #import <MobileCoreServices/MobileCoreServices.h>
@interface ViewController (){ int allocatedMB; Byte *p[10000]; } @property (nonatomic) UIImage *image; @property (nonatomic) UIImageView *imageView; @end
@implementation ViewController
- (void)viewDidLoad { [super viewDidLoad]; _imageView = [[UIImageView alloc] initWithFrame:self.view.bounds]; [self.view addSubview:_imageView]; // UIImage *img = [self createDecodedImage]; // NSData *data = UIImageJPEGRepresentation(img, 0.9); // // NSString *cachePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"abc.jpeg"]; // [data writeToFile:cachePath atomically:YES]; // // self.image = [self.class loadCachedImageWithoutDecode]; //// self.image = img; //// self.image = [UIImage imageWithData:data]; // [self.class loadCachedImageWithoutDecode]; [self testCGRasterDataOOM]; }
- (void)testHeepOOM { while (1) { [NSThread sleepForTimeInterval:0.1]; [self allocateMemory]; } }
- (void)testImageNoReleaseOOM { NSMutableArray *arr = [NSMutableArray new]; while (1) { [NSThread sleepForTimeInterval:0.1]; [arr addObject:[self createDecodedImage]]; } }
- (void)testCGRasterDataOOMSmallSize { UIGraphicsBeginImageContextWithOptions(CGSizeMake(2, 2), NO, 1); NSMutableArray *arr = [NSMutableArray new]; while (1) { [arr addObject:UIGraphicsGetImageFromCurrentImageContext()]; } }
- (void)testCGRasterDataOOM { UIGraphicsBeginImageContextWithOptions(CGSizeMake(100, 100), NO, 1); NSMutableArray *arr = [NSMutableArray new]; while (1) { [NSThread sleepForTimeInterval:0.01]; [arr addObject:UIGraphicsGetImageFromCurrentImageContext()]; } }
- (void)allocateMemory { p[allocatedMB] = malloc(10485760); memset(p[allocatedMB], 0, 10485760); allocatedMB += 1; }
- (UIImage *)createDecodedImage { UIGraphicsBeginImageContextWithOptions(CGSizeMake(4000, 4000), NO, 1); [[UIColor redColor] setFill]; CGContextFillRect(UIGraphicsGetCurrentContext(), CGRectMake(0, 0, 4000, 4000)); UIImage *img = UIGraphicsGetImageFromCurrentImageContext(); img = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return img; }
+ (nullable UIImage *)loadCachedImageWithoutDecode { NSString *cachePath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"abc.jpeg"]; if ([[NSFileManager defaultManager] fileExistsAtPath:cachePath]) { NSURL *imageUrl = [NSURL fileURLWithPath:cachePath]; NSDictionary *imageSourceOptions = @{(__bridge id)kCGImageSourceShouldCache : @NO , (__bridge id)kCGImageSourceShouldCacheImmediately : @NO}; CGImageSourceRef imageSource = CGImageSourceCreateWithURL((CFURLRef)imageUrl, (__bridge CFDictionaryRef)imageSourceOptions); if (imageSource) { NSDictionary *options = @{ (__bridge id)kCGImageSourceShouldCache : @YES , (__bridge id)kCGImageSourceTypeIdentifierHint : (id)kUTTypeJPEG, (__bridge id)kCGImageSourceShouldCacheImmediately : @YES}; CGImageRef imageRef = CGImageSourceCreateImageAtIndex(imageSource, 0, (__bridge CFDictionaryRef)options); CFRelease(imageSource); if (imageRef) { return [UIImage imageWithCGImage:imageRef]; } } } return nil; }
@end
|
相关文档
ios app maximum memory budget
https://developer.apple.com/library/archive/documentation/Performance/Conceptual/ManagingMemory/Articles/AboutMemory.html
https://www.jianshu.com/p/1f884615a1d7?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation
https://blog.jamchenjun.com/2018/08/22/image-and-graphics-best-practices.html