当NS_RETURNS_RETAINED需要?(When is NS_RETURNS_RETAINE

2019-07-31 05:09发布

以下面的例子:

- (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如果返回的对象是一个autorelease'ed方法的结果是不需要的。 所以说,在最后一个例子返回被修正

return [NSArray arrayWithObject:@"Unknown"];

什么是最好的做法呢?

Answer 1:

第一个例子

它是正确的把NS_RETURNS_RETAINED呢?

这是不正确 -无属性需要在这里。 添加属性将违背命名约定,这是遵循非常重要。

更详细地说,不需要属性,因为参考是在使用例如转移 (__bridge_transfer NSString*) 有人可能认为一个CFCreated引用可能需要更多的东西,但(__bridge_transfer NSString*)是所有需要转移关闭以圆弧参考; 对于它来管理你。

如果你使用已经类型强制转换(__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类型

其基本原理是参照转让应该是本地的实现,并且很少有真正需要从偏离; 向后兼容性可能是“最好”的原因,我能想到的。 如果你有你的代码的控制,只需更新它做正确的事尽可能,而不是引入这些属性。 这可以通过遵守命名约定,并通过转让引用电弧在适当情况下完成。

对于您可以运行到使用属性和命名约定偏离误差的一些示例,请参阅: 字典的深层复制给在Xcode 4.2分析误差 。

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

某个地方

- (NSString *)name NS_RETURNS_RETAINED;

别处

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


Answer 2:

[这个答案是部分长评论/校正由Justin给出了答案。 以前的答案给了我相信这两个属性的语义的不正确的描述和ARC如何处理返回的引用。]

答案就在ARC分析如何工作和意义NS_RETURNS_RETAINED

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

如果所有您的应用程序的源是可利用的话,理论上的分析或许能够确定从“第一原则”这一信息-从最小的表情和向外工作。

但是,所有的源不可用-例如一些已经编译在框架等-因此分析方法调用ARC时,不会看方法的来源,但仅在其署名-它的名字和类型的参数和返回的值。

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

ARC基于所述方法和任何属性的名称信息。 方法开始initnew或包含copy转移,顾名思义,所有权; 所有其他的方法,没有。 属性NS_RETURNS_RETAINED通知ARC的是,方法,不管它的名字,传输其返回的引用的所有权。

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

return确实是一个类型的分配,并做了保留性对象引用赋值时ARC确定参考是否需要为基于其目前的所有权和参考和目标要求的知识得以保留,自动释放,或离开。

对于return声明目的地的要求,勿庸置疑,该方法的名称和签名指定的任何属性决定的。 如果签名表明所有权被转移然后ARC将返回保留参考,否则将返回一个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并不确定该方法返回引用的所有权。

编译调用当pcenapplicationDidFinishLaunching方法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 ),因此成本并不像看上去那样大。

然而正确的答案既不是...

推荐的解决方案是依靠可可/ ARC公约和改变你的方法,如名称:

@interface NSString (AddingPercentEscapes)

- (NSString *) newPercentEscapedString;

@end

而相关的变化。

做到这一点,你会得到相同的代码pcen NS_RETURNS_RETAINED作为ARC确定是否应基于名称所有权转让new...

这个答案是(太)已久,希望上面会帮助你制定出解决您的另外两个例子!



文章来源: When is NS_RETURNS_RETAINED needed?