ARC 与 AutoreleasePool

Aug 27, 2022 • 预计阅读时间 2 分钟

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 中推荐的使用方式

  1. 对象的初始化方法定义遵循 init 家族的标准
  2. 对于类工厂方法如果不符合 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

Objective-CSwift
版权声明:如果转发请带上本文链接和注明来源。

lvv.me

iOS/macOS Developer

Xcode 13 中优化 Swift 对象的生命周期

使用 spctl 检查 App 的来源