在ARC下,我们需要在哪些情况下使用__autoreleasing所有权限定符?

126

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

__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 *);

但是这太模糊了,我想完全理解为什么。我找到的代码片段在两个星号之间放置了 __autoreleasing,这对我来说看起来很奇怪。类型是NSError**(指向指针的指针),那么为什么不把 __autoreleasing 直接放在 NSError** 前面呢?

此外,可能还有其他情况需要依赖于 __autoreleasing


1
我也有同样的问题,下面的答案并不完全令人信服...例如,为什么系统提供的接口不像您和“过渡到Arc发布说明”所说的那样使用__autoreleasing修饰符声明带有NSError **参数的接口?例如,在NSFileManager.h中的许多例程之一? - Dad
4个回答

70

你说得对。正如官方文档所解释的那样:

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

所有这些都在ARC过渡指南中有很好的解释。

在您的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来避免这种情况。


3
不,__autoreleasing 只用于传递引用的参数。这是一个特殊情况,因为你有一个指向对象指针的指针。但像方便构造函数之类的东西则不是这种情况,因为它们只返回一个指向对象的指针,并且由 ARC 自动处理。 - Macmade
7
为什么 __autoreleasing 限定符要放在星号之间,而不是仅放在 NSError** 前面?对我来说,这看起来很奇怪,因为类型是 NSError*。或者这是因为它试图表明所指向的 NSError 指针必须被限定为指向一个自动释放的对象? - Proud Member
1
关于您的第一个评论,@Proud Member - 如果我理解正确的话 - 那是不正确的。请参见下面的 Glen Low 的答案。错误对象是在 save 函数内部创建并分配给可自动释放变量(即您传入的变量)。这个赋值导致对象在那时被保留并自动释放。save 函数的声明防止我们向其发送除可自动释放变量以外的任何东西,因为它需要的就是这个 - 这就是为什么编译器如果我们尝试会创建一个临时变量的原因。 - Colin
2
那么为什么似乎没有任何苹果接口有这个功能呢?例如,NSFileManager.h 中的所有内容? - Dad
1
@Macmade:恰巧我注意到你的回答已被编辑(由http://stackoverflow.com/users/12652/comptrol),我有印象至少对于你的第一个例子(“隐式地...将被转换为...”)的更改是错误的,因为__strong限定符已从第二行移动到第一行。也许你可以检查一下。 - Martin R
显示剩余6条评论

38

继承自Macmade的答案,以及Proud Member在评论区的追问(如果我写成评论会超出最大字符数):

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

首先,声明带有限定符的对象指针的正确语法如下:

NSError * __qualifier someError;
编译器会原谅这个:
__qualifier NSError *someError;

但这是不正确的。请参阅Apple ARC转换指南(阅读以“您应该正确修饰变量...”开头的部分)。

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

SomeClass * __qualifier *someVariable;

因此,在参数为双倍NSError指针的方法中,数据类型声明为:

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

这段英文的意思是“指向一个__autoreleasing NSError对象指针”。


谢谢。那就是我需要的答案,而且你可以将“因为指向内存地址的指针是指向原始类型而不是对象的指针”加粗,这是关键所在。 - Motti Shneor

16

definitive ARC specification指出:

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

因此,例如以下代码:

NSError* __autoreleasing error = someError;

实际上被转换为

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

为什么当你有一个参数 NSError* __autoreleasing * errorPointer 时它起作用,调用的方法将会把错误分配给 *errorPointer,然后上面的语义就会发挥作用。

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


0
简而言之:这仅是为了与MRC兼容性。
苹果公司已经达成协议,自己的库中返回的对象始终是自动释放的。因此,ARC代码将与旧二进制文件(例如,如果您的部署目标是iOS 4)正常工作,反之亦然,MRC代码将与ARC二进制文件正常工作。
因此,总之:
  • 永远不应该使用__autoreleasing:编译器会在需要时自动添加它

  • 如果您不打算支持MRC代码,则应该在任何地方使用* __strong *。这将避免家族崩溃:

    @autoreleasingpool {
       *autorelesingOut = [@"crash maker" mutableCopy];//NSString * __autoreleasing *autorelesingOut;
       *strongOut = [@"it's ok" mutableCopy];//NSString * __strong *strongOut;
       //如果在此autorelease池之外引用autorelesingOut,应用程序将崩溃
    }
    

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接