什么时候需要NS_RETURNS_RETAINED?

时间:2012-08-27 03:34:59

标签: objective-c ios memory-management automatic-ref-counting reference-counting

采用以下示例:

- (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"];

那么最佳做法是什么?

2 个答案:

答案 0 :(得分:11)

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

答案在于ARC分析的工作原理以及NS_RETURNS_RETAINED的含义。

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

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

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

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

ARC根据方法的名称和任何属性确定此信息。根据定义,所有权,以initnew或包含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返回的引用的{1}},表达式返回(__bridge_transfer NSString *) ... kCFStringEncodingUTF8)所拥有的引用。

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

__bridge_transfer方法中编译对pcen的调用时,再次查看签名,确定当前方法需要所有权,并且不归属于返回的引用,并插入applicationDidFinishLaunching

您可以通过调用"产品>来验证这一点。生成输出>装配文件"在Xcode中,在生成的程序集中,您将在retain的代码中看到以下内容:

pcen

显示了ARC插入的自动释放,以及callq _CFURLCreateStringByAddingPercentEscapes movq %rax, %rdi callq _objc_autoreleaseReturnValue addq $16, %rsp popq %rbp ret 组件中的自动释放:

applicationDidFinishLaunching

调用callq _objc_msgSend movq %rax, %rdi callq _objc_retainAutoreleasedReturnValue 后跟ARC插入保留。

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

pcen

运行(和分析)此版本,它也可以。但是,生成的代码已更改,ARC确定它应根据属性的存在转移所有权,因此@interface NSString (AddingPercentEscapes) - (NSString *) pcen NS_RETURNS_RETAINED; @end 语句的程序集将变为:

return

ARC 插入自动释放。在呼叫站点,程序集变为:

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

此处ARC执行插入保留。

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

看起来带有属性的版本更好,因为ARC不需要插入autorelease / retain;但是运行时优化了这个序列(因此调用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 而不是像_objc_retainAutoreleasedReturnValue那样),因此成本不会像看上去那么大。

然而正确答案 ......

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

_objc_retain

以及相关的更改。

执行此操作后,您将获得与@interface NSString (AddingPercentEscapes) - (NSString *) newPercentEscapedString; @end 相同的代码,因为ARC确定它应根据名称 pcen NS_RETURNS_RETAINED转移所有权。

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

答案 1 :(得分:2)

第一个例子

  

将NS_RETURNS_RETAINED放在那里是否正确?

不正确 - 此处不需要任何属性。添加属性将违反命名约定,这非常重要。

更详细地说,不需要任何属性,因为使用(__bridge_transfer NSString*)在示例中传输 。有人可能会认为CFCreated-Reference可能需要更多,但(__bridge_transfer NSString*)是将该引用传递给ARC所需的全部内容;为它管理你。

如果您使用(__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来实现。

有关错误的一些示例,您可能会遇到使用属性并偏离命名约定的情况,请参阅:Deep copy of dictionaries gives Analyze error in Xcode 4.2

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

某处

- (NSString *)name NS_RETURNS_RETAINED;

别处

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