NSString属性:复制还是保留?


331

假设我有一个SomeClass带有string属性名称的类:

@interface SomeClass : NSObject
{
    NSString* name;
}

@property (nonatomic, retain) NSString* name;

@end

我了解可能会给该名称分配一个,NSMutableString在这种情况下,这可能会导致错误的行为。

  • 对于一般的字符串,使用属性而不是总是一个好主意吗?copyretain
  • “复制”资产的效率是否比这种“保留”资产低?

6
后续问题:是否name应该发布dealloc
Chetan

7
@chetan是的,应该!
乔恩

Answers:


440

对于类型是符合NSCopying协议的不变值类的属性,几乎总是应copy@property声明中指定。retain在这种情况下,指定几乎是您所不希望的。

这就是您要这样做的原因:

NSMutableString *someName = [NSMutableString stringWithString:@"Chris"];

Person *p = [[[Person alloc] init] autorelease];
p.name = someName;

[someName setString:@"Debajit"];

Person.name属性的当前值将有所不同,具体取决于是否声明了该属性retaincopy@"Debajit"是否标记了该属性retain,但是标记@"Chris"了该属性copy

由于几乎在所有情况下都希望防止在对象背后改变对象的属性,因此应标记代表它们的属性copy。(如果您自己写setter而不是使用setter,@synthesize则应记住实际使用copy而不是retain在其中使用。)


61
这个答案可能引起了一些混乱(请参阅robnapier.net/blog/implementing-nscopying-439#comment-1312)。您对NSString绝对是正确的,但我相信您的观点过于笼统。应该复制NSString的原因是它具有通用的可变子类(NSMutableString)。对于没有可变子类的类(特别是您自己编写的类),通常最好保留它们而不是复制,以避免浪费时间和内存。
罗布·纳皮尔

63
您的推理不正确。您不应该根据时间/内存来确定是复制还是保留,而应该根据所需的语义来确定。这就是为什么我在答案中专门使用术语“不变值类”的原因。一个类是否具有可变的子类还是可变的本身也无关紧要。
克里斯·汉森

10
遗憾的是,Obj-C无法按类型强制执行不变性。这与C ++缺少可传递const相同。我个人认为字符串始终是不可变的。如果我需要使用可变字符串,则以后再对其进行突变时,我将永远不会发出不可变的引用。我认为任何与代码气味无关的东西。结果-在我的代码中(我一个人工作),我对所有字符串都使用了保留。如果我是团队的一部分,我可能会以不同的方式看待事情。
10年

5
@Phil Nash:我认为对您单独处理的项目和与他人共享的项目使用不同的样式是一种代码味道。在每种语言/框架中,都有开发人员同意的通用规则或样式。在私人项目中无视它们似乎是错误的。并且出于您的理由,“在我的代码中,我不会返回可变的字符串”:这可能适用于您自己的字符串,但是您永远不知道从框架接收到的字符串。
Nikolai Ruhe 2010年

7
@Nikolai我只是不使用NSMutableString,只是作为一个短暂的“字符串生成器”类型(我立即从中获取一个不变的副本)。我希望它们是谨慎的类型-但我会允许这样的事实:如果原始字符串不可更改,则可以自由地保留副本,这一事实减轻了我的大部分担忧。
philsquared 2010年

120

复制应用于NSString。如果它是Mutable,那么它将被复制。如果不是,则只保留它。正是您想要在应用程序中使用的语义(让类型做到最好)。


1
我仍然希望可变形式和不可变形式要谨慎,但是我没有意识到,如果原始字符串是不可变的,那么该副本可能只是保留下来-这是大多数方式。谢谢。
10年

25
+1表示NSString声明为的属性仍然copy会获得retain(当然,如果它是不可变的)。我能想到的另一个例子是NSNumber
matm 2011年

这个答案和@GBY的不赞成票有什么区别?
加里·林恩

67

对于一般的字符串,使用copy属性而不是keep总是一个好主意吗?

是的-通常总是使用copy属性。

这是因为可以将NSString实例NSMutableString实例传递给您的NSString属性,因此我们无法真正确定要传递的值是不可变的还是可变的对象。

“复制”资产的效率是否比这种“保留”资产低?

  • 如果您的属性正在传递NSString实例,则答案为“ ”-复制的效率不比保留效率低。
    (效率也不低,因为NSString足够聪明,无法实际执行复制。)

  • 如果您的属性是通过NSMutableString实例传递的,则答案为“ ”-复制的效率低于保留。
    (效率较低,因为必须进行实际的内存分配和复制,但这可能是可取的。)

  • 一般而言,“复制”属性的效率可能较低-但是,通过使用NSCopying协议,可以实现一个与保留副本“同样有效”的类。NSString实例就是一个例子。

通常(不仅仅用于NSString),什么时候应该使用“复制”而不是“保留”?

copy当您不希望属性的内部状态更改而不会发出警告时,应始终使用。即使对于不可变的对象-正确编写的不可变的对象也将有效地处理复制(有关不可变性和,请参见下一节NSCopying)。

retain对象可能出于性能方面的考虑,但是却带来了维护开销-您必须管理内部状态在代码外部更改的可能性。正如他们所说-最优化。

但是,我写的课是一成不变的-我不能只是“保留”它吗?

不使用copy。如果您的类确实是不可变的,则最佳实践是实施NSCopying协议以使您的类在copy使用时返回自身。如果您这样做:

  • 您班上的其他用户在使用时将获得性能上的好处copy
  • copy注解让自己的代码更易于维护-的copy注释表明你真的不需要担心这个对象改变别处状态。

39

我尝试遵循以下简单规则:

  • 在将对象分配给属性时,是否要保留该对象的?使用copy

  • 我是否要保留该对象,并且不在乎其当前的内部价值或将来的内部价值?使用牢固(保留)。

为了说明:我想坚持到名字 “丽莎·米勒”(副本)或我想坚持到丽莎·米勒()?她的名字以后可能会更改为“ Lisa Smith”,但她仍然是同一个人。


14

通过此示例,复制和保留可以解释如下:

NSMutableString *someName = [NSMutableString stringWithString:@"Chris"];

Person *p = [[[Person alloc] init] autorelease];
p.name = someName;

[someName setString:@"Debajit"];

如果属性是copy类型,则,

将为该[Person name]字符串创建一个新副本,该副本将保存someName字符串的内容。现在,任何对someNamestring的操作都不会对起作用[Person name]

[Person name]someName字符串将具有不同的内存地址。

但是如果保留,

两者[Person name]将拥有与somename字符串相同的内存地址,只是somename字符串的保留计数将增加1。

因此,somename字符串中的任何更改都将反映在[Person name]字符串中。


3

肯定会在使用面向对象的环境时将“复制”放在属性声明中,在这种环境中,堆上的对象通过引用传递-您获得的好处之一是,在更改对象时,所有对该对象的引用查看最新更改。许多语言都提供“ ref”或类似的关键字,以允许值类型(即堆栈上的结构)从相同的行为中受益。就个人而言,我会谨慎使用copy,如果我认为应该保护属性值免受对其分配对象的更改,则可以在分配过程中调用该对象的copy方法,例如:

p.name = [someName copy];

当然,在设计包含该属性的对象时,只有您会知道设计是否受益于分配副本的模式-Cocoawithlove.com的说法如下:

“当setter参数可能是可变的,但您不能在没有警告的情况下更改属性的内部状态时,应使用复制访问器”-因此,对于是否可以承受该值意外更改的判断是您自己的。想象一下这种情况:

//person object has details of an individual you're assigning to a contact list.

Contact *contact = [[[Contact alloc] init] autorelease];
contact.name = person.name;

//person changes name
[[person name] setString:@"new name"];
//now both person.name and contact.name are in sync.

在这种情况下,无需使用副本,我们的联系人对象将自动获取新值;但是,如果确实使用过它,则必须手动确保检测到并同步了更改。在这种情况下,可能需要保留语义;另一方面,复制可能更合适。


1
@interface TTItem : NSObject    
@property (nonatomic, copy) NSString *name;
@end

{
    TTItem *item = [[TTItem alloc] init];    
    NSString *test1 = [NSString stringWithFormat:@"%d / %@", 1, @"Go go go"];  
    item.name = test1;  
    NSLog(@"-item.name: point = %p, content = %@; test1 = %p", item.name, item.name, test1);  
    test1 = [NSString stringWithFormat:@"%d / %@", 2, @"Back back back"];  
    NSLog(@"+item.name: point = %p, content = %@, test1 = %p", item.name, item.name, test1);
}

Log:  
    -item.name: point = 0x9a805a0, content = 1 / Go go go; test1 = 0x9a805a0  
    +item.name: point = 0x9a805a0, content = 1 / Go go go, test1 = 0x9a84660

0

您应该一直使用copy来声明NSString属性

@property (nonatomic, copy) NSString* name;

您应该阅读这些内容,以获得有关它返回不可变字符串(如果传递可变字符串)还是返回保留字符串(如果传递不可变字符串)的更多信息。

NSCopying协议参考

当类及其内容不可变时,通过保留原始副本而不是创建新副本来实现NSCopying

价值对象

因此,对于我们的不可变版本,我们可以这样做:

- (id)copyWithZone:(NSZone *)zone
{
    return self;
}

-1

由于name是一个(不可变的)NSString,因此,如果将另一个设置NSString为name ,则复制或保留没有区别。换句话说,复制的行为就像保留,将引用计数增加一个。我认为这是对不可变类的自动优化,因为它们是不可变的,不需要克隆。但是,当将a NSMutalbeString mstr设置为name时,mstr为了正确起见,将复制其内容。


1
您正在将声明的类型与实际类型混淆。如果使用“保留”属性并分配NSMutableString,则该NSMutableString将保留,但仍可以修改。如果使用“ copy”,则在分配NSMutableString时将创建不可变的副本;从那时起,属性上的“副本”将只保留,因为可变字符串的副本本身是不可变的。
gnasher729 2014年

1
您在这里缺少一些重要的事实,如果您使用来自保留变量的对象,则当该变量被修改时,您的对象也是如此,如果它来自复制的变量,则您的对象将具有该变量的当前值,即不会改变
Bryan P

-1

如果字符串很大,则复制将影响性能,并且大字符串的两个副本将使用更多的内存。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.