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

@interface SomeClass : NSObject
{
    NSString* name;
}

@property (nonatomic, retain) NSString* name;

@end


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


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


评论

后续问题:是否应在dealloc中释放名称?

@chetan是的,应该!

#1 楼

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

这就是为什么要这样做:

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

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

[someName setString:@"Debajit"];


当前值根据是否将属性声明为Person.nameretaincopy属性会有所不同-如果将属性标记为@"Debajit",则属性将为retain;如果将属性标记为@"Chris",则属性将为copy

因为几乎在所有情况下要防止更改对象背后的属性,应标记代表它们的属性copy。 (而且,如果您自己编写设置器而不是使用@synthesize,则应记住实际上使用的是copy而不是retain。)

评论


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

–罗布·纳皮尔
2010-4-27的0:08



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

–克里斯·汉森(Chris Hanson)
10年4月28日在0:06

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

– philsquared
2010年5月27日10:38



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

– Nikolai Ruhe
2010年5月27日在12:18

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

– philsquared
2010年5月27日14:37

#2 楼

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

评论


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

– philsquared
2010年5月27日下午14:38

+1表示声明为copy的NSString属性无论如何都会保留(当然,如果它是不可变的)。我能想到的另一个例子是NSNumber。

–matm
2011年5月12日14:33



这个答案和@GBY的不赞成票有什么区别?

–加里·林恩
13年5月12日在10:21

#3 楼


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


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

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


“复制”属性的效率是否比“保留”属性低?



如果您的媒体资源正在传递NSString实例,答案是“否”-复制的效率不比保留低。(效率也不低,因为NSString足够聪明,无法实际执行复制。)
如果您的媒体资源传递给NSMutableString实例,然后答案是“是”-复制的效率比保留的效率低。(效率低,因为必须进行实际的内存分配和复制,但这可能是可取的。)
通常来说“复制”属性的效率可能较低-但是,通过使用NSCopying协议,可以实现一个与保留副本“同样有效”的类。 NSString实例就是一个例子。


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


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

retain对象可能有性能原因,但它会带来维护开销-您必须管理代码外部的内部状态更改的可能性。就像他们说的-最后优化。


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


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


您班上的其他用户使用copy时将获得性能上的好处。
copy批注使您自己的代码更易于维护-copy批注指示您真的不需要担心该对象在其他地方的状态更改。


#4 楼

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


在将对象分配给属性时,是否要保留该对象的值?使用copy。
是否要保留该对象,并且不在乎其当前内部值或将来的内部值?请使用强项(保留)。

举例说明:我要保留名称“ Lisa Miller”(副本)还是要保留Lisa Miller人(强烈)的名称?她的名字后来可能会更改为“ Lisa Smith”,但她仍然是同一个人。

#5 楼

通过此示例,可以像下面这样解释复制和保留:

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]字符串中。

#6 楼

肯定会在使用面向对象的环境中将“复制”放在属性声明中,而在这种环境中,堆上的对象是通过引用传递的-这样做的好处之一是,在更改对象时,所有对该对象的引用查看最新更改。许多语言都提供'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.


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

#7 楼

@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


#8 楼

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

@property (nonatomic, copy) NSString* name;


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

NSCopying协议参考


通过保留原始而不是创建
new来实现NSCopying当类及其内容不可变时进行复制


值对象


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


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


#9 楼

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

评论


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

– gnasher729
2014年3月21日在12:06

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

–布莱恩·普(Bryan P)
2014年5月15日下午5:11

#10 楼

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