什么时候需要NS_RETURNS_RETAINED?

Max*_*Max 11 memory-management reference-counting objective-c ios automatic-ref-counting

以下面的例子为例:

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

把它放在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;
}
Run Code Online (Sandbox Code Playgroud)

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


第三个例子:

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

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


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

return [NSArray arrayWithObject:@"Unknown"];

什么是最佳实践呢?

CRD*_*CRD 14

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

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

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

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

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

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

ARC根据方法名称和任何属性确定此信息.开始方法initnew或含有copy转移,根据定义,所有权; 所有其他方法都没有.该属性NS_RETURNS_RETAINED通知ARC,方法(无论其名称如何)都会转移其返回引用的所有权.

这是故事的一半......另一半是ARC如何处理return方法体中的语句.

A 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
Run Code Online (Sandbox Code Playgroud)

并轻而易举地使用它:

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

   NSLog(@"pcen: %@", [test pcen]);
}
Run Code Online (Sandbox Code Playgroud)

当编译pcen方法return声明ARC看签名,姓名(pcen)不表明所有权的转移,并没有属性,所以ARC增加了一个autorelease表达式返回引用的(__bridge_transfer NSString *) ... kCFStringEncodingUTF8)作为表达式返回所拥有的参考pcen.

重要提示: 表达式的含义并不重要,只是pcen拥有它保留的引用 - 特别__bridge_transfer是不确定方法返回的引用的所有权.

pcenapplicationDidFinishLaunching方法ARC中编译调用再次查看签名时,确定当前方法需要所有权并且返回的引用不是所有者并插入a retain.

您可以通过在Xcode中调用"Product> Generate Output> Assembly File"来验证这一点,在生成的程序集中,您将在代码中看到以下pcen内容:

callq   _CFURLCreateStringByAddingPercentEscapes
movq    %rax, %rdi
callq   _objc_autoreleaseReturnValue
addq    $16, %rsp
popq    %rbp
ret
Run Code Online (Sandbox Code Playgroud)

它显示了ARC插入的自动释放,并在程序集中显示了以下applicationDidFinishLaunching内容:

callq   _objc_msgSend
movq    %rax, %rdi
callq   _objc_retainAutoreleasedReturnValue
Run Code Online (Sandbox Code Playgroud)

这是调用pcen后跟ARC插入保留.

所以你的例子在没有注释的情况下工作正常,ARC做正确的事情.但是它也适用于注释,让我们将界面更改为:

@interface NSString (AddingPercentEscapes)

- (NSString *) pcen NS_RETURNS_RETAINED;

@end
Run Code Online (Sandbox Code Playgroud)

运行(和分析)此版本,它也有效.但是生成的代码已经更改,ARC确定它应该根据属性的存在转移所有权,因此该return语句的程序集变为:

callq   _CFURLCreateStringByAddingPercentEscapes
addq    $16, %rsp
popq    %rbp
ret
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)

这里ARC 没有插入保留.

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

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

然而,正确的答案既不是 ......

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

@interface NSString (AddingPercentEscapes)

- (NSString *) newPercentEscapedString;

@end
Run Code Online (Sandbox Code Playgroud)

以及相关的变化.

执行此操作,您将获得与pcen NS_RETURNS_RETAINEDARC 相同的代码,因为它确定应根据名称 转移所有权new....

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


jus*_*tin 4

第一个例子

将 NS_RETURNS_RETAINED 放在那里是否正确?

这是不正确的——这里不需要任何属性。添加该属性会违反命名约定,而遵循命名约定非常重要。

更详细地说,不需要属性,因为在示例中使用传输引用(__bridge_transfer NSString*)。人们可能会认为 CFCreated-Reference 可能需要更多东西,但这(__bridge_transfer NSString*)就是将该引用转移到 ARC 所需的全部内容;让它为你管理。

如果您使用 using 进行类型转换(__bridge NSString*)CF_*_Create_*_,则 CFCreate 函数返回的引用将不会传输到 ARC,并且会引入泄漏。

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

第二个例子

但是,它从中获取的图形上下文是在该方法的范围内创建的,因此这里也有 NS_RETURNS_RETAINED 是否正确?

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

与第一个示例不同,__bridge_transfer不应进行显式说明。

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

第三个例子

- (NSArray *)places 
...
Run Code Online (Sandbox Code Playgroud)

不知道在这里要做什么,因为返回的对象可能是新创建的,也可能不是。

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

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

return [NSArray arrayWithObject:@"Unknown"];
Run Code Online (Sandbox Code Playgroud)

同样,不需要任何属性。不应进行显式转移。

所有系统库中仅存在该属性的少数用途。


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

  • 其中涉及动态调度(所有 objc 方法都符合)
  • 其中参数(使用)和结果(保留的返回)是 ObjC 类型

理由是引用传输应该是本地实现的,并且很少有真正需要偏离这一点;向后兼容性可能是我能想到的“最好”原因。如果您可以控制代码,只需更新它以尽可能执行正确的操作,而不是引入这些属性。这可以通过遵守命名约定并在适当的情况下将引用传输到 ARC 来实现。

有关使用属性和偏离命名约定时可能遇到的错误的一些示例,请参阅:字典的深层复制在 Xcode 4.2 中给出分析错误

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

某个地方

- (NSString *)name NS_RETURNS_RETAINED;
Run Code Online (Sandbox Code Playgroud)

别处

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