问题 什么时候需要NS_RETURNS_RETAINED?


以下面的例子为例:

- (NSString *)pcen NS_RETURNS_RETAINED {
    return (__bridge_transfer NSString *)CFURLCreateStringByAddingPercentEscapes(NULL, (__bridge CFStringRef) self, NULL, (CFStringRef) @"!*'();:@&=+$,/?%#[]", kCFStringEncodingUTF8);
}

把它放在一边是否正确? NS_RETURNS_RETAINED 那里?


另一个例子:

+ (UIImage *)resizeImage:(UIImage *)img toSize:(CGSize)size NS_RETURNS_RETAINED {
    UIGraphicsBeginImageContextWithOptions(size, NO, 0.0);
    [img drawInRect:...];
    UIImage *resizedImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return resizedImage;
}

这似乎更复杂,因为返回的UIImage是“Get”方法的结果。但是,它所获得的图形上下文是在方法范围内创建的,所以它也是正确的 NS_RETURNS_RETAINED 这里?


第三个例子:

@property (readonly) NSArray *places;
---
@synthesize places=_places;
---
- (NSArray *)places {
    if (_places)
        return _places;
    return [[NSArray alloc] initWithObjects:@"Unknown", nil];
}

不知道该怎么做,因为返回的对象可以是新创建的。


还有最后一个问题;想必 NS_RETURNS_RETAINED 如果返回的对象是自动释放方法的结果,则不需要。所以说最后一个例子的回报被修改为

return [NSArray arrayWithObject:@"Unknown"];

什么是最佳实践呢?


4249
2017-08-27 03:34


起源



答案:


第一个例子

将NS_RETURNS_RETAINED放在那里是否正确?

它是 不正确  - 这里没有必要的属性。添加属性将违反命名约定,这非常重要。

更详细地说,因为引用是,所以不需要任何属性 转入 在示例中使用 (__bridge_transfer NSString*)。有人可能会认为CFCreated-Reference可能需要更多东西,但是 (__bridge_transfer NSString*) 就是将该引用转移到ARC所需的一切;为它管理你。

如果你要使用typecasted (__bridge NSString*)CF_*_Create_*_,那么CFCreate函数返回的引用将不会转移到ARC,并且会引入泄漏。

作为替代方案,如果您选择明确释放返回的字符串,则可以避免泄漏(例如,使用 CFRelease)。

第二个例子

但是,它所获得的图形上下文是在方法范围内创建的,所以在这里使用NS_RETURNS_RETAINED也是正确的吗?

使用属性不正确或不必要。 ARC使用命名约定和属性来确定要添加的引用计数操作 - 它了解您的程序。

与第一个例子不同,一个明确的 __bridge_transfer 不应该做。

添加属性会破坏命名约定。

第三个例子

- (NSArray *)places 
...

不知道该怎么做,因为返回的对象可以是新创建的。

同样,不应使用任何属性。一个明确的 __bridge_transfer 不应该做。 ARC了解ObjC约定,包括返回现有和新创建的对象。它将为两个路径插入正确的引用计数操作。

还有最后一个问题;如果返回的对象是autorelease'ed方法的结果,则可能不需要NS_RETURNS_RETAINED。所以说最后一个例子的回报被修改为

return [NSArray arrayWithObject:@"Unknown"];

同样,不需要任何属性。不应进行明确的转移。

所有系统库中只存在少数几个属性用途。


我真的,真的,真的,真的建议不要使用这些属性,特别是:

  • 涉及动态调度的地方(所有objc方法都符合条件)
  • 参数(消耗)和结果(保留的返回)是ObjC类型

理由是参考转移应该是实施的本地转移,并且很少有真正的需要偏离它;向后兼容性可能是我能想到的“最佳”原因。如果您可以控制您的代码,只需更新它以尽可能做正确的事情,而不是引入这些属性。这可以通过遵守命名约定,并在适当的时候将引用转移到ARC来实现。

有关错误的一些示例,您可能会遇到使用属性并偏离命名约定,请参阅: 字典的深层副本在Xcode 4.2中给出了分析错误 。

坚持使用命名约定的另一个好理由是,您并不总是知道如何使用您的程序。如果有人想在MRC翻译中使用你的程序,那么他们将不得不编写如下所示的异常程序:

某个地方

- (NSString *)name NS_RETURNS_RETAINED;

别处

NSString * name = obj.name;
NSLog(@"%@", name);
[name release]; // << ME: not a mistake. triple checked.

2
2017-08-27 06:17



非常感谢你清理这一切。出于兴趣,在什么情况下会使用NS_RETURNS_RETAINED? - Max
@Alec你很受欢迎。如果您按照惯例遵守惯例并遵守规则,我们中的许多人将永远不需要使用此属性。我已经提到了向后兼容性(也就是说,如果你想保持一个不符合Apple命名约定的设计)。 Apple的框架中也有一些有趣的用途; self - 在无归档和 NSMakeCollectable (一个垃圾收集添加,它也有一个消费属性) - 这几乎是所有iOS框架中的一切。 (续) - justin
(续)我在一些(非常)内部函数(都使用静态调度)中使用consume属性,以便在初始化和所有权'take'期间进行漏斗。总的来说,这些属性非常难以使用并且非常内部。 - justin
@Alec - 虽然最终的推荐是有效的,但我相信这个答案中给出的解释是正确的 错误对不起贾斯汀没有足够的空间来解释为什么在这里,我将作为一个单独的答案添加。 - CRD
@CRD去吧 - 直到我学会了东西后才能入睡:) - justin


答案:


第一个例子

将NS_RETURNS_RETAINED放在那里是否正确?

它是 不正确  - 这里没有必要的属性。添加属性将违反命名约定,这非常重要。

更详细地说,因为引用是,所以不需要任何属性 转入 在示例中使用 (__bridge_transfer NSString*)。有人可能会认为CFCreated-Reference可能需要更多东西,但是 (__bridge_transfer NSString*) 就是将该引用转移到ARC所需的一切;为它管理你。

如果你要使用typecasted (__bridge NSString*)CF_*_Create_*_,那么CFCreate函数返回的引用将不会转移到ARC,并且会引入泄漏。

作为替代方案,如果您选择明确释放返回的字符串,则可以避免泄漏(例如,使用 CFRelease)。

第二个例子

但是,它所获得的图形上下文是在方法范围内创建的,所以在这里使用NS_RETURNS_RETAINED也是正确的吗?

使用属性不正确或不必要。 ARC使用命名约定和属性来确定要添加的引用计数操作 - 它了解您的程序。

与第一个例子不同,一个明确的 __bridge_transfer 不应该做。

添加属性会破坏命名约定。

第三个例子

- (NSArray *)places 
...

不知道该怎么做,因为返回的对象可以是新创建的。

同样,不应使用任何属性。一个明确的 __bridge_transfer 不应该做。 ARC了解ObjC约定,包括返回现有和新创建的对象。它将为两个路径插入正确的引用计数操作。

还有最后一个问题;如果返回的对象是autorelease'ed方法的结果,则可能不需要NS_RETURNS_RETAINED。所以说最后一个例子的回报被修改为

return [NSArray arrayWithObject:@"Unknown"];

同样,不需要任何属性。不应进行明确的转移。

所有系统库中只存在少数几个属性用途。


我真的,真的,真的,真的建议不要使用这些属性,特别是:

  • 涉及动态调度的地方(所有objc方法都符合条件)
  • 参数(消耗)和结果(保留的返回)是ObjC类型

理由是参考转移应该是实施的本地转移,并且很少有真正的需要偏离它;向后兼容性可能是我能想到的“最佳”原因。如果您可以控制您的代码,只需更新它以尽可能做正确的事情,而不是引入这些属性。这可以通过遵守命名约定,并在适当的时候将引用转移到ARC来实现。

有关错误的一些示例,您可能会遇到使用属性并偏离命名约定,请参阅: 字典的深层副本在Xcode 4.2中给出了分析错误 。

坚持使用命名约定的另一个好理由是,您并不总是知道如何使用您的程序。如果有人想在MRC翻译中使用你的程序,那么他们将不得不编写如下所示的异常程序:

某个地方

- (NSString *)name NS_RETURNS_RETAINED;

别处

NSString * name = obj.name;
NSLog(@"%@", name);
[name release]; // << ME: not a mistake. triple checked.

2
2017-08-27 06:17



非常感谢你清理这一切。出于兴趣,在什么情况下会使用NS_RETURNS_RETAINED? - Max
@Alec你很受欢迎。如果您按照惯例遵守惯例并遵守规则,我们中的许多人将永远不需要使用此属性。我已经提到了向后兼容性(也就是说,如果你想保持一个不符合Apple命名约定的设计)。 Apple的框架中也有一些有趣的用途; self - 在无归档和 NSMakeCollectable (一个垃圾收集添加,它也有一个消费属性) - 这几乎是所有iOS框架中的一切。 (续) - justin
(续)我在一些(非常)内部函数(都使用静态调度)中使用consume属性,以便在初始化和所有权'take'期间进行漏斗。总的来说,这些属性非常难以使用并且非常内部。 - justin
@Alec - 虽然最终的推荐是有效的,但我相信这个答案中给出的解释是正确的 错误对不起贾斯汀没有足够的空间来解释为什么在这里,我将作为一个单独的答案添加。 - CRD
@CRD去吧 - 直到我学会了东西后才能入睡:) - justin


[这个答案部分是对贾斯汀给出的答案的长期评论/修正。之前的回答让我相信对属性和ARC如何处理返回引用的语义的错误描述。

答案在于ARC分析的工作原理和意义 NS_RETURNS_RETAINED

ARC分析您的源以确定何时保留,释放或自动释放可保留的对象引用。

如果 您的应用程序的所有源代码都可用,理论上,分析可能能够从“第一原则”确定此信息 - 从最小的表达式开始并向外工作。

然而 所有来源都不可用 - 例如有些已经在框架等中编译了 - 因此在分析方法时,ARC不会查看方法的来源,而只会查看其签名 - 其名称及其参数类型和返回值。

考虑到可保留对象类型的返回值,ARC需要知道是否正在转移所有权 - 在这种情况下ARC将需要 发布 它在某个时刻 - 或者不是(例如 自动释放 参考) - 在这种情况下ARC将需要 保留 如果需要所有权。

ARC根据此信息确定此信息 名称 方法和任何属性。方法从 init 要么 new 或含有 copy 根据定义,转让所有权;所有其他方法都没有。属性 NS_RETURNS_RETAINED 告知ARC一个方法,无论其名称如何,都会转移其返回引用的所有权。

那是故事的一半......另一半是ARC处理的问题 return 方法体中的语句。

一个 return 实际上是一种赋值,当执行可保留对象引用赋值时,ARC根据其对当前所有权和引用以及目标要求的了解,确定引用是否需要保留,自动释放或保留。

为一个 return 声明目的地的要求,毫不奇怪,由方法的名称和签名上指定的任何属性决定。如果签名表明所有权正在转移,那么ARC将返回一个 保留 参考,否则它将返回 自动释放 一。

重要的是要理解ARC正在进行方法调用的两个方面,它确保返回适当的引用  确定如何处理返回的引用。

通过所有前言,我们可以看看你的第一个例子。看起来你正在写一个方法 NSString,所以我们将添加该细节,首先我们将省略该属性:

@interface NSString (AddingPercentEscapes)

- (NSString *) pcen;

@end

@implementation NSString (AddingPercentEscapes)

- (NSString *) pcen
{
   return (__bridge_transfer NSString *)CFURLCreateStringByAddingPercentEscapes(NULL, (__bridge CFStringRef) self, NULL, (CFStringRef) @"!*'();:@&=+$,/?%#[]", kCFStringEncodingUTF8);
}

@end

并轻而易举地使用它:

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
   NSString *test = @"This & than > other";

   NSLog(@"pcen: %@", [test pcen]);
}

编译时 pcen 方法 return 声明ARC查看签名,名称(pcen)并不表示所有权的转移,也没有属性,因此ARC增加了一个 autorelease 表达式返回的引用 (__bridge_transfer NSString *) ... kCFStringEncodingUTF8) 因为该表达式返回所拥有的引用 pcen

重要:  什么 表达式并不重要,只是表达式 pcen 拥有它保留的参考 - 特别是 __bridge_transfer 不确定方法返回的引用的所有权。

编译调用时 pcen 在里面 applicationDidFinishLaunching 方法ARC再次查看签名,确定当前方法需要所有权,并且返回的引用不是所有者并插入一个 retain

您可以通过在Xcode中调用“Product> Generate Output> Assembly File”来验证这一点,在生成的程序集中,您将在代码中看到 pcen 类似的东西:

callq   _CFURLCreateStringByAddingPercentEscapes
movq    %rax, %rdi
callq   _objc_autoreleaseReturnValue
addq    $16, %rsp
popq    %rbp
ret

它显示了ARC插入的自动释放,以及组装中的自动释放 applicationDidFinishLaunching 类似的东西:

callq   _objc_msgSend
movq    %rax, %rdi
callq   _objc_retainAutoreleasedReturnValue

这是对的呼唤 pcen 然后是ARC插入保留。

因此,如果没有注释,您的示例工作正常,ARC会做正确的事情。但是它也适用于注释,让我们将界面更改为:

@interface NSString (AddingPercentEscapes)

- (NSString *) pcen NS_RETURNS_RETAINED;

@end

运行(和分析)此版本,它也可以工作。但是生成的代码已经改变,ARC确定它应该根据属性的存在来转移所有权,所以汇编为 return 声明变成:

callq   _CFURLCreateStringByAddingPercentEscapes
addq    $16, %rsp
popq    %rbp
ret

ARC确实如此  插入自动释放。在呼叫站点,组件变为:

callq   _objc_msgSend
movq    -40(%rbp), %rdi         ## 8-byte Reload
movq    %rax, %rsi
movq    %rax, -48(%rbp)         ## 8-byte Spill
movb    $0, %al
callq   _NSLog

ARC这样做  插入一个保留。

所以这两个版本都是“正确的”,但哪个更好?

可能看起来带有属性的版本更好,因为ARC不需要插入自动释放/保留;但是运行时优化了这个序列(因此调用了 _objc_retainAutoreleasedReturnValue 而不是像 _objc_retain所以成本并不像看上去那么大。

然而 正确的答案是 也不...

建议的解决方案是依赖Cocoa / ARC约定并更改方法的名称,例如:

@interface NSString (AddingPercentEscapes)

- (NSString *) newPercentEscapedString;

@end

以及相关的变化。

这样做,你会得到相同的代码 pcen NS_RETURNS_RETAINED因为ARC决定它应该基于的转移所有权 名称  new...

这个答案已经很久了,希望上面的内容可以帮助你找出其他两个例子的答案!


8
2017-08-27 21:19



谢谢CRD,非常非常翔实的答案。关于你的推荐 new... 命名约定,它看起来像Cocoa方法 stringByAppendingString: 别。怎么来的? - Max
也可能是一个纠正: Methods starting with init or new or containing copy transfer, by definition, ownership; all other methods do not. 不是吗 alloc, new 并包含 copy? - Max
@Alec - new... 与 string... (一般来说 <classname>...) 类 方法。这些约定在ARC之前。前者是类方法的约定 alloc & init;后者适用于那些 alloc, init 和 autorelease。在你的例子中,你有一个 例 创建新对象的方法。要让ARC自动转移所有权,该方法需要位于init,new或copy系列之一。所以我选择了 newPercentEscapedString, 也许 copyWithPercentEscapes 本来可以是一个更好的名字,随便挑选! - CRD
@Alec - 重新 alloc。正确, alloc 确实返回被调用者拥有的引用。但是,列表中通常没有提到它。一个 init 方法 消耗 (即取得所有权)其论点(来自 alloc)并返回被调用者拥有的引用 - 因此它在列表中。 [注意:不能保证 init 返回它传递的相同引用,因此*消耗  - 如果它返回一个不同的引用,则释放一个引用。类如 NSNumber 可能会这样做,例如从传递相同值的不同调用返回相同的引用。] * - CRD
@CRD那么 特别 在我的回答是“错误的”? - justin