如何创建自动释放对象

how to create an autorelease object

提问人:Wang.Ying 提问时间:2/4/2022 更新时间:4/4/2022 访问量:127

问:

此方法是否创建自动释放对象?

- (instancetype)autoreleasePerson {
    return [[Person alloc] init];
}

我创建了一个命令行工具项目来测试这一点:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        {
            [Person autoreleasePerson];
            
        }
        NSLog(@"did out scope");
        
        NSLog(@"will out autoreleasepool");
    }
    NSLog(@"did out autoreleasepool");
    return 0;
}

输出为:

2022-02-04 23:22:23.224298+0800 MyTest[8921:4007144] did out scope
2022-02-04 23:22:23.224771+0800 MyTest[8921:4007144] will out autoreleasepool
2022-02-04 23:22:23.224876+0800 MyTest[8921:4007144] -[Person dealloc]
2022-02-04 23:22:23.224948+0800 MyTest[8921:4007144] did out autoreleasepool

当 autoreleasepool 耗尽时,person 实例将解除锁定!

但是当我在我的 iOS APP 项目中使用相同的 Person 类时:

- (void)viewDidLoad {
    [super viewDidLoad];
    {
        [Person autoreleasePerson];
    }
    NSLog(@"out scope");
}

输出为:

2022-02-04 23:28:13.992969+0800 MyAppTest[9023:4011490] -[Person dealloc] <Person: 0x600001fe8ff0>
2022-02-04 23:28:13.993075+0800 MyAppTest[9023:4011490] out scope

person 实例在范围外释放一次!

为什么会这样?

iOS Objective-C nsAutoreleasePool

评论

0赞 MartinM 2/5/2022
这就是 autoreleasepools 的意义所在。请参阅文档:developer.apple.com/library/archive/documentation/Cocoa/...将其视为一个自己的范围,其中元素的生存时间可能比其范围更长。对于另一个代码段,它是合乎逻辑的,因为在范围末尾的保留计数为 0,因此 ARC 将导致 dealloc。这与 ARC 之前的时代相比有更长的历史,在那个时代,您必须手动触发释放(或使用这些自动释放帮助程序来提高性能)。
0赞 battlmonstr 2/7/2022
有趣。。。我预计不会。至少两者应该匹配。如果将其分配给命令行工具中的变量,是否相同?如果将调用移动到子功能,情况是否相同?
0赞 Wang.Ying 2/8/2022
@battlmonstr 是的,是一样的

答:

0赞 battlmonstr 2/8/2022 #1

在macOS上,默认行为是自动释放返回值,但方法名称以“new”、“init”或“copy”开头的情况除外:

+ (Person *)createPerson {
    return [Person new]; // autorelease & return
}
+ (Person *)newPerson {
    return [Person new]; // direct return
}

若要控制此行为,请应用编译器属性:

+ (Person *)createPerson __attribute__((ns_returns_retained)) {
    return [Person new]; // direct return
}
+ (Person *)newPerson __attribute__((ns_returns_not_retained)) {
    return [Person new]; // autorelease & return
}

若要检查编译器是否添加了对 objc_autoreleaseReturnValue 的调用,请启用 Debug -> Debug Workflow -> Always Show Disassembly, 并在这些方法中的返回行上放置一个断点。然后,对 objc_autoreleaseReturnValue 的调用应该可见:

enter image description here

请参阅 ARC 参考 - 保留的返回值

评论

0赞 Wang.Ying 2/8/2022
谢谢!我在 Person 类的 dealloc 方法中放置了一个断点,实例由函数释放。我也从你上面提到的文档中找到了这一点:**在这种方法的定义中没有强制执行额外的语义;它只是在调用者中启用优化**。所以我认为这是因为苹果做了一些优化。我说得对吗?libobjc.A.dylib`objc_unsafeClaimAutoreleasedReturnValue:
0赞 battlmonstr 2/8/2022
理论上 - 是的。如果它以某种方式知道该方法可以内联,则可以删除此调用。如果你想更深入地挖掘 - 你可以查看 clang 源代码(与 ARC 相关的优化)。它是开源的。
0赞 newacct 4/4/2022 #2

这两个结果都是有效的。永远不要假设 ARC 中有自动释放。请参阅 ARC 规范中的“未保留的返回值”部分:

返回可保留对象类型但返回的方法或函数 不返回保留值必须确保对象仍然有效 跨越返回边界。

从此类函数或方法返回时,ARC 会保留该值 在计算 return 语句时,然后保留所有 本地范围,然后平衡保留,同时确保 值存在于调用边界上。在最坏的情况下,这可能 涉及自动释放,但调用方不得假定该值为 实际上在自动释放池中。

所以也许它是自动发布的,也许不是(即也许 ARC 会优化它)。

在这里,ARC 在返回 时会调用 objc_autoreleaseReturnValue(),因为返回的是保留的引用,但返回的是非保留的引用。的作用是检查返回的结果是否会在调用函数帧中传递给。如果是这样,它可以跳过被调用函数中的自动释放和调用函数中的保留(因为它们“抵消”),并将所有权直接移交给调用函数中保留的引用。autoreleasePerson+allocautoreleasePersonobjc_autoreleaseReturnValue()objc_retainAutoreleasedReturnValue()

当 ARC 将保留函数调用的结果时,将调用 objc_retainAutoreleasedReturnValue()。现在,我不知道为什么在这种情况下调用会涉及结果的保留,因为结果未使用。也许编译器将其视为 ,因此保留然后释放它。这似乎没有必要,但 ARC 这样做是有效的。如果 ARC 确实碰巧在内部以这种方式处理它,那么上面描述的优化可以跳过自动释放和保留,它将在调用函数中简单地释放。也许它是在你的一个案例中这样做的,而不是在另一个案例中这样做的。谁知道为什么?但我的观点是,两者都是有效的。[Person autoreleasePerson];Person temp = [Person autoreleasePerson];

有关更详细的说明,请参阅此文章