Objective-C 中的 ARC 与 AutoreleasePool
Objective-C 中的对象在引用计数为 0 的时候就会被释放,为了解决手动管理引用计数(MRC)容易发生内存泄漏或者过度释放的问题,引入了自动引用计数(ARC)。
引用计数什么情况下会被加一:
- 对象被强引用
引用计数什么情况下会被减一:
- 超出作用域
- 自动释放池在 RunLoop 中被释放的时候
正常情况下,对象如果是通过 init
家族函数(alloc
, copy
, init
, mutableCopy
, new
)创建的,那么返回值的引用计数会加一:
// init family
- (instancetype)init {
if (self = [super init]) {
}
return self;
}
在函数最后的 return self;
,编译器会让 self
的引用数加一,外部得到的对象就是 self
本身。
init
家族函数都有这个特性,是编译器保留的特性,所以在方法和属性命名时,应该避开使用这些前缀:copy
, init
, mutableCopy
, new
。
同时,以这些前缀开头的方法会被认为是 init
家族的一员:copyXX
, initXX
, mutableCopyXX
, newXX
。其中的 XX
必须是大写字母开头。
除了 init
家族之外的其他方法,返回的对象都会被放到自动释放池(AutoreleasePool)中:
+ (instancetype)defaultConfiguration {
return [[self alloc] init];
}
为了不让返回的对象析构,对象会被先放入自动释放池中再返回。
@interface Configuration : NSObject
@end
@implementation Configuration
- (void)dealloc {
NSLog(@"Configuration - dealloc");
}
+ (instancetype)defaultConfiguration {
return [[self alloc] init];
}
@end
测试一下 dealloc
方法调用的时机:
{
[Configuration defaultConfiguration]; // 返回对象在自动释放池中析构
}
可以看到对象析构的时候,调用栈是在 AutoreleasePool 中的。
如果改为 [[Configuration alloc] init]
那么析构时的调用栈就是代码所在作用域。
实际开发的时候,我们并不希望对象被放入自动释放池中延迟释放,那么可以使用 NS_RETURNS_RETAINED
标识,告诉编译器不要放入自动释放池:
+ (instancetype)defaultConfiguration NS_RETURNS_RETAINED {
return [[self alloc] init];
}
相应地,NS_RETURNS_NOT_RETAINED
标识则是让编译器把返回对象加入到自动释放池中。
对于 init
家族成员,默认值是 NS_RETURNS_RETAINED
。
对于其他方法和属性,默认值是 NS_RETURNS_NOT_RETAINED
。
Swift 中的 AutoreleasePool
如果以上代码,不加 NS_RETURNS_RETAINED
标识,在 Swift 中调用的时候,是否也会加入到 AutoreleasePool 中呢?
经过测试,答案是:不会。
因为 Swift 对 ARC 调用进行了优化处理,会把变量的生命周期尽量控制在作用域内。
Objective-C 中推荐的使用方式
- 对象的初始化方法定义遵循
init
家族的标准 - 对于类工厂方法如果不符合
init
家族的标准,要加上NS_RETURNS_RETAINED
参考资料
https://clang.llvm.org/docs/AutomaticReferenceCounting.html#retained-return-values
https://swiftrocks.com/autoreleasepool-in-swift
https://forums.swift.org/t/is-autorelease-no-longer-in-pure-swift-arc/33712/2