在解除分配实例期间,预计不会出现 ARC 的奇怪行为

Not expected strange behaviour of ARC during deallocating instances

我正在更新我在 Objective-C 世界中的知识,现在我正在使用 __weak 局部变量测试一些 ARC。

我对此类文件的代码非常简单GAObject.h

#import <Foundation/Foundation.h>
@interface GAObject : NSObject
+ (instancetype)create;
@end

这个接口的实现GAObject.h

#import "GAObject.h"
@implementation GAObject
+ (instancetype)create {
    return [[GAObject alloc] init];
}
- (void)dealloc {
    NSLog(@"GAObject is being deallocated");
}
@end

所以有一个简单的工厂方法 create 并且我覆盖了 dealloc 方法来观察对象是否在我期望的时候被释放。现在是有趣的部分 main.m:

#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#import "Learning/GAObject.h"

int main(int argc, char * argv[]) {
    @autoreleasepool {
        NSLog(@"1");
        NSObject *o1 = [[GAObject alloc] init];
        NSObject * __weak weakObject = o1; // Line 1
        o1 = nil; // o1 should be deallocated because there is no strong references pointing to o1.
        NSLog(@"2");
        NSObject *o2 = [GAObject create]; // Line 2
        o2 = nil; // o2 should be deallocated here too but it is not deallocated. Why?
        NSLog(@"3");

        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

在输出中我看到了这个:

1
GAObject is being deallocated
2
3

但我的预期结果应该是:

1
GAObject is being deallocated
2
GAObject is being deallocated
3

如果我使用工厂方法创建 o2,那么我就会有这种行为。如果我像这样创建 o2[[GAObject alloc] init] 那么我会得到预期的输出。我还注意到,当我删除带有 weakObject 的行时,我也会得到预期的结果。有人可以解释一下吗?

这是因为 ARC 仍然遵守 Cocoa 内存管理命名约定。

根据这些约定,名为 +create return 的方法是 +0 引用。因此,在该方法的实现中,ARC 必须通过自动释放引用来平衡 alloc/init 对的 +1 引用。

然后,在 main() 中,ARC 必须假设它已从对 +create 的调用中收到 +0 引用。如果它需要引用在当前范围内存活,它会保留它,但它不会保留它。第二个 GAObject 实例将在自动释放池被耗尽时被释放,但这永远不会发生,因为 UIApplicationMain() 永远不会 returns。如果您使用两个单独的自动释放池,一个用于处理 GAObject 的代码,另一个用于调用 UIApplicationMain(),我希望您会得到您期望的结果。

如果 ARC 确实需要引用来生存,它会保留对强变量的赋值,并在该变量被分配新值(包括 nil)或超出范围时释放。 ARC 有一个 运行-time 优化,被调用者中的自动释放-return 和调用者中 returned 值的保留相互抵消,这样对象就永远不会被放置在自动释放池中。如果发生这种情况,您将获得预期的结果。

事实上,我的期望是即使在您的情况下,编译器最初也会发出保留和释放,但随后的传递会删除多余的保留和释放。您的示例在保留之后立即发布,这使得编译器更加明显地认为这对是多余的。因为保留被删除,自动释放优化不会启动,并且对您的对象的引用确实会被放入自动释放池中。

如果您的方法被命名为 +newGAObject,那么命名约定将意味着它 return 是一个 +1 引用,这一切都会改变。 (当然,就目前而言,你的 +create 方法只是做与内置 +new 方法相同的事情,除了 ARC 必须添加的自动释放。所以,你可以改变调用代码以使用 +new,这也将回避这个问题。)

我不知道为什么带有 weakObject 的那一行很重要。但是,由于您看到的行为取决于某些优化,任何可以调整优化的东西都可以改变结果。