[这个答案部分是对贾斯汀给出的答案的长评论/更正。先前的答案让我相信对属性的语义以及 ARC 如何处理返回引用的描述不正确。]
答案在于ARC分析的工作原理和NS_RETURNS_RETAINED的含义。
ARC 分析您的源以确定何时保留、释放或自动释放可保留对象引用。
如果您的应用程序的所有来源都可用,那么理论上,分析可能能够从“第一原则”确定此信息 - 从最小的表达式开始并向外工作。 p>
然而所有来源都不可用 - 例如有些已经在框架等中编译 - 因此在分析方法调用时,ARC 不会查看方法的来源,而只会查看其签名 - 它的名称及其参数和返回值的类型。
仅考虑可保留对象类型的返回值 ARC 需要知道所有权是否正在转移 - 在这种情况下,ARC 需要在某个时候释放它 - 或不(例如 autoreleased 参考)——在这种情况下,如果需要所有权,ARC 将需要保留它。
ARC 根据方法的名称 和任何属性确定此信息。以init 或new 开头或包含copy 的方法根据定义转移所有权;所有其他方法都没有。属性 NS_RETURNS_RETAINED 通知 ARC,无论其名称如何,方法都会转移其返回引用的所有权。
这是故事的一半……另一半是 ARC 如何处理方法体中的return 语句。
return 实际上是一种赋值,在进行可保留对象引用赋值时,ARC 根据其对当前所有权和引用的了解以及目的地的要求。
对于return 语句,毫无疑问,目标的要求由方法名称和签名上指定的任何属性决定。如果签名表明所有权正在转移,那么 ARC 将返回一个 retained 引用,否则它将返回一个 autoreleased 引用。
了解 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 并不能确定所有权方法返回的引用。
在applicationDidFinishLaunching 方法中编译对pcen 的调用时,ARC 再次查看签名,确定当前方法需要所有权并且返回的引用不属于所有并插入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
这是对 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... 转移所有权。
这个答案已经(太)长了,希望以上内容可以帮助您找出其他两个示例的答案!