iOS开发基础25-内存管理深度解析:ARC 与 MRC
iOS 内存管理深度解析:ARC 与 MRC
内存管理是 iOS 开发的核心基础。本文深入解析自动引用计数(ARC)与手动引用计数(MRC)的原理、规则、循环引用、autoreleasepool、引用计数存储以及 CoreFoundation 桥接,并补充 Swift 中的 ARC 实践。
一、自动引用计数(ARC)
1. 什么是 ARC
ARC(Automatic Reference Counting)从 iOS 5 引入,是编译器特性,在编译时自动分析对象的生命周期,插入 retain、release、autorelease 等内存管理代码,开发者无需手动管理。
ARC 以编译时特性为主,但
weak引用的自动置 nil、引用计数的存储与维护等需要运行时支持(objc_runtime),并非纯编译时。
2. 基本原理
ARC 基于引用计数:对象被强引用时引用计数 +1,强引用释放时 -1,引用计数归 0 时对象销毁(调用 dealloc)。
| 方法 | 作用 |
|---|---|
retain |
引用计数 +1 |
release |
引用计数 -1,归 0 时销毁对象 |
autorelease |
放入自动释放池,pool 销毁时 -1 |
3. 使用规则
- 禁止显式调用
retain、release、autorelease、retainCount(编译器报错)。 - 可以重写
dealloc,但不能调用[super dealloc](编译器自动插入)。 @property修饰符:
| 修饰符 | 作用 | 适用场景 |
|---|---|---|
strong |
强引用,引用计数 +1,类似 MRC 的 retain |
大多数对象属性 |
weak |
弱引用,不增加引用计数,对象销毁时自动置 nil | delegate、Block 内引用 self、避免循环引用 |
assign |
不增加引用计数,对象销毁后不自动置 nil(野指针风险) | 基本数据类型(int/BOOL/CGFloat) |
copy |
拷贝对象并强引用,类似 MRC 的 copy |
NSString、NSArray、Block 等可变类型 |
unsafe_unretained |
类似 weak,但对象销毁后不自动置 nil(野指针) | 需兼容 iOS 4 等不支持 weak 的场景 |
weak与unsafe_unretained的核心区别:weak在对象销毁时自动置 nil(安全),unsafe_unretained不置 nil(悬垂指针,访问会崩溃)。iOS 5+ 优先使用weak。
4. dealloc 中需要手动处理的内容
ARC 下成员变量的释放由编译器自动完成,但以下资源需在 dealloc 中手动清理:
- (void)dealloc {
// 移除 KVO 监听(不移除会崩溃)
[self.obj removeObserver:self forKeyPath:@"property"];
// 移除通知观察者(iOS 9+ selector 方式可自动移除,Block 方式仍需手动)
[[NSNotificationCenter defaultCenter] removeObserver:self];
// 停止定时器(NSTimer 会强引用 target,不移除会循环引用)
[self.timer invalidate];
// 停止网络请求、关闭文件句柄等
}
5. 循环引用
当两个对象互相强引用时,引用计数永远无法归 0,导致内存泄漏:
// 错误:循环引用,两个对象都无法释放
@interface Person : NSObject
@property (nonatomic, strong) Dog *dog; // Person 强引用 Dog
@end
@interface Dog : NSObject
@property (nonatomic, strong) Person *person; // Dog 强引用 Person → 循环引用!
@end
解决方案:将其中一端改为 weak:
@interface Dog : NSObject
@property (nonatomic, weak) Person *person; // weak 打破循环引用
@end
常见循环引用场景
| 场景 | 原因 | 解决方案 |
|---|---|---|
| delegate | 控制器强引用 View,View 强引用 delegate=控制器 | delegate 用 weak |
| Block | Block 强引用 self,self 强引用 Block | Block 内用 __weak typeof(self) weakSelf |
| NSTimer | timer 强引用 target=self,self 强引用 timer | 使用 weak target 中间类或 iOS 10+ block-based timer |
| 异步回调 | 控制器被 pop 后仍被强引用 | 使用 weak 引用 |
Block 循环引用的 Weak-Strong Dance
__weak typeof(self) weakSelf = self;
self.completionBlock = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
if (!strongSelf) return; // self 已释放,直接返回
[strongSelf doSomething];
};
6. ARC 底层实现
编译器在编译时自动插入内存管理代码。以属性 setter 为例:
// 源码
@interface Person : NSObject
@property (nonatomic, strong) Dog *dog;
@end
@implementation Person
- (void)setDog:(Dog *)dog {
if (_dog != dog) {
[_dog release]; // 释放旧值
_dog = [dog retain]; // 保留新值
}
}
@end
对象创建与使用的编译后逻辑:
// 源码
Person *person = [[Person alloc] init]; // alloc init 后引用计数为 1
Dog *dog = [[Dog alloc] init]; // 引用计数为 1
person.dog = dog; // strong setter 内部 retain dog → dog 引用计数 2
// ... 使用 ...
// 方法结束时,编译器自动插入:
[dog release]; // dog 引用计数 1(仍被 person.dog 持有)
[person release]; // person 引用计数 0 → dealloc → dealloc 中 release _dog → dog 引用计数 0 → dealloc
[[Person alloc] init]返回的对象引用计数已经是 1,ARC 不会在 alloc 后再插入retain。方法结束时,编译器根据对象的使用情况自动插入release。
二、手动引用计数(MRC)
1. 什么是 MRC
MRC(Manual Reference Counting)是 iOS 早期的内存管理方式,开发者需手动调用 retain、release、autorelease 管理对象生命周期。iOS 5 之前是默认方式,现已被 ARC 取代,但理解 MRC 有助于深入理解 ARC。
2. 核心规则(内存管理黄金法则)
你拥有(own)通过
alloc、new、copy、mutableCopy创建的对象,必须在不需要时release。通过其他方法获得的对象,你不拥有它;如果需要保持,调用
retain,不需要时release。
| 操作 | 所有权 | 是否需要 release |
|---|---|---|
[[Person alloc] init] |
拥有 | 是 |
[Person new] |
拥有 | 是 |
[obj copy] |
拥有 | 是 |
[NSArray array](便利构造器) |
不拥有(autoreleased) | 不需要,如需保持则 retain |
[dict objectForKey:] |
不拥有 | 不需要,如需保持则 retain |
3. MRC 示例
- (void)example {
// 1. alloc 创建,拥有对象,引用计数 1
Person *person = [[Person alloc] init];
// 2. 便利构造器,不拥有,autoreleased
Dog *dog = [Dog dogWithName:@"Wangcai"];
// 3. 属性为 retain 时,setter 内部自动 retain
// 属性为 assign 时,不 retain
person.dog = dog; // retain 属性 → dog 引用计数 +1
// 4. person 不再需要,release
[person release]; // person 引用计数 0 → dealloc → dealloc 中 [_dog release] → dog 引用计数 -1
// dog 是 autoreleased,由 autoreleasepool 在适当时候 release
}
MRC 下的 @property 与 setter
// .h
@property (nonatomic, retain) Dog *dog; // retain 属性
// .m(MRC 下需手动合成 setter)
- (void)setDog:(Dog *)dog {
if (_dog != dog) {
[_dog release]; // 释放旧值
_dog = [dog retain]; // 保留新值
}
}
- (void)dealloc {
[_dog release]; // 必须在 dealloc 中释放 retain 的属性
[super dealloc]; // MRC 下必须调用 [super dealloc]
}
MRC 下
@property(retain)的 setter 会自动 retain(如果是合成的 setter),但dealloc中必须手动释放属性并调用[super dealloc]。
4. autorelease 与自动释放池
autorelease 将对象放入当前的自动释放池,pool 销毁时对池中所有对象调用 release:
// 系统在 main 函数和 RunLoop 每次循环中自动创建 autoreleasepool
// 也可以手动创建,适用于循环中产生大量临时对象的场景
@autoreleasepool {
for (NSInteger i = 0; i < 10000; i++) {
// stringWithFormat: 返回 autoreleased 对象
NSString *str = [NSString stringWithFormat:@"%ld", i];
// 不手动 release,由 pool 在结束时统一 release
}
} // pool 销毁,所有 autoreleased 对象 release
手动
@autoreleasepool常用于循环内产生大量临时对象的场景,避免内存峰值过高。
5. 野指针与内存泄漏
| 问题 | 原因 | 后果 |
|---|---|---|
| 内存泄漏 | 该 release 的对象没有 release | 对象无法销毁,内存持续占用 |
| 野指针(悬垂指针) | 对象已 release,但指针未置 nil,仍指向已销毁内存 | 访问时 EXC_BAD_ACCESS 崩溃 |
| 过度释放 | 对对象 release 次数超过 retain 次数 | 引用计数异常,崩溃 |
MRC 下对象释放后指针不会自动置 nil,需手动置 nil:
Person *person = [[Person alloc] init];
[person release];
person = nil; // 手动置 nil,避免野指针
三、引用计数的存储
引用计数并非简单地存在对象的某个字段中,其存储方式与架构相关:
1. arm64:nonpointer isa
在 64 位架构下,isa 指针是一个优化过的共用体(nonpointer isa),部分位域存储引用计数:
| 位域 | 说明 |
|---|---|
nonpointer |
1 表示优化过的 isa,0 表示原始指针 |
has_assoc |
是否有关联对象 |
has_cxx_dtor |
是否有 C++ 析构函数 |
shiftcls |
类指针(33 位) |
magic |
调试用魔数 |
weakly_referenced |
是否被 weak 引用 |
deallocating |
是否正在析构 |
has_sidetable_rc |
引用计数是否溢出到 SideTable |
extra_rc |
额外引用计数(存储引用计数 - 1) |
extra_rc 可存储部分引用计数,当超出容量时,has_sidetable_rc 置 1,溢出部分存储在 SideTable 中。
2. SideTable
SideTable 是一个全局的哈希表结构,存储:
- 引用计数表(
RefcountMap):对象地址 → 引用计数 - weak 引用表(
weak_table_t):对象地址 → 所有 weak 指针的地址
对象销毁时,Runtime 遍历 weak 表,将所有指向该对象的 weak 指针置 nil。
四、CoreFoundation 桥接
Objective-C 对象(Foundation)与 CoreFoundation 对象(CF)可以互相桥接,ARC 只管理 Foundation 对象,CF 对象需手动管理:
| 桥接关键字 | 所有权转移 | 说明 |
|---|---|---|
__bridge |
不转移 | 仅类型转换,双方各自管理 |
__bridge_transfer |
OC → CF | OC 对象所有权转移给 CF,ARC 不再管理,CF 端需 CFRelease |
__bridge_retained |
CF → OC | CF 对象 retain 一次交给 ARC 管理,CF 端仍需 CFRelease 自己的引用 |
// __bridge:不转移所有权
NSString *ocStr = @"Hello";
CFStringRef cfStr = (__bridge CFStringRef)ocStr;
// 双方都不 release 对方,各自管理
// __bridge_transfer:OC → CF,CF 端负责释放
CFStringRef cfStr = (__bridge_transfer CFStringRef)[[NSString alloc] initWithString:@"Hello"];
// 使用完后必须 CFRelease(cfStr)
// __bridge_retained:CF → OC,ARC 管理 OC 端
CFStringRef cfStr = CFStringCreateWithCString(kCFAllocatorDefault, "Hello", kCFStringEncodingUTF8);
NSString *ocStr = (__bridge_retained NSString *)cfStr;
CFRelease(cfStr); // CF 端释放自己的引用,ocStr 由 ARC 管理
五、ARC 与 MRC 对比
| 维度 | ARC | MRC |
|---|---|---|
| 管理方式 | 编译器自动插入 retain/release | 开发者手动调用 |
| 引入版本 | iOS 5+ | iOS 早期 |
| 出错概率 | 低(循环引用仍需手动处理) | 高(内存泄漏、野指针、过度释放) |
| 代码量 | 少 | 多(每个对象需跟踪 retain/release) |
| 性能 | 与 MRC 基本一致(编译器优化后) | 手动优化空间大 |
| dealloc | 不调用 [super dealloc],成员变量自动释放 |
必须调用 [super dealloc],手动释放属性 |
| weak 支持 | 支持(自动置 nil) | 不支持(只能用 assign/unsafe_unretained) |
| 与 Swift 兼容 | Swift 使用 ARC | 不兼容 |
混合编译
ARC 与 MRC 可以在同一项目中混合使用,通过编译标记控制:
-fobjc-arc:该文件使用 ARC-fno-objc-arc:该文件使用 MRC
在 Xcode 的 Build Phases → Compile Sources 中为特定文件添加编译标记即可。
六、Swift 中的 ARC
Swift 使用 ARC,与 Objective-C ARC 兼容。Swift 提供 weak 和 unowned 两种弱引用:
1. weak
class Person {
let name: String
weak var dog: Dog? // weak,对象销毁时自动置 nil
init(name: String) { self.name = name }
deinit { print("Person deinit") }
}
class Dog {
let name: String
weak var owner: Person? // weak 打破循环引用
init(name: String) { self.name = name }
deinit { print("Dog deinit") }
}
2. unowned
unowned 类似 unsafe_unretained,但更安全——不自动置 nil,但如果访问已释放的 unowned 引用会触发运行时断言(崩溃信息更明确)。适用于引用对象生命周期一定更长且不会为 nil 的场景:
class Customer {
let name: String
var card: CreditCard?
init(name: String) { self.name = name }
}
class CreditCard {
let number: String
unowned let customer: Customer // customer 生命周期更长,不会为 nil
init(number: String, customer: Customer) {
self.number = number
self.customer = customer
}
}
| 特性 | weak | unowned |
|---|---|---|
| 是否可选 | 必须是可选类型(?) |
可以是非可选 |
| 对象销毁时 | 自动置 nil | 不置 nil |
| 访问已释放对象 | 安全(nil) | 运行时断言崩溃 |
| 适用场景 | 引用可能为 nil | 引用一定不为 nil 且生命周期更长 |
3. 闭包循环引用
class ViewController {
var completion: (() -> Void)?
func setup() {
// [weak self] 捕获列表,避免循环引用
completion = { [weak self] in
guard let self = self else { return } // weak-strong dance
self.doSomething()
}
}
func doSomething() {}
}
七、总结
- ARC 是编译器特性(辅以运行时支持),自动插入 retain/release,是现代 iOS/Swift 开发的默认方式。仍需手动处理循环引用(delegate 用 weak、Block 用 weak-strong dance、NSTimer 用 weak target)。
- MRC 需手动管理引用计数,遵循黄金法则(alloc/new/copy 拥有的对象必须 release,其他方法获得的如需保持则 retain)。已被 ARC 取代,但理解 MRC 有助于理解 ARC 底层。
- 引用计数存储:arm64 下部分存储在 isa 的
extra_rc位域,溢出部分存储在全局SideTable;weak 引用表也在 SideTable 中,对象销毁时自动将 weak 指针置 nil。 - autoreleasepool:管理 autoreleased 对象的生命周期,系统在 RunLoop 循环中自动创建,循环内大量临时对象时可手动创建降低内存峰值。
- CoreFoundation 桥接:
__bridge(不转移)、__bridge_transfer(OC→CF)、__bridge_retained(CF→OC),CF 对象需手动CFRelease。 - Swift ARC:提供
weak(自动置 nil,可选)和unowned(不置 nil,非可选,生命周期更长),闭包用[weak self]捕获列表避免循环引用。

浙公网安备 33010602011771号