OC 内存管理
这是我参与「第四届青训营 -IOS场」笔记创作活动的第1篇笔记
背景知识
Stack 栈 vs. Heap 堆
- Stack
- Fist in last out
- 空间向下延伸, 向低地址扩展. 最底部位栈顶, 由栈指针(stack pointer) 指向,做标记.
- 用于存储参数、局部变量、函数调用返回地址.
- 局部指针变量存储于栈中, 而局部指针所指向的空间位于堆(heap)里.
- Heap
- 空间向上延伸, 向低地址扩展.
- 空间一般由手动分配, 用于代码运行时声明的空间(malloc).
- 存储数组对象,线程共享。
C 和 C++内存管理
- C: 用 malloc & free 动态分配/释放内存空间. 需要手动计算类型大小, 管理内存失败返回 0.
- C++: 增加了 new & delete 用于调用构造函数和析构函数用于实例对象的初始化与清理. 可自动计算类型大小, 管理内存失败返回异常.
IOS 内存管理
OC 内存管理
- 非 OC 对象 (如基本类型变量 int/float/char...) 位于栈内存.
- OC 对象(如 NSObject 对象及其子类) 位于堆内存, 需要内存管理.
- 常见问题, 为什么需要管理内存:
- 多个对象之间相互强引用 strong reference,构成循环, 其引用计数 reference counter >= 0, 导致不能释放, 无法被系统回收.
- 单个程序内存占用过多, 造成Out Of Memory (OOM) crash.
- 指针所指向的空间被提前释放,导致野指针 wild pointer, 由于野指针所指向的内存位置的内容是不确定的,因此造成crash.
MRC: Manual Reference Counting
- 在OC中,对象是通过引用计数 reference countering 来进行内存管理. 当一个对象被持有 retain (被一个指针变量所指向), 引用计数就会递增。反之, 如果某个持有(指针) 被释放 release, 指针计数就会递减。当一个对象的指针计数为0时, 对象及其所占内存空间就会被系统回收。
- alloc, new, copy, or mutableCopy 开头的方法创建的对象,引用计数 = 1.
- 对对象发送 retain 消息: 引用计数 += 1
- 对对象发送release 消息: 引用计数 -= 1
- 如果对象的引用计数 == 0, 即将销毁前, 系统会对其发送 dealloc 消息.
- Autorelease vs. release
- release: 马上释放对对象的强引用
- autorelease: 延迟释放对象
- Autorelease-Pool: 对象会放到自动释放池, 统一释放, 在部分场景下可降低内存峰值.
ARC: automatic Reference Counting
- 编译器会自动加入内存管理代码 (retain/release code)
- 不需要通过 retain/release 来手动维护引用计数, 只用专注于对象的初始化构建.
- ARC 将在对象强引用个数=0 时才销毁对象, 而不是引用计数=0时.
- ARC 默认所有对象变量的指针都为强指针。但也可显示声明弱指针.
@Property 有关内存管理的修饰符
参考资料: Objective-C属性(property)的特性(attribute)
- Strong: (default, Only for OC object) 强持有关系
- 属性对一个对象有强引用/持有
- 在强持有被释放前, 对象将一直被存储在堆内存中.
- Weak: 弱持有关系
- 属性对一个对象有弱引用, 但不会对其主张所有权(不会增加 引用计数)
- 即使本属性的引用被解除, 如果还存在其他对该对象的强持有, 对象将一直被存储在堆内存中.
- 如果一个对象被销毁, 那么所有引用此对象的weak 属性都将变成 nil.
- Copy:
- 在对属性赋值前,先回对传入对象进行 copy 操作, 再对copy 的结果主张所有权.
- 实现方式是在setter内部调用copy方法: _val = [obj copy]
- Note: 用 copy 修饰 mutable 对象(如NSMutableString), 赋值时将会出现警告, 因为setter中 copy的操作会吧 mutalbe对象 转变成 immutable对象.
- Assign:
- 与 weak 类似, 唯一区别是, 当其引用的对象被销毁时, 那么所有引用此对象的 assign 属性都将变成野指针.
- 常用语修饰基本类型(int/char/float...)
浅拷贝 vs. 深拷贝
- 浅拷贝 Shallow copy, copy-by-reference. 指针拷贝, 拷贝后的指针指向原被拷贝数据内存区域.
- 深拷贝 Deep copy, copy-by-value. 内容拷贝, 拷贝后的指针指向全新的内存区域, 其中的内容与被拷贝数据一致.
可变对象与不可变对象的复制
| Immutable Obj (Copy Type) | Immutable Obj (Result Type) | Mutable Obj (Copy Type) | Mutable Obj (Result Type) | |
|---|---|---|---|---|
| copy | Shallow Copy | Immutable Obj | Deep Copy | Immutable Obj |
| mutableCopy | Deep Copy | Mutable Obj | Deep Copy | Mutable Obj |
- Note: NSMutableString 是NSString的子类.
copy vs. strong
- 对于不可变对象(copy 或 strong),属性赋值时都是浅拷贝
- 对于可变对象,copy 修饰的属性赋值时是深拷贝, strong修饰的属性赋值时是浅拷贝.
Q: 为什么不可变对象一般用 copy 修饰而不是 strong?
- 防止传入可变对象
- 在copy 的修饰下, 不论传入对象是 mutable 还是 immutable, 传入的结果都是深拷贝的副本, 且不会被外部修改所影响.