13

举个例子:

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

那么最好的做法是什么?

4

2 回答 2

19

[这个答案部分是对贾斯汀给出的答案的长评论/更正。先前的答案让我相信对属性的语义以及 ARC 如何处理返回引用的描述不正确。]

答案在于 ARC 分析的工作原理以及NS_RETURNS_RETAINED.

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

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

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

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

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

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

Areturn实际上是一种赋值,在进行可保留对象引用赋值时,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当在方法 ARC 中编译调用时,applicationDidFinishLaunching再次查看签名,确定当前方法需要所有权并且返回的引用不属于所有并插入一个retain.

您可以通过在 Xcode 中调用“产品 > 生成输出 > 程序集文件”来验证这一点,在生成的程序集中,您将在代码中看到以下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

这是对pcenARC 插入的保留的调用。

因此,您的示例在没有注释的情况下可以正常工作,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...

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

于 2012-08-27T21:19:16.837 回答
4

第一个例子

将 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 约定,包括返回现有和新创建的对象。它将为两条路径插入正确的引用计数操作。

最后一个问题;如果返回的对象是自动释放方法的结果,则可能不需要 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.
于 2012-08-27T06:17:00.857 回答