假设我有一个名为SomeClass
的类,其string
属性名称为:
@interface SomeClass : NSObject
{
NSString* name;
}
@property (nonatomic, retain) NSString* name;
@end
我知道该名称可能会被分配NSMutableString
,在这种情况下,这可能会导致错误的行为。
copy
属性而不是retain
总是是一个好主意吗? 答案 0 :(得分:438)
对于类型为符合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"
{1}},但retain
如果属性标记为@"Chris"
。
因为在几乎所有情况下你都希望阻止在其背后改变对象的属性,你应该标记代表它们的属性copy
。 (如果你自己编写setter而不是使用copy
,你应该记住在其中实际使用@synthesize
而不是copy
。)
答案 1 :(得分:120)
复制应该用于NSString。如果它是Mutable,那么它会被复制。如果不是,那么它就会被保留下来。正是你想要在应用程序中使用的语义(让类型做到最好)。
答案 2 :(得分:66)
对于字符串,一般来说,最好使用copy属性而不是retain?
是 - 通常始终使用复制属性。
这是因为 NSString属性 可以传递 NSString实例 或 NSMutableString实例 ,因此我们无法确定传递的值是不可变对象还是可变对象。
“复制”属性是否比这种“保留”属性效率低?
如果您的媒体资源是 NSString实例 ,则答案是“否” - 复制的效率不低于保留。
(效率并不低,因为NSString非常智能,无法实际执行副本。)
如果您的媒体资源已通过 NSMutableString实例 ,则答案为“是” - 复制效果低于保留。
(效率较低,因为必须进行实际的内存分配和复制,但这可能是理想的事情。)
一般来说,“复制”属性可能效率较低 - 但是通过使用NSCopying
协议,可以实现一个“同样有效”的类来复制它是保留。 NSString实例 就是一个例子。
一般情况下(不只是NSString),何时应该使用“copy”而不是“retain”?
如果您不希望在没有警告的情况下更改属性的内部状态,则应始终使用copy
。即使对于不可变对象 - 正确编写的不可变对象也会有效地处理复制(请参阅下一节关于不变性和NSCopying
)。
retain
个对象可能存在性能原因,但它带来了维护开销 - 您必须管理内部状态在代码外部更改的可能性。正如他们所说 - 优化最后一次。
但是,我写的课程是不可变的 - 我不能只是“保留”它吗?
否 - 使用copy
。如果您的类确实是不可变的,那么最佳实践是实现NSCopying
协议,以便在使用copy
时让您的类返回。如果你这样做:
copy
时会获得性能优势。copy
注释使您自己的代码更易于维护 - copy
注释表明您确实无需担心此对象在其他位置更改状态。答案 3 :(得分:39)
我尝试遵循这个简单的规则:
我是否希望在我将属性分配给我的属性的时间点 值?使用复制。
我是否希望坚持对象和我不关心其内部价值目前或将来会是什么?使用强(保留)。
举例说明:我是否想要坚持名称" Lisa Miller" (复制)或者我想要坚持人 Lisa Miller(强)?她的名字可能会改为" Lisa Smith",但她仍然是同一个人。
答案 4 :(得分:13)
通过这个例子,copy和retain可以解释为:
NSMutableString *someName = [NSMutableString stringWithString:@"Chris"];
Person *p = [[[Person alloc] init] autorelease];
p.name = someName;
[someName setString:@"Debajit"];
如果属性是copy类型,那么
将为[Person name]
字符串创建一个新副本,该字符串将保存someName
字符串的内容。现在,对someName
字符串的任何操作都不会对[Person name]
生效。
[Person name]
和someName
字符串将具有不同的内存地址。
但是在保留的情况下,
[Person name]
将保留与somename字符串相同的内存地址,只是somename字符串的保留计数将增加1。
因此,somename字符串中的任何更改都将反映在[Person name]
字符串中。
答案 5 :(得分:3)
当然,在使用面向对象的环境中放置“复制”时会出现“复制”,其中堆上的对象通过引用传递 - 这里的好处之一是,在更改对象时,所有引用到那个对象看到最新的变化。许多语言提供“ref”或类似关键字以允许值类型(即堆栈上的结构)从相同的行为中受益。个人而言,我会谨慎地使用副本,如果我觉得属性值应该受到保护,不受对它所分配对象的更改的影响,我可以在赋值期间调用该对象的复制方法,例如:
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.
在这种情况下,不使用复制,我们的联系对象会自动获取新值;但是,如果我们确实使用它,我们必须手动确保检测到并同步了更改。在这种情况下,可能需要保留语义;另一方面,副本可能更合适。
答案 6 :(得分: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
答案 7 :(得分:0)
您应该始终使用复制来声明NSString属性
@property (nonatomic, copy) NSString* name;
您应该阅读这些内容,以获取有关它是否返回不可变字符串(如果传递了可变字符串)或返回保留字符串(如果传递了不可变字符串)的更多信息。
通过保留原始内容而不是创建一个来实现NSCopying 当类及其内容不可变时的新副本
因此,对于我们的不可变版本,我们可以这样做:
- (id)copyWithZone:(NSZone *)zone
{
return self;
}
答案 8 :(得分:-1)
由于name是(不可变的)NSString
,因此如果您将另一个NSString
设置为name,则复制或保留没有任何区别。换句话说,复制行为就像保留一样,将引用计数增加一。我认为这是对不可变类的自动优化,因为它们是不可变的,不需要克隆。但是,当NSMutalbeString
mstr
设置为name时,为了正确起见,将复制mstr
的内容。
答案 9 :(得分:-1)
如果字符串非常大,那么复制将影响性能,大字符串的两个副本将使用更多内存。