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

@interface SomeClass : NSObject
{
    NSString* name;
}

@property (nonatomic, retain) NSString* name;

@end

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

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


Copy应该用于NSString。如果它是可变的,那么它就会被复制。如果不是,那么它就会被保留。正是你在应用程序中想要的语义(让类型发挥最好的作用)。


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

以下是你想这么做的原因:

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

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

[someName setString:@"Debajit"];

Person.name属性的当前值将根据属性被声明为retain还是copy而有所不同——如果属性被标记为retain,则为@"Debajit",但如果属性被标记为copy,则为@"Chris"。

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


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


Surely putting 'copy' on a property declaration flies in the face of using an object-oriented environment where objects on the heap are passed by reference - one of the benefits you get here is that, when changing an object, all references to that object see the latest changes. A lot of languages supply 'ref' or similar keywords to allow value types (i.e. structures on the stack) to benefit from the same behaviour. Personally, I'd use copy sparingly, and if I felt that a property value should be protected from changes made to the object it was assigned from, I could call that object's copy method during the assignment, e.g.:

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.

在这种情况下,不使用copy,我们的contact对象自动接受新值;但是,如果我们确实使用了它,我们必须手动确保检测到更改并同步。在这种情况下,保留语义可能是可取的;在另一种情况下,copy可能更合适。


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

是的——通常总是使用copy属性。

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

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

If your property is being passed an NSString instance, the answer is "No" - copying is not less efficient than retain. (It's not less efficient because the NSString is smart enough to not actually perform a copy.) If your property is passed an NSMutableString instance then the answer is "Yes" - copying is less efficient than retain. (It's less efficient because an actual memory allocation and copy must occur, but this is probably a desirable thing.) Generally speaking a "copied" property has the potential to be less efficient - however through the use of the NSCopying protocol, it's possible to implement a class which is "just as efficient" to copy as it is to retain. NSString instances are an example of this.

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

当您不希望属性的内部状态在没有警告的情况下发生变化时,应该始终使用copy。即使对于不可变对象,正确编写的不可变对象也可以有效地处理复制(参见下一节关于不可变性和NSCopying)。

保留对象可能是出于性能方面的考虑,但这带来了维护开销——您必须管理内部状态在代码外部更改的可能性。正如他们所说,最后优化。

但是,我把我的类写成了不可变的——难道我不能“保留”它吗?

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

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


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

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

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

[someName setString:@"Debajit"];

如果属性的类型是copy,则,

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

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

但是在保留的情况下,

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

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


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

当我将对象赋值给我的属性时,我是否想保留对象在时间点上的值?使用复制。 我是否想保留这个对象而不关心它当前的内部值或将来的内部值?使用strong(保留)。

举例说明:我是想坚持“丽莎·米勒”这个名字(复制)还是我想坚持丽莎·米勒这个人(坚强)?她的名字以后可能会改成“丽莎·史密斯”,但她还是同一个人。


@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协议参考

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

值对象

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

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

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