本章节列举对内存使用不合理的场景及优化方案,在实际的研发中还是需要同学们严格遵守代码规范,避免踩坑。
共有以下几点:
1、使用NSCache
2、避免内存泄漏
3、移除当前未使用的内存
4、优化图片使用
使用缓存
使用 NSCache
不仅能保证线程安全,同时在收到内存警告时,可以释放未使用的内存,此外针对compressed memory,NSCache
也有优化。
在实际的研发中,也推荐对缓存设置上限,比如YY 中关于图片缓存设置一个最大的缓存值。对于缓存设置上限后引入的缓存清除策略,也可以参考YY 使用 LRU(last recently use)方式,更新缓存值。
避免内存泄漏
内存泄漏是指申请的内存空间使用完毕之后未回收。造成内存泄漏的原因有很多,各个场景也不尽相同,下面列举常见一些场景。
Block 使用不当等原因造成的循环引用
- self 持有的Block 内直接使用了self,这种属于非常常见的循环引用了,不过这里还是不推荐大家遇到Block无脑使用weakSelf,使用 weakSelf是会造成额外的性能损耗的,像常见的使用
GCD
Block内是不需要使用弱引用的 - 隐含的循环引用,比如superView -> self.xxxView -> self.xxxView.block -> superView。有一些场景并不会直接使用self关键字,但是由于addSubview等方式会持有一些子View,这些子View 又可能持有其他子View且这些子View 可能会使用Block,这时候就需要特别注意,最好理清所有对象的引用关系,避免循环引用。
子线程runloop
没有停掉,导致子线程一直存活。
一般来说子线程在任务结束后 会自动关闭,但是如果子线程中由于定时器功能,会打开runloop
,如果不先停止runloop
的话,子线程也就无法正常关闭
移除未使用的内存
- 消息列表中已不可见的消息数据
- 进入后台后,当前页面可以移除
优化图片使用
图片所占用的内存对于大多数APP 来说都是不能忽视的,合理的使用图片不仅需要研发尽量保证编码规范,还需要实施一些图片大小监控。
1、避免将图片放在内存里
- 解码后的UIImage 占用的内存比较大,如果当前不需要显示时,可以不放在内存里
- 同一个页面使用重复资源时,避免重复创建(比如 解码的YYImage)
- 批量使用图片的场景,推荐使用
autoreleasepool
等方式及时释放内存 - 大图不放在Asset中且通过
imageWithContentOfFile
读取
2、图片裁剪
大多情况下业务场景需要显示的图片尺寸小于图片的原始尺寸。图片裁剪一般有两种选择,一是在后端下发图片的时候,不同场景下发不同尺寸的URL;二是图片下载后,客户端对原图进行合适的裁剪。
3、图片绘制及缩放使用 UIGraphicsImageRenderer
和 ImageIO
常见的UIGraphicsBeginImageContextWithOptions
绘图方式会有两个问题:
(1)默认是 SRGB 的格式,也就是说每个像素需要占 4 个 bytes 的空间,对于一些黑白或者仅有 alpha 通道的数据来说是没有必要的。
(2)需要将原图片完全解码后渲染出来,原图片的解码会造成内存占用的高峰。
iOS 12之后,使用UIGraphicsImageRenderer
绘图。系统会自动选择合适的颜色格式,避免不必要的内存消耗。
对于原方法需要解码原图造成内存暴涨的问题,可以考虑用ImageIO来解决。ImageIO可以直接读取图像大小和元数据信息,不会带来额外的内存开销。
这里附上ImageIO 创建缩略图的使用代码
+ (UIImage *)getThumbImageWithMax:(CGFloat)max imageData:(NSData *)imageData {
if (imageData.length == 0) {
return nil;
}
CFDictionaryRef dicOptionsRef = (__bridge CFDictionaryRef) @{(id)kCGImageSourceCreateThumbnailFromImageAlways : @(YES),
(id)kCGImageSourceThumbnailMaxPixelSize : @(max),
(id)kCGImageSourceShouldCache : @(NO),
(id)kCGImageSourceCreateThumbnailWithTransform:@(YES)
};
CGImageSourceRef src = CGImageSourceCreateWithData((__bridge CFDataRef)imageData, nil);
CGImageRef imageRef = CGImageSourceCreateThumbnailAtIndex(src, 0, dicOptionsRef);
UIImage *newImage = [UIImage imageWithCGImage:imageRef];
if (imageRef != nil) CFRelease(imageRef);
CFRelease(src);
return newImage;
}
网友评论