[这个答案部分是对贾斯汀给出的答案的长评论/更正。先前的答案让我相信对属性的语义以及 ARC 如何处理返回引用的描述不正确。]
答案在于 ARC 分析的工作原理以及NS_RETURNS_RETAINED
.
ARC 分析您的源以确定何时保留、释放或自动释放可保留对象引用。
如果您的应用程序的所有资源都可用,那么理论上,分析可能能够从“第一原则”确定这些信息 - 从最小的表达式开始并向外工作。
然而,所有源代码都不可用 - 例如,一些已经在框架中编译等 - 因此在分析方法调用时,ARC 不会查看方法的源代码,而只会查看其签名 - 它的名称和参数类型并返回价值。
仅考虑可保留对象类型的返回值 ARC 需要知道所有权是否正在转移——在这种情况下,ARC 需要在某个时候释放它——或者不(例如,自动释放的引用)——在这种情况下,ARC 需要保留如果需要所有权。
ARC 根据方法的名称和任何属性确定此信息。根据定义,以所有权转移或包含转移init
的new
方法;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
这是对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...
。
这个答案已经(太)长了,希望以上内容可以帮助您找出其他两个示例的答案!