在哪些情况下我们需要在 ARC 下编写 __autoreleasing 所有权限定符?

Posted

技术标签:

【中文标题】在哪些情况下我们需要在 ARC 下编写 __autoreleasing 所有权限定符?【英文标题】:In which situations do we need to write the __autoreleasing ownership qualifier under ARC? 【发布时间】:2012-02-10 07:58:45 【问题描述】:

我正在尝试完成这个谜题。

__strong 是所有 Objective-C 可保留对象指针(如 NSObject、NSString 等)的默认值。它是一个强引用。 ARC 在作用域末尾用-release 平衡它。

__unsafe_unretained 等于旧方式。用于不保留可保留对象的弱指针。

__weak__unsafe_unretained 类似,只是它是一个自动归零的弱引用,这意味着一旦引用的对象被释放,指针就会被设置为 nil。这消除了悬空指针和 EXC_BAD_ACCESS 错误的危险。

但是__autoreleasing 到底有什么用呢?我很难找到关于何时需要使用此限定符的实际示例。我相信它仅适用于需要指针指针的函数和方法,例如:

- (BOOL)save:(NSError**);

NSError *error = nil;
[database save:&error];

在 ARC 下必须这样声明:

- (BOOL)save:(NSError* __autoreleasing *);

但这太模糊了,我想完全理解为什么。我发现的代码 sn-ps 将 __autoreleasing 放在两颗星之间,这对我来说看起来很奇怪。类型是NSError**(指向NSError 的指针),那么为什么要将__autoreleasing 放在星星之间而不是简单地放在NSError** 前面呢?

另外,在其他情况下我可能必须依赖__autoreleasing

【问题讨论】:

我有同样的问题,下面的答案并不完全令人信服......例如,为什么系统不提供接口来接受像你这样的 __autoreleasing 装饰器声明的 NSError** 参数和过渡到 Arc 发行说明说他们应该是?例如,NSFileManager.h 中的许多这些例程中的任何一个?? 【参考方案1】:

你是对的。正如官方文档解释的那样:

__autoreleasing 表示通过引用 (id *) 传递并在返回时自动释放的参数。

所有这些都在ARC transition guide 中得到了很好的解释。

在您的 NSError 示例中,声明意味着 __strong,隐含:

NSError * e = nil;

将转化为:

NSError * __strong error = nil;

当您调用 save 方法时:

- ( BOOL )save: ( NSError * __autoreleasing * );

然后编译器必须创建一个临时变量,设置为__autoreleasing。所以:

NSError * error = nil;
[ database save: &error ];

将转化为:

NSError * __strong error = nil;
NSError * __autoreleasing tmpError = error;
[ database save: &tmpError ];
error = tmpError;

您可以通过直接将错误对象声明为__autoreleasing 来避免这种情况。

【讨论】:

不,__autoreleasing 仅用于通过引用传递的参数。这是一种特殊情况,因为您有一个指向对象指针的指针。便利构造函数之类的东西并非如此,因为它们只返回一个指向对象的指针,而 ARC 会自动处理它。 为什么 __autoreleasing 限定符放在星星之间,而不是放在 NSError** 前面?这对我来说看起来很奇怪,因为类型是 NSError**。还是因为这试图表明指向的 NSError* 指针必须被限定为指向自动释放的对象? @Proud Member 关于您的第一条评论 - 这是不正确的(如果我理解正确的话) - 请参阅下面的 Glen Low 的回答。错误对象被创建并分配给一个自动释放变量(你传入的那个)inside save 函数。此分配导致对象在当时被保留和自动释放。 save 函数的声明阻止我们向它发送除自动释放变量以外的任何内容,因为这是它所需要的 - 这就是为什么如果我们尝试编译器会创建一个临时变量。 那么为什么苹果的界面似乎都没有这个?例如,NSFileManager.h 中的所有内容? @Macmade:碰巧我注意到您的答案已被编辑(***.com/users/12652/comptrol),我的印象是至少对您的第一个示例进行了更改(“隐式......转换为 ...) 是错误的,因为 __strong 限定符已从第二行移至第一行。也许您可以检查一下。【参考方案2】:

在 cmets 中跟进 Macmade 的回答和 Proud Member 的后续问题,(也会将此作为评论发布,但它超过了最大字符数):

这就是为什么 __autoreleasing 的变量限定符放在两颗星之间的原因。

首先,用限定符声明对象指针的正确语法是:

NSError * __qualifier someError;

编译器会原谅这个:

__qualifier NSError *someError;

但这是不正确的。请参阅the Apple ARC transition guide(阅读以“您应该正确地装饰变量...”开头的部分)。

解决手头的问题:双指针不能有 ARC 内存管理限定符,因为指向内存地址的指针是指向原始类型的指针,而不是指向对象的指针。但是,当您声明双指针时,ARC 确实想知道第二个指针的内存管理规则是什么。这就是为什么双指针变量被指定为:

SomeClass * __qualifier *someVariable;

所以在方法参数是双 NSError 指针的情况下,数据类型声明为:

- (BOOL)save:(NSError* __autoreleasing *)errorPointer;

在英文中是“指向 __autoreleasing NSError 对象指针的指针”。

【讨论】:

谢谢。这就是我需要的答案,您可以设置“因为指向内存地址的指针是指向原始类型的指针,而不是指向对象的指针”。用粗体表示,这是事情的核心。【参考方案3】:

definitive ARC specification 这么说

对于 __autoreleasing 对象,新指针被保留、自动释放,并使用原始语义存储到左值中。

例如,代码

NSError* __autoreleasing error = someError;

实际上被转换为

NSError* error = [[someError retain] autorelease];

...这就是为什么当你有一个参数NSError* __autoreleasing * errorPointer 时它可以工作的原因,然后被调用的方法会将错误分配给*errorPointer 并且上面的语义将起作用。

您可以在不同的上下文中使用__autoreleasing 来强制将 ARC 对象放入自动释放池,但这并不是非常有用,因为 ARC 似乎只在方法返回时使用自动释放池并且已经自动处理了。

【讨论】:

【参考方案4】:

简而言之:这只是为了与 MRC 兼容。

Apple 已同意在自己的库中** 返回的对象始终是自动释放的。所以ARC 代码可以与旧的二进制文件一起正常工作(例如,如果您的部署目标是 ios 4),反之亦然,MRC 代码也可以与ARC 二进制文件一起正常工作。

总之:

你不应该使用__autoreleasing:编译器会在需要的地方自动添加它

如果你不打算支持 MRC 代码,那么你应该在任何地方使用* __strong *。它将从家庭崩溃中拯救:

@autoreleasingpool 
   *autorelesingOut = [@"crash maker" mutableCopy];//NSString * __autoreleasing *autorelesingOut;
   *strongOut = [@"it's ok" mutableCopy];//NSString * __strong *strongOut;
   //App will crash if autorelesingOut will be referenced outside of this autoreleasepool

【讨论】:

以上是关于在哪些情况下我们需要在 ARC 下编写 __autoreleasing 所有权限定符?的主要内容,如果未能解决你的问题,请参考以下文章

ARC 引入了哪些新的类型限定符?

对 ARC 下对象的弱引用 (__unsafe_unretained) 的 NSArray

block在arc中和mrc中的用法有啥区别

内存管理的思考方式2(ARC下)

我可以在没有 CURSOR 的情况下编写此查询吗?

[ARC121]Editorial