1、iOS内存管理基础:引用计数与ARC原理
内存管理,说白了就是管好对象的生与死。
我刚入行那会儿,用的还是MRC(手动引用计数)。那时候写代码,retain、release、autorelease满天飞。稍不留神,要么内存泄漏,要么野指针崩溃。嗯,那段日子,确实挺痛苦的。
后来ARC来了,大家都说「解放了」。但你真的理解它吗?
1.1 引用计数:对象存活的「计数器」
每个OC对象内部,都有一个retainCount。它就像一张入场券。
- 对象创建时,retainCount = 1
- 别人想用这个对象,就retain一下,计数+1
- 用完了,就release一下,计数-1
- 当计数归0,对象就被销毁,内存回收
举个例子:
// MRC时代的手动管理
Person *p = [[Person alloc] init]; // retainCount = 1
[p retain]; // retainCount = 2
[p release]; // retainCount = 1
[p release]; // retainCount = 0 → 对象销毁
你想想看,如果少写了一个release,对象就永远活着,内存泄漏。如果多写了一个release,对象提前挂了,后面再访问就是野指针崩溃。
1.2 ARC:编译器替你写retain/release
ARC不是垃圾回收,它是在编译阶段,自动插入retain、release、autorelease。
说白了,ARC就是帮你干苦力活。但它的规则,你必须懂。
ARC的核心规则就三条:
- 强引用(strong):持有对象,引用计数+1。默认就是strong。
- 弱引用(weak):不持有对象,引用计数不变。对象销毁时,weak指针自动置nil。
- 无主引用(unowned):类似weak,但不自动置nil。对象销毁后,访问它会崩溃。
💡 我个人习惯:
能用weak,就别用unowned。除非你能100%确定,对象的生命周期比你长。我在项目中遇到过几次unowned导致的崩溃,后来全改成weak了,省心。
1.3 自动释放池:延迟释放的「垃圾桶」
有些对象,你不想立刻释放,想让它多活一会儿。这时候就用autorelease。
// ARC下,你不需要手动写autorelease
// 但你可以看到它的效果
- (NSString *)createString {
NSString *str = [NSString stringWithFormat:@"Hello %d", i];
// 这个str是autorelease的
return str;
}
为什么要有autorelease?
因为方法返回的对象,调用者需要用它。如果方法内部直接release了,调用者拿到的是个野指针。所以,把它放进自动释放池,等调用者用完再释放。
自动释放池的机制是这样的:
- 每个RunLoop循环结束时,会销毁当前的自动释放池
- 池里的所有对象,都会收到一次release消息
- 如果对象的引用计数归0,就销毁
🔧 避坑指南:
我曾经在for循环里创建了大量临时字符串,没有加@autoreleasepool。结果循环跑完之前,内存暴涨。后来加了个池子,内存就稳了。
// 错误写法:大量临时对象堆积
for (int i = 0; i < 100000; i++) {
NSString *str = [NSString stringWithFormat:@"%d", i];
// 处理str...
}
// 正确写法:及时释放
for (int i = 0; i < 100000; i++) {
@autoreleasepool {
NSString *str = [NSString stringWithFormat:@"%d", i];
// 处理str...
}
}
1.4 循环引用:最常见的「内存泄漏」元凶
ARC虽然帮你管理了引用计数,但它管不了循环引用。
什么叫循环引用?A强引用B,B强引用A。两个对象互相持有,引用计数永远不为0。它们就永远活着,泄漏了。
最常见的场景:block和delegate。
// 循环引用示例
self.myBlock = ^{
[self doSomething]; // block强引用self,self强引用block
};
// 正确写法:使用weakSelf
__weak typeof(self) weakSelf = self;
self.myBlock = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
[strongSelf doSomething];
};
为什么block里要用weakSelf?
因为block会捕获外部变量。如果block被self持有,block内部又强引用self,就形成了闭环。
📌 记住这个口诀:
「block里用weak,delegate用weak,NSTimer用weak」
这三个地方,是循环引用的高发区。我每次写代码,都会下意识检查一遍。
1.5 总结:ARC不是万能的
ARC帮我们省去了手动管理引用计数的麻烦。但它不是银弹。
- 你仍然需要理解引用计数的原理
- 你仍然需要小心循环引用
- 你仍然需要关注自动释放池的时机
我个人觉得,理解ARC的最好方式,就是把它当成「一个帮你写retain/release的编译器插件」。它的规则是固定的,你只要遵守规则,它就不会坑你。
下一章,我们会深入讲「内存泄漏的检测与定位」。到时候我会分享一些Xcode的调试技巧,还有我在线上环境排查内存问题的实战经验。
嗯,先消化这些吧。