NSString属性:复制或保留?

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

 @interface SomeClass : NSObject { NSString* name; } @property (nonatomic, retain) NSString* name; @end 

我知道这个名字可能会被分配一个NSMutableString在这种情况下这可能会导致错误的行为。

  • 对于一般的string,使用copy属性而不是retain是个好主意?
  • “复制”的财产是否比这样的“保留”财产效率低?

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

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

 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。 如果它是可变的,那么它被复制。 如果不是,那么它就会被保留。 准确地说,你想在应用程序中的语义(让types做最好的)。

对于一般的string,使用copy属性而不是retain是个好主意?

是的 – 一般总是使用复制属性。

这是因为你的NSString属性可以传递一个NSString实例或一个NSMutableString实例 ,因此我们不能真正确定传递的值是不可变的还是可变的对象。

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

  • 如果您的属性正在传递一个NSString实例 ,答案是“ ” – 复制效率不如保留。
    (效率不低,因为NSString足够聪明,不会真正执行副本。)

  • 如果您的属性传递了一个NSMutableString实例,那么答案是“ ” – 复制效率低于保留。
    (效率不高,因为实际的内存分配和复制必须发生,但这可能是一个理想的事情。)

  • 一般来说,“复制”属性的效率可能会低一些 – 但是通过使用NSCopying协议,可以实现一个“同样高效”的类来复制和保留。 NSString实例就是这样一个例子。

一般来说(不只是NSString),什么时候应该使用“复制”而不是“保留”?

如果您不希望在不发出警告的情况下更改内部状态,则应始终使用copy 。 即使对于不可变的对象 – 正确编写的不可变对象将有效地处理拷贝(参见下一节关于不变性和NSCopying )。

retain对象可能会有性能方面的原因,但是它带来了维护开销 – 您必须pipe理内部状态在代码之外更改的可能性。 正如他们所说 – 最后优化。

但是,我写我的课是不变的 – 我不能“保留”它吗?

不使用copy 。 如果你的类真的是不可变的,那么最好的做法是实现NSCopying协议,使你的类在使用copy时自行返回。 如果你这样做:

  • 其他用户在使用copy时将获得性能优势。
  • copy注释使您自己的代码更易维护 – copy注释表明您确实无需担心其他位置的此对象更改状态。

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

  • 当我把它分配给我的财产的时候,我想要坚持对象的价值吗? 使用复制

  • 我是否想要坚持这个客体我不在乎它现在或将来的内在价值是什么? 使用 (保留)。

为了说明:我想要坚持名字 “丽莎米勒”( 副本 ),还是我想坚持对 Lisa Miller( )? 她的名字后来可能变成“丽莎史密斯”,但她仍然是同一个人。

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

 NSMutableString *someName = [NSMutableString stringWithString:@"Chris"]; Person *p = [[[Person alloc] init] autorelease]; p.name = someName; [someName setString:@"Debajit"]; 

如果该属性是复制types,

将为[Person name]string创build一个新副本,该string将保存someNamestring的内容。 现在对someNamestring的任何操作都不会对[Person name]

[Person name]someNamestring将有不同的内存地址。

但是在保留的情况下,

[Person name]将保持与somenamestring相同的内存地址,只是somenamestring的保留计数将增加1。

因此,somenamestring中的任何更改都会反映在[Person name]string中。

当然,在一个属性声明中使用“复制”会导致使用面向对象的环境,堆中的对象是通过引用传递的 – 您在这里获得的好处之一是,在更改对象时,对该对象的所有引用看到最新的变化。 许多语言提供'ref'或类似的关键字来允许值types(即栈上的结构)从相同的行为中受益。 就个人而言,我会谨慎使用复制,如果我觉得属性值应该被保护,以免被赋值的对象发生更改,我可以在赋值期间调用该对象的复制方法,例如:

 p.name = [someName copy]; 

当然,在devise包含这个属性的对象的时候,只有你会知道这个devise是否受益于一个模式,在这个模式中,作业是否被复制 – 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. 

在这种情况下,不使用复制,我们的联系对象自动获取新的值; 如果我们使用它,但是,我们必须手动确保检测和同步更改。 在这种情况下,保留语义可能是可取的。 在另一种情况下,复制可能更合适。

 @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属性

 @property (nonatomic, copy) NSString* name; 

你应该阅读这些关于它是否返回不可变string(在可变string被传递的情况下)还是返回一个保留string(在不可变string被传递的情况下)

NSCopying协议参考

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

价值对象

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

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

如果string非常大,则复制将影响性能,大string的两个副本将使用更多的内存。

既然name是一个(不可变的) NSString ,如果你设置另一个NSString的名字,复制或保留就没有区别了。 换句话说,复制行为就像保留一样,增加引用计数。 我认为这是一个不可变类的自动优化,因为它们是不可变的,不需要被克隆。 但是当一个NSMutalbeString mstr被设置为name的时候,为了正确性, mstr的内容将被复制。