[这个答案部分是对贾斯汀给出的答案的长评论/更正。我认为之前的答案对属性的语义以及 ARC 如何处理返回引用给出了错误的描述。]
答案在于 ARC 分析的工作原理和含义NS_RETURNS_RETAINED
.
ARC 分析您的源以确定何时保留、释放或自动释放可保留对象引用。
If然后,您的应用程序的所有源代码都可用,理论上,分析可能能够从“第一原理”确定这些信息 - 从最小的表达式开始并向外工作。
However所有来源均不可用 - 例如有些已经在框架等中编译 - 因此在分析方法调用时,ARC 不会查看方法的源代码,而只会查看其签名 - 它的名称及其参数和返回值的类型。
仅考虑可保留对象类型的返回值,ARC 需要知道所有权是否正在转移 - 在这种情况下,ARC 需要release它在某个时刻 - 或不(例如自动释放参考) - 在这种情况下 ARC 需要retain如果需要所有权的话。
ARC 根据以下信息确定此信息name该方法和任何属性。方法开头为init
or new
或含有copy
根据定义,转让所有权;所有其他方法都没有。属性NS_RETURNS_RETAINED
通知 ARC,一个方法,无论其名称如何,都会转移其返回引用的所有权。
这就是故事的一半...另一半是 ARC 如何处理return
方法体中的语句。
A return
实际上是一种赋值类型,当执行可保留对象引用赋值时,ARC 根据其对当前所有权和引用的了解以及目标的要求来确定该引用是否需要保留、自动释放或保持原样。
For a return
毫无疑问,目的地的要求是由方法名称和签名上指定的任何属性决定的。如果签名表明所有权正在转移,那么 ARC 将返回retained引用,否则将返回自动释放 one.
重要的是要了解 ARC 在方法调用的两侧都起作用,它确保返回适当的引用and确定如何处理返回的引用。
有了所有的序言,我们可以看看你的第一个例子。看起来你正在写一个方法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
method return
语句 ARC 查看签名、姓名 (pcen
) 并不表示所有权转移,也没有属性,所以 ARC 添加了一个autorelease
表达式返回的引用的(__bridge_transfer NSString *) ... kCFStringEncodingUTF8)
因为该表达式返回一个引用pcen
.
重要的: what表达方式并不重要,重要的是是否pcen
拥有它保留的引用 - 特别是__bridge_transfer
不确定该方法返回的引用的所有权。
编译时调用pcen
in the applicationDidFinishLaunching
方法 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 确实not插入自动释放。在调用站点,程序集变为:
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 所做的not插入保留。
所以两个版本都是“正确的”,但哪个更好呢?
看起来带有该属性的版本更好,因为 ARC 不需要插入自动释放/保留;但运行时优化了这个序列(因此调用_objc_retainAutoreleasedReturnValue
而不是类似的东西_objc_retain
)因此成本并不像看起来那么大。
However正确答案是neither...
推荐的解决方案是依赖 Cocoa/ARC 约定并更改方法的名称,例如:
@interface NSString (AddingPercentEscapes)
- (NSString *) newPercentEscapedString;
@end
以及相关的变化。
执行此操作,您将获得与以下相同的代码pcen NS_RETURNS_RETAINED
ARC 决定应根据name new...
.
这个答案已经(太)长了,希望以上内容能帮助您找到其他两个示例的答案!