当前位置: 首页 > news >正文

深入了解iOS内存管理

在开始之前,我们先要弄清一个问题,我们为什么要减少软件的内存占用?

简单的回答是,为了使用户获得更好的体验

不仅你的 App 会启动得更快,系统会表现得更好,你的 App 也会在内存中保留更长的时间

其他 App 也会在内存中保留更长的时间,几乎一切都变得更好

内存占用

首先我们要谈谈相对底层的部分,页

系统通过内存页来分布内存,一页的大小一般是16kB,它们可能是净页,也可能是脏页

一个页可以存储多个对象,一个对象也可能被分配到多个页上来存储

App的内存占用,实际上指的是页面数量乘以页面大小

关于脏页和净页,这里有一个例子

int* array = malloc(20000 * sizeof(int));
array[0] = 32;
array[19999] = 64;

假设我分配了一个含有20000个int的数组,系统可能会分配给我6个内存页面

此时,这六个页面是净页

但是当我开始写入数据的时候,例如我写入第0个数据,这个内存页就会变为脏页

类似的,如果我写入最后一个位置,最后一页也会变成脏页

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

注意!!!!

此时中间的四个页面仍然是净页,因为App还没有写入它们

所以判断页是否干净的依据是这页上是否有写入数据,而不是是否被占用

同时另一个有趣的东西是内存映射文件,它是一种在磁盘上的文件,但是加载到了内存中,如果你使用的是只读文件,这些页面会一直是净页

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

当我们讨论某个App时,它们的内存占用和分析文件,都会分为三部分

  • 脏的部分
  • 压缩部分
  • 干净部分

净内存

净内存是可以被分页的内存,它们通常是

  • 内存映射文件
  • 框架(_DATA_CONST部分)

关于框架的部分,_DATA_CONST部分一般是净内存,但是如果你使用run time的一些方法,它就会变成脏内存

脏内存

脏内存是App写入的任何内存,它们可能是

  • 对象
  • 图像缓冲
  • 框架(_DATA,_DATA_DIRTY)

压缩内存

压缩内存在iOS 7后被加入,内存压缩器接收未访问的内存页并压缩它们

这实际上可以创建更多的空间

但注意,在访问时,压缩器会对它们进行解压,以便读取内存

内存警告

关于内存警告部分,我们首先要知道,你的App实际上可能并不是引发内存警告的原因

如果在一个低内存的设备上接到一个电话,那也可能会触发一个内存警告

压缩器使得对内存警告的处理,即内存的释放变得复杂

例如:

我们在一个内存中有一个字典,它本身占了3页,但是在压缩后,它变成了一页

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这本身是非常好的事,因为我们多了两页可以用的内存

但当我们收到一个内存警告,并决定从缓存中删除掉所有的对象的时候,问题就出现了

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

由于我们需要访问这个字典,这个字典被压缩器解压了!

这让本就不富裕的内存更加雪上加霜

内存占用限制

在App中,当我们讨论App的内存占用的时候,我们实际上在讨论的内存是脏内存和压缩内存,净内存在这里并不重要,这并不难理解

每个App都有一个内存占用限制

这个限制通常来说对于一个App来说是相当高的,但是请记住,根据设备的不同,这个限制也会改变,所以在优化App的内存占用的时候,还是尽可能做到少占用

当你的App超过了内存占用限制,就会出现异常,这种异常就是EXC_RESOURCE_EXCEPTION

图像的内存占用

关于图像的内存,最重要的就是内存的使用与图像的尺寸有关,而不与它的文件大小有关

举一个例子,我们有一个2048*1536的大小为590kB的图像,它如果被加载到内存中,需要占多少内存呢?

答案是10MB

把像素的宽度乘以高,每个像素再乘以4,就是占用的内存数量

图像在iOS上工作有加载,解码,渲染三个阶段

在加载阶段,这个被压缩的590KB的JPEG文件被接受并被加载到内存中

在解码阶段,JPEG文件将被转化为GPU可以读取的格式,图像需要被解压,这将使得文件大小增加至10MB

在这之后,就可以被渲染了

在SRGB格式中,每个像素有4个字节,这是图像中最常见的格式,红,绿,蓝各一个字节,Alpha通道一个字节

图像下采样

在之后的项目中,我们可能需要对图像进行下采样,即获取缩略图操作

我们在制作缩略图的时候,不应该使用UIImage来直接控制大小,如果有超清图片的存在,你的内存占用将飙升到一个恐怖的值

这里只简单介绍一个下采样方法,使用Image IO来进行下采样

先加一张图片

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这张照片的信息为

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

130MB对吧,但是记得我们之前说过的吗

内存的使用与图像的尺寸有关,而不与它的文件大小有关

所以加入内存中的大小应该为14075 * 7010 * 4

我们把它加入程序显示

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

占用内存稳定为420MB

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如果我们要直接缩小这个View来实现缩略图的效果,

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

太棒了!内存占用根本没有任何变化!

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

那我们如何高效的实现缩略图呢?

思路实际上很简单,我们想要描绘大象的身形,我们可以不把它抬进屋,只在门口把它的影子画出来就好了

即避免完全解码,直接根据原始数据来生成缩略图

我们的函数需要原始的未解码的原始数据,和最后图片的精确度,所以函数接口应该长这样

- (UIImage *)downsampleImageWithData:(NSData *)imageData toMaxDimension:(CGFloat)maxDimension

我们要使用的部分为Image IO框架,这个框架是使用c语言来编写和构建的,更为底层

当我说这个框架是c语言构建的时候,就说明,这个框架的内存管理是非常自由的(完全没有)

它们不使用ARC,而是遵循传统的c语言内存管理规则,需要手动调用来释放资源

这同时意味着,NSData,NSDictionary不能直接被使用,你必须使用c语言中与之对应的数据类型CFDataRef, CGImageSourceRef, CFDictionaryRef

同时,你还需要使用桥接来连接这两个完全不同的世界,以便你可以将你的高级对象传递给底层的c

数据内容

CGImageSourceRef代表图像的来源,它是ImageIO框架的核心,用于从各种来源(NSData,NSURL等)读取图像数据和元数据

我们的第一步就应该是将参数传入的数据转化为CGImageSourceRef类型,方便识别

CGImageSourceCreateWithData 是一个非常基础和重要的函数,它的作用是建立一个读取图像数据的通道,它返回一个c类型的指针,并且需要手动释放

__bridge 是一种桥接,它把一个oc对象的指针安全地转化为一个c语言类型的指针,注意,它只会进行指针的转换,而不改变对象的引用计数或所有权

CFDataRef 是 Core Foundation 框架中表示原始二进制数据的 C 类型引用。它与 Cocoa 框架中的 NSData功能上等价

知道了上述这四个东西,我们就可以迈出第一步:

CGImageSourceRef imageSource = CGImageSourceCreateWithData((__bridge CFDataRef)imageData, NULL);

成功的将图片转化为了一段数据的引用

配置下采样选项

我们曾经在AFN的第三方库里学过使用字典来配置请求连接

同样的这里的下采样也适用字典来配置采样的选项

这里简单介绍我们需要用到的四个选项

  • kCGImageSourceShouldCache

    告诉ImageIO不要在内部缓存完整解压后的原始图像数据,这也是我们的目的,高效实现下采样,确保我们只为缩略图分配内存

    在这里我们设置为 @NO

  • kCGImageSourceCreateThumbnailWithTransform

    方向修正,告诉ImageIO在生成缩略图的时候,是否自动旋转图片,让图片方向正确

    这里我们设置为 @YES

  • kCGImageSourceCreateThumbnailFromImageIfAbsent

    当图像不包含嵌入的缩略图,也要从主图像数据生成一个

    这个选项保证我们无论如何都可以得到一个缩略图

    这里我们设置为 @YES

    你知道吗?
    很多原始图片文件是包含一个或多个嵌入的缩略图的
    许多现代的图像文件格式(最常见的是 JPEG 和一些 RAW 格式)在文件主体像素数据之外,还包含额外的元数据(Metadata)。这些元数据中,往往包含了一个或多个预先生成好的缩略图
    这样可以提高用户体验和文件预览速度

  • kCGImageSourceThumbnailMaxPixelSize

    告诉ImageIO最终生成缩略图的最大边长(宽或高),不能超过这个参数,ImageIO 会根据这个值在读取数据时直接执行高效缩放

    这个值即为我们传入的参数

    设置为 @(maxDimension)

最后我们的字典应该是

NSDictionary *options = @{(__bridge NSString *)kCGImageSourceShouldCache: @NO,(__bridge NSString *)kCGImageSourceCreateThumbnailWithTransform: @YES,(__bridge NSString *)kCGImageSourceCreateThumbnailFromImageIfAbsent: @YES,(__bridge NSString *)kCGImageSourceThumbnailMaxPixelSize: @(maxDimension)};

生成缩略图

所有准备工作都完成了,我们就可以开始生成缩略图了

CGImageRef 结果是一个指向位图图像数据的引用。这个数据就是已经按 maxDimension 缩放好的小图的像素数据

CFDictionaryRef 其实就是oc的字典对象在c里的投射,不过多讲述

CGImageSourceCreateThumbnailAtIndex 函数,接收一段数据,和一个字典作为选项

ImageIO 在接收到这个指令后,它不会读取和解码所有像素。它会利用图像文件格式(如 JPEG)的特性,在读取数据流的同时,进行缩放和下采样,只解码生成目标尺寸 (X×Y) 所需的像素

这样CGImageRef就是我们需要的缩略图数据了

CGImageRef downsampledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, (__bridge CFDictionaryRef)options);

转化为UIImage

在转化之前,我们要先善后

在一开始我们就提到过,ImageIO框架由c语言撰写,所以内存管理相当的自由

在我们已经拿到了我们需要的数据之后,我们要先把之前不需要的数据释放掉

CFRelease(imageSource); // 释放之前的数据

之后,我们就可以将之前的数据转化为UIImage对象

UIImage *finalImage = [UIImage imageWithCGImage:downsampledImage];

最后释放我们的CGImageRef数据

CGImageRelease(downsampledImage);

以及返回最终的缩略图像

整个过程到这里就结束了

效果是这样的

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

下采样就到这里就算完成了
当然iOS内存管理部分要学习的还有很多,包括方法交换造成的内存占用都是将来要学习的部分内容
关于CF和CG框架,也只是在这里简单介绍了一下而已,之后还需要多学习

引用资料

  1. https://developer.apple.com/cn/videos/play/wwdc2018/416
  2. https://zhuanlan.zhihu.com/p/579702765
  3. https://blog.csdn.net/q923714892/article/details/118343736
http://www.dtcms.com/a/589116.html

相关文章:

  • 介质电磁特性参数
  • 网站建设行业广告语建网站找那家企业好
  • Python中使用sqlite3模块和panel完成SQLite数据库中PDF的写入和读取
  • 佛山网站建设网络公司上海网站seo诊断
  • 操作系统面试题学习
  • Java 大视界 -- Java 大数据在智能教育虚拟学习环境构建与用户体验优化中的应用
  • .NET Core 如何使用 Quartz?
  • excel下拉选项设置
  • 深入解析:利用EBS直接API实现增量快照与精细化数据管理(AWS)
  • 专门做石材地花设计的网站有哪些网站是免费学做网页的
  • [Godot] Google Play审核反馈:如何应对“您的游戏需要进行更多测试才能发布正式版”?
  • Rust 练习册 :深入探索可变长度数量编码
  • dify二次开发部署服务器
  • webrtc降噪-NoiseEstimator类源码分析与算法原理
  • 4.3 Boost 库工具类 optional 的使用
  • 帮人做网站要怎么赚钱吗吉林平安建设网站
  • 文广网站建设sq网站推广
  • Nop平台拆分出核心部分nop-kernel
  • 结构型设计模式1
  • 普中51单片机学习笔记-中断
  • 二十六、STM32的ADC(DMA+ADC)
  • 网站开发的著作权和版权网站品牌推广
  • 【Docker】docker compose
  • 4.1.8 【2022 统考真题】
  • 深圳网站设计官网番禺人才网上
  • Tailwind CSS的Flex布局
  • 深入解析 LeetCode 1:两数之和
  • 重庆网站制作福州嘉兴网络科技有限公司
  • OpenCV(二十二):图像的翻转与旋转
  • 权限维持:操作系统后门技术分析与防护