关于objective c:NSString属性:复制还是保留?

NSString property: copy or retain?

假设我有一个名为SomeClass的类,它有一个string属性名:

1
2
3
4
5
6
7
8
@interface SomeClass : NSObject
{
    NSString* name;
}

@property (nonatomic, retain) NSString* name;

@end

我知道这个名字可能被指定为NSMutableString,在这种情况下,这可能导致错误行为。

  • 一般来说,对于字符串,使用copy属性而不是retain属性总是一个好主意吗?
  • "复制"财产是否比"保留"财产效率低?


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

这就是为什么你想这样做:

1
2
3
4
5
6
NSMutableString *someName = [NSMutableString stringWithString:@"Chris"];

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

[someName setString:@"Debajit"];

Person.name财产的现值将根据财产被宣布为retaincopy而有所不同,如果财产被标记为retain则为@"Debajit",如果财产被标记为copy则为@"Chris"

因为在几乎所有情况下,您都希望防止对象背后的属性发生变化,所以应该标记表示它们的属性copy。(如果你自己写setter而不是使用@synthesize,你应该记住实际使用copy而不是retain。)


复制应用于nsstring。如果它是可变的,那么它就会被复制。如果不是的话,那就保留下来。确切地说,是应用程序中需要的语义(让类型做最好的事情)。


For strings in general, is it always a good idea to use the copy attribute instead of retain?

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

这是因为您的nsstring属性可以通过nsstring实例或nsmutableString实例传递,因此我们无法真正确定传递的值是不可变对象还是可变对象。

Is a"copied" property in any way less efficient than such a"retain-ed" property?

  • 如果您的属性正在传递一个nsstring实例,则答案是"否"——复制的效率不低于保留。(它的效率并不低,因为nsstring足够智能,无法实际执行复制。)

  • 如果您的属性被传递给一个nsmutableString实例,那么答案是"是"——复制比保留效率低。(它效率较低,因为必须进行实际的内存分配和复制,但这可能是一件可取的事情。)

  • 一般来说,"复制的"属性可能效率较低,但是通过使用NSCopying协议,可以实现一个"与保留一样高效"的类来进行复制。NSstring实例就是这样一个例子。

Generally (not just for NSString), when should I use"copy" instead of"retain"?

当不希望更改属性的内部状态而不需要警告时,应始终使用EDCOX1×1。即使对于不可变的对象,正确写入的不可变对象也将有效地处理复制(参见下一节关于不可变性和EDCOX1(0))。

EDOCX1 3个对象可能有性能上的原因,但是它有一个维护开销——您必须管理内部状态在代码之外变化的可能性。正如他们所说,优化最后。

But, I wrote my class to be immutable - can't I just"retain" it?

不使用EDOCX1×1。如果你的类真的是不可变的,那么最好使用EDCOX1 0协议来让你的类在使用EDCOX1×1时返回。如果你这样做:

  • 您班的其他用户在使用copy时将获得性能优势。
  • copy注释使您自己的代码更易于维护—copy注释表示您确实不需要担心这个对象在其他地方改变状态。

我试着遵循这个简单的规则:

  • 当我将对象分配给我的属性时,是否要保留对象的值?使用复制品。

  • 我是想抓住这个对象,而不关心它的内部值目前是什么,还是将来会是什么?使用强力胶(保留)。

举例说明:我想保留"丽莎·米勒"这个名字(副本)还是我想保留"丽莎·米勒"这个人(强)?她的名字后来可能改为"丽莎·史密斯",但她仍然是同一个人。


通过这个例子,复制和保留可以解释为:

1
2
3
4
5
6
NSMutableString *someName = [NSMutableString stringWithString:@"Chris"];

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

[someName setString:@"Debajit"];

如果属性是copy类型,则,

将为保存someName字符串内容的[Person name]字符串创建一个新副本。现在对someName字符串的任何操作都不会对[Person name]产生影响。

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

但如果保留,

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

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


当然,将"copy"放在属性声明上会很快遇到使用面向对象的环境的情况,在这种环境中,堆中的对象是通过引用传递的——您在这里得到的好处之一是,更改对象时,对该对象的所有引用都会看到最新的更改。许多语言提供"ref"或类似的关键字,以允许值类型(即堆栈上的结构)从相同的行为中受益。就我个人而言,我会谨慎使用copy,如果我觉得应该保护一个属性值,使其不受分配对象更改的影响,我可以在分配期间调用该对象的copy方法,例如:

1
p.name = [someName copy];

当然,在设计包含该属性的对象时,只有您知道设计是否受益于分配复制的模式-cocoawithlove.com有以下内容:

"如果setter参数可能是可变的,但属性的内部状态在没有警告的情况下无法更改,则应使用copy访问器"-因此,判断是否可以承受意外更改的值完全是您自己的决定。想象一下这个场景:

1
2
3
4
5
6
7
8
//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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@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

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

1
@property (nonatomic, copy) NSString* name;

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

nscopying协议参考

Implement NSCopying by retaining the original instead of creating a
new copy when the class and its contents are immutable

价值客体

So, for our immutable version, we can just do this:

1
2
3
4
- (id)copyWithZone:(NSZone *)zone
{
    return self;
}

如果字符串非常大,那么复制将影响性能,并且两个大字符串副本将使用更多内存。


由于名称是一个(不可变的)NSString,如果将另一个NSString设置为名称,则复制或保留没有任何区别。换言之,copy的行为与retain类似,将引用计数增加一个。我认为这是对不可变类的自动优化,因为它们是不可变的,不需要克隆。但是,当NSMutalbeStringmstr设置为名称时,为了正确起见,将复制mstr的内容。