Answers:
对于类型是符合NSCopying
协议的不变值类的属性,几乎总是应copy
在@property
声明中指定。retain
在这种情况下,指定几乎是您所不希望的。
这就是您要这样做的原因:
NSMutableString *someName = [NSMutableString stringWithString:@"Chris"];
Person *p = [[[Person alloc] init] autorelease];
p.name = someName;
[someName setString:@"Debajit"];
该Person.name
属性的当前值将有所不同,具体取决于是否声明了该属性retain
或copy
— @"Debajit"
是否标记了该属性retain
,但是标记@"Chris"
了该属性copy
。
由于几乎在所有情况下都希望防止在对象背后改变对象的属性,因此应标记代表它们的属性copy
。(如果您自己写setter而不是使用setter,@synthesize
则应记住实际使用copy
而不是retain
在其中使用。)
NSMutableString
,只是作为一个短暂的“字符串生成器”类型(我立即从中获取一个不变的副本)。我希望它们是谨慎的类型-但我会允许这样的事实:如果原始字符串不可更改,则可以自由地保留副本,这一事实减轻了我的大部分担忧。
复制应用于NSString。如果它是Mutable,那么它将被复制。如果不是,则只保留它。正是您想要在应用程序中使用的语义(让类型做到最好)。
NSString
声明为的属性仍然copy
会获得retain
(当然,如果它是不可变的)。我能想到的另一个例子是NSNumber
。
对于一般的字符串,使用copy属性而不是keep总是一个好主意吗?
是的-通常总是使用copy属性。
这是因为可以将NSString实例或NSMutableString实例传递给您的NSString属性,因此我们无法真正确定要传递的值是不可变的还是可变的对象。
“复制”资产的效率是否比这种“保留”资产低?
如果您的属性正在传递NSString实例,则答案为“ 否 ”-复制的效率不比保留效率低。
(效率也不低,因为NSString足够聪明,无法实际执行复制。)
如果您的属性是通过NSMutableString实例传递的,则答案为“ 是 ”-复制的效率低于保留。
(效率较低,因为必须进行实际的内存分配和复制,但这可能是可取的。)
一般而言,“复制”属性的效率可能较低-但是,通过使用NSCopying
协议,可以实现一个与保留副本“同样有效”的类。NSString实例就是一个例子。
通常(不仅仅用于NSString),什么时候应该使用“复制”而不是“保留”?
copy
当您不希望属性的内部状态更改而不会发出警告时,应始终使用。即使对于不可变的对象-正确编写的不可变的对象也将有效地处理复制(有关不可变性和,请参见下一节NSCopying
)。
retain
对象可能出于性能方面的考虑,但是却带来了维护开销-您必须管理内部状态在代码外部更改的可能性。正如他们所说-最优化。
但是,我写的课是一成不变的-我不能只是“保留”它吗?
不使用copy
。如果您的类确实是不可变的,则最佳实践是实施NSCopying
协议以使您的类在copy
使用时返回自身。如果您这样做:
copy
。copy
注解让自己的代码更易于维护-的copy
注释表明你真的不需要担心这个对象改变别处状态。我尝试遵循以下简单规则:
在将对象分配给属性时,是否要保留该对象的值?使用copy。
我是否要保留该对象,并且不在乎其当前的内部价值或将来的内部价值?使用牢固(保留)。
为了说明:我想坚持到名字 “丽莎·米勒”(副本)或我想坚持到人丽莎·米勒(强)?她的名字以后可能会更改为“ Lisa Smith”,但她仍然是同一个人。
通过此示例,复制和保留可以解释如下:
NSMutableString *someName = [NSMutableString stringWithString:@"Chris"];
Person *p = [[[Person alloc] init] autorelease];
p.name = someName;
[someName setString:@"Debajit"];
如果属性是copy类型,则,
将为该[Person name]
字符串创建一个新副本,该副本将保存someName
字符串的内容。现在,任何对someName
string的操作都不会对起作用[Person name]
。
[Person name]
和someName
字符串将具有不同的内存地址。
但是如果保留,
两者[Person name]
将拥有与somename字符串相同的内存地址,只是somename字符串的保留计数将增加1。
因此,somename字符串中的任何更改都将反映在[Person name]
字符串中。
肯定会在使用面向对象的环境时将“复制”放在属性声明中,在这种环境中,堆上的对象通过引用传递-您获得的好处之一是,在更改对象时,所有对该对象的引用查看最新更改。许多语言都提供“ 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.
在这种情况下,无需使用副本,我们的联系人对象将自动获取新值;但是,如果确实使用过它,则必须手动确保检测到并同步了更改。在这种情况下,可能需要保留语义;另一方面,复制可能更合适。
@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;
您应该阅读这些内容,以获得有关它返回不可变字符串(如果传递可变字符串)还是返回保留字符串(如果传递不可变字符串)的更多信息。
当类及其内容不可变时,通过保留原始副本而不是创建新副本来实现NSCopying
因此,对于我们的不可变版本,我们可以这样做:
- (id)copyWithZone:(NSZone *)zone
{
return self;
}
由于name是一个(不可变的)NSString
,因此,如果将另一个设置NSString
为name ,则复制或保留没有区别。换句话说,复制的行为就像保留,将引用计数增加一个。我认为这是对不可变类的自动优化,因为它们是不可变的,不需要克隆。但是,当将a NSMutalbeString
mstr
设置为name时,mstr
为了正确起见,将复制其内容。
name
应该发布dealloc
?