内存管理
问题
OC 的内存管理机制经历了哪些演变?ARC 的工作原理是什么?Autorelease Pool 的作用和时机?
答案
MRC 到 ARC 的演变
| MRC | ARC | |
|---|---|---|
| 全称 | Manual Reference Counting | Automatic Reference Counting |
| 管理方式 | 手动 retain/release | 编译器自动插入 retain/release |
| 引入版本 | iOS 2 | iOS 5 |
| 引用计数 | 手动维护 | 编译器 + Runtime 维护 |
| dealloc 调用 | [super dealloc] | 不需要调 super |
引用计数原理
// MRC 时代
NSObject *obj = [[NSObject alloc] init]; // retainCount = 1
[obj retain]; // retainCount = 2
[obj release]; // retainCount = 1
[obj release]; // retainCount = 0 → dealloc
引用计数存储位置:
retainCount < 某阈值:存在isa指针的 extra_rc 位域中(nonpointer isa)retainCount过大时:溢出部分存到 SideTable 的refcnts哈希表中
ARC 规则
ARC 是编译器特性,在编译时自动插入内存管理代码:
// 你写的代码
- (void)viewDidLoad {
NSObject *obj = [[NSObject alloc] init];
self.myObj = obj;
}
// ARC 编译器实际生成的(伪代码)
- (void)viewDidLoad {
NSObject *obj = [[NSObject alloc] init]; // +1
[self.myObj release]; // 旧值 -1
[obj retain]; // 新值 +1
self.myObj = obj;
[obj release]; // 局部变量出作用域 -1
}
属性内存管理修饰符
| 修饰符 | 引用计数 | 使用场景 |
|---|---|---|
strong | +1 | 默认,强引用持有对象 |
weak | 不变 | 避免循环引用(delegate 等) |
assign | 不变 | 基本类型(int、BOOL 等) |
copy | +1(拷贝副本) | NSString、Block |
unsafe_unretained | 不变 | 类似 weak 但不置 nil |
weak 的实现原理
weak 置 nil 过程
- 对象引用计数变为 0,调用
dealloc - Runtime 从
SideTable的weak_table中找到所有指向该对象的 weak 指针 - 将这些指针全部置为
nil - 从
weak_table中移除记录
Autorelease Pool
// 使用场景:延迟释放
- (NSString *)fullName {
NSString *result = [NSString stringWithFormat:@"%@ %@",
self.firstName, self.lastName];
// result 被 autorelease,调用者可以安全使用
return result;
}
// 自定义 Autorelease Pool
@autoreleasepool {
for (int i = 0; i < 100000; i++) {
NSString *str = [NSString stringWithFormat:@"%d", i];
// str 在 @autoreleasepool 结束时释放
// 避免内存峰值过高
}
}
Autorelease Pool 的底层:
- 底层结构是
AutoreleasePoolPage(双向链表) - 每个 page 4096 字节
@autoreleasepool {}对应push()/pop()操作- RunLoop 每次循环结束会
pop并push新的 pool
RunLoop 与 Autorelease 的关系
常见面试问题
Q1: ARC 下还会有内存泄漏吗?
答案:会。ARC 管理的是引用计数,以下情况仍会泄漏:
- 循环引用:A → B → A(需用
weak打破) - Block 循环引用:self → block → self
- NSTimer 强引用:target 被强引用导致 VC 无法释放
- Delegate 设为 strong
- Core Foundation 对象:CF 对象不受 ARC 管理,需要手动
CFRelease - C 语言 malloc:需手动
free
Q2: weak 和 assign 的区别?
答案:
| weak | assign | |
|---|---|---|
| 适用范围 | OC 对象 | 基本类型或对象 |
| 对象释放后 | 自动置 nil | 指针不变(野指针) |
| 性能开销 | 略高(维护 weak 表) | 无额外开销 |
用 assign 修饰对象会导致野指针崩溃,所以对象应该用 weak。
Q3: 什么时候需要手动创建 @autoreleasepool?
答案:
// 1. 大量临时对象的循环
for (int i = 0; i < 1000000; i++) {
@autoreleasepool {
NSString *str = [NSString stringWithFormat:@"%d", i];
// 每次循环结束就释放,避免内存飙升
}
}
// 2. 非主线程(没有默认 RunLoop 的 Autorelease Pool)
dispatch_async(dispatch_get_global_queue(0, 0), ^{
@autoreleasepool {
// 后台线程操作
}
});
Q4: retain/release 的底层实现?
答案:
引用计数存储在两个地方(nonpointer isa 优化):
- isa 的 extra_rc 位域:存储额外引用计数(8 位或 19 位,取决于架构)
- SideTable 的 refcnts:extra_rc 溢出时,一半转存到 SideTable
retain 操作:先尝试对 isa 的 extra_rc 做原子 +1,溢出则转存到 SideTable。
release 操作:先尝试对 extra_rc 做原子 -1,如果为 0 则检查 SideTable,引用计数归 0 触发 dealloc。