本文介绍与内存相关的几类优化与极限管理:Option/位运算共用内存(多选项共用一个整数)、内存的极限管理(低内存策略与约束)、Copy-on-Write(写时拷贝)、Tagged Pointer。与 01-内存五大分区、11-深浅拷贝与内存、07-实践与常见问题 配合阅读。
一、Option 与位运算共用内存
1.1 概念
- 将多个布尔或选项压缩到一个整数的不同位上,通过位运算读写,实现「多个开关/状态共占一块内存」;在 C/OC 中常用 NS_OPTIONS、位域(bitfield),在 Swift 中对应 OptionSet。
- 内存:N 个独立
BOOL 或 bool 可能占 N 个字节(甚至对齐后更多);用一个整型的若干位表示,只需 1 个整型(如 4 或 8 字节),在选项较多或实例数量巨大时显著节省内存并利于缓存。
1.2 NS_OPTIONS / 位运算示例(Objective-C)
typedef NS_OPTIONS(NSUInteger, ViewOptions) {
ViewOptionsNone = 0,
ViewOptionsHidden = 1 << 0,
ViewOptionsDisabled = 1 << 1,
ViewOptionsSelected = 1 << 2,
ViewOptionsLoading = 1 << 3,
};
ViewOptions opts = ViewOptionsHidden | ViewOptionsSelected;
BOOL isHidden = (opts & ViewOptionsHidden) != 0;
opts |= ViewOptionsLoading;
opts &= ~ViewOptionsDisabled;
- 上述
ViewOptions 只占 一个 NSUInteger(8 字节),即可表示 64 个独立布尔;若用 64 个 BOOL 属性,会占用更多内存且不利于缓存。
1.3 位域(bitfield)共用内存
struct PackedFlags {
unsigned int visible : 1;
unsigned int enabled : 1;
unsigned int selected : 1;
unsigned int loading : 1;
unsigned int reserved : 4;
};
- 多个成员共享同一整型内存,适合配置、状态、权限等密集布尔/小范围枚举,在大量实例(如 Cell、配置项)时减少内存占用。
1.4 典型场景
| 场景 | 说明 |
|---|
| UI 状态 | 如 hidden、enabled、selected、loading 等用 NS_OPTIONS 或 OptionSet 存为一个整数 |
| 权限/能力 | 读、写、执行等用位表示,一个整数表示一组权限 |
| 配置/特性开关 | 大量配置项用位域或 OptionSet,减少结构体/对象体积 |
| 网络/解析标志 | 协议中的 flags 字段,多位表示多种含义,共用内存 |
二、内存的极限管理
2.1 目标与场景
- 在内存紧张(低内存设备、后台、系统压力大)时,通过主动释放、限制缓存、延迟加载等手段,把占用控制在系统允许范围内,避免被系统杀掉或 OOM。
- 与 07-实践与常见问题 中的「内存警告」「音视频/图层场景」配合使用。
2.2 策略要点
| 策略 | 说明 |
|---|
| 响应 didReceiveMemoryWarning | 释放可重建的缓存(图片、数据)、释放非当前页大对象;主线程不阻塞,异步释放。 |
| 缓存上限与淘汰 | 图片/数据缓存设置 maxCount / maxCost,LRU 等淘汰;避免无界增长。 |
| 后台释放 | 进入后台时释放非必要资源(解码器、大缓冲、预览图),回到前台再按需加载。 |
| 按需加载 / 流式 | 大列表、大文件不一次性进内存;分页、流式读取、大图降采样。 |
| @autoreleasepool | 循环中大量临时对象用 @autoreleasepool {} 控制峰值,见 [05-AutoreleasePool与RunLoop](05-主题 | 内存管理@iOS-AutoreleasePool与RunLoop.md)。 |
| 内存映射 | 大文件用 mmap 等映射访问,减少常驻 RSS;注意映射大小与释放时机。 |
2.3 极限下的注意
- 不保留可重建数据:能重新下载、重新计算的就不要在内存里常驻。
- 控制单页/单模块占用:列表、相册、音视频播放等设定上限,避免单场景吃满内存。
- Instruments:用 Allocations、VM Tracker、Leaks 做「极限场景」压测(反复进入退出、后台、低内存模拟),观察峰值与泄漏。
三、Copy-on-Write(写时拷贝)
3.1 概念
- Copy-on-Write(COW):多个逻辑上的「副本」在未修改前共享同一份底层存储;仅在某一方发生写操作时才为该方复制出一份新存储,再修改,从而避免「一赋值就整块拷贝」的开销。
- 与深浅拷贝的关系:浅拷贝是「多引用、共享子对象」;COW 是「多引用、共享存储,写时才真正拷贝」,在保证值语义的前提下减少内存与 CPU 消耗。详见 11-深浅拷贝与内存。
3.2 Swift 中的 COW
- Array、Dictionary、Set、String 等值类型在 Swift 标准库中实现了 COW:赋值时不立即复制底层 buffer,而是共享;首次发生写操作时,若检测到 buffer 被多处引用(非唯一引用),则先复制 buffer 再写。
- 实现要点:内部持有一个引用类型的 buffer;写前通过
isKnownUniquelyReferenced(或等价机制)判断是否唯一引用,若不唯一则 copy buffer 再写。
- 效果:大量「只读共享」的赋值与传参几乎零拷贝成本;只有写时才付出拷贝代价,适合读多写少的集合与字符串。
3.3 与内存的关系
- 省内存:未修改的「副本」不占额外存储,仅多一个指向同一 buffer 的引用。
- 写时峰值:在共享的 buffer 上首次写入会触发一次拷贝,此时有短暂的内存与 CPU 开销;若写非常频繁且共享多,需注意是否适合用 COW 结构。
- 自定义值类型:Swift 不会自动为自定义 struct 实现 COW,若需要需自己维护「内部引用 + 写时复制」逻辑。
3.4 流程图(概念)
flowchart LR
A[赋值/传参] --> B{写操作?}
B -->|否| C[继续共享 buffer]
B -->|是| D{唯一引用?}
D -->|是| E[直接写]
D -->|否| F[复制 buffer 再写]
四、Tagged Pointer
4.1 概念
- Tagged Pointer 是 Apple 在 64 位 架构下的一种优化:把「小对象」的数据与类型信息直接编码进指针值本身,而不在堆上分配对象;该「指针」并不是指向堆地址,而是即是指针也是数据。
- 内存:不占用堆,不参与引用计数(retain/release 对 Tagged Pointer 为 no-op);仅占一个指针宽度(8 字节),无额外分配、无 isa、无引用计数块,极限节省小对象的内存与调用开销。
4.2 原理(64 位简要)
- 64 位下对象指针通常 16 字节对齐,低 4 位恒为 0;系统用最高位或最低位(依平台而定,如 ARM64 常用最高位)作为 tag,表示「这是 Tagged Pointer」。
- 其余位中:若干位表示类型(如 NSNumber、NSString、NSDate 等),其余位存数据(如小整数、短字符串的编码)。
- 运行时通过「解 tag + 类型 + 数据位」还原出逻辑上的「对象」,不访问堆,不触发 retain/release。
4.3 典型类型与约束
| 类型 | 说明 |
|---|
| NSNumber | 小整数、部分浮点数可直接存进指针,不分配堆对象。 |
| NSString | 较短字符串(如 ASCII 或少量字符)在较新系统上可能用 Tagged Pointer;更长则仍为堆上分配。 |
| NSDate 等 | 部分小对象类型在系统实现中可能使用 Tagged Pointer。 |
- 约束:能编码进指针的数据量有限(几十 bit),仅适用于「小」数据;大数、长字符串、复杂对象仍走普通堆分配。
4.4 对内存管理的影响
- 无堆分配:Tagged Pointer 不占堆,不增加 Allocations 中的对象数。
- 无引用计数:对 Tagged Pointer 发 retain/release 会被识别并忽略,不会造成过度释放或泄漏(从引用计数角度)。
- 不可假设地址:不能把 Tagged Pointer 当普通指针做指针运算或与 C 内存接口混用;判断是否 Tagged Pointer 可用运行时 API(如
objc_isTaggedPointer)。
4.5 小结对比
| 维度 | 普通堆对象 | Tagged Pointer |
|---|
| 存储位置 | 堆 | 指针值本身(无堆) |
| 引用计数 | 有 | 无(no-op) |
| 内存占用 | 对象头 + 实例 + 指针 | 仅 8 字节指针 |
| 适用 | 任意对象 | 小数据(小整数、短字符串等) |
五、思维导图
mindmap
root((Option 与内存优化))
Option 位运算
NS_OPTIONS OptionSet
位域 共用整型
内存极限管理
内存警告 缓存上限
后台释放 按需加载
CopyOnWrite
写时复制 共享 buffer
Swift 集合 isKnownUniquelyReferenced
Tagged Pointer
小对象编码进指针
无堆 无引用计数
参考文献