在属性中使用(copy)特性有什么实际意义吗?

3

我曾以为copy属性的目的是让我可以仅仅将一个属性赋值给另一个属性,然后底层的getter/setter方法会正确地处理所有事情。但是在阅读了许多文章(包括一些在stackoverflow上的文章)之后,我并不清楚相对于显式使用适当的方法来复制数据,是否有任何真正的好处。首先,我发现将可变字符串分配给另一个可变字符串仍然会使你得到一个不可变字符串,那么这有什么好处呢?

这对其他类是否有效?换句话说,通常来说,如果我有以下代码:

 @property (copy) Foo* f1;
 @property (copy) Foo* f2;
           ....
 @property (copy) Foo* fn;

我一般可以写

f1 = f2
f3 = f2

并获取适当的副本,即完全独立的对象?


3
“working properly”是什么意思?你对可变字符串有什么问题?如果f1被声明为@property (copy) Foo* f1;,那么 self.f1 = self.f2 的作用是(这里简化了):_f1 = [_f2 copy]copy 的工作方式取决于类 Foo。 - Martin R
@MartinR 我认为他的“可变字符串问题”在于,调用 copy 方法返回的是一个不可变的 NSString,这是很不直观的。 - Aaron Brager
如果是这样的话,让我来介绍一下@david给-mutableCopy吧;-) - jemmons
是的,这基本上就是问题所在了。一旦我遇到这个问题,我想确保带有复制属性的属性的getter(至少在理论上)应该使用适当的“copy”(clone可能更好)方法来创建一个独立的对象副本。 - David
我完全理解,我可以明确地自己使用适当的复制方法(因此我可以明确地使用mutableCopy),但问题似乎在于MutableString的(copy)属性在getter中没有使用-mutableCopy。 - David
4个回答

6

(copy)将调用正在设置的项目上的copy方法,因此它将取决于对象。让我们看一个NSString的例子,看看这是否有帮助?

两个类

@interface ObjectWithCopy : NSObject

@property (copy) NSString *aString;

@end

以及ObjectWithoutCopy

@interface ObjectWithoutCopy : NSObject

@property (strong) NSString *aString;

@end

这很容易,我们都知道NSString是不可变的,那么"copy"会做什么呢?记住NSMutableString是NSString的子类。因此我们可以将其传递给setter。这样做会发生什么呢?

NSMutableString *aMutableString = [[NSMutableString alloc] initWithString:@"Hello World"];

ObjectWithCopy *objectWithCopy = [[ObjectWithCopy alloc] init];
objectWithCopy.aString = aMutableString;
NSLog(@"ObjectWithCopy.aString = %@", objectWithCopy.aString);

//Now we change aMutableString
[aMutableString appendString:@" and every other world"];
NSLog(@"ObjectWithCopy.aString after aMutableString was modified = %@", objectWithCopy.aString);


NSLog(@"Now what happens without copy?");

// Reset aMutableString
aMutableString = [[NSMutableString alloc] initWithString:@"Hello World"];

ObjectWithoutCopy *objectWithoutCopy = [[ObjectWithoutCopy alloc] init];
objectWithoutCopy.aString = aMutableString;
NSLog(@"ObjectWithoutCopy.aString = %@", objectWithoutCopy.aString);

//Now we change aMutableString and see what objectWithoutCopy.aString is?
[aMutableString appendString:@" and every other world"];
NSLog(@"ObjectWithoutCopy.aString after aMutableString was modified = %@", objectWithoutCopy.aString);

输出?

2014-02-02 10:40:04.247 TableViewCellWithAutoLayout[95954:a0b] ObjectWithCopy.aString = Hello World
2014-02-02 10:40:04.248 TableViewCellWithAutoLayout[95954:a0b] ObjectWithCopy.aString after aMutableString was modified = Hello World
2014-02-02 10:40:04.248 TableViewCellWithAutoLayout[95954:a0b] Now what happens without copy?
2014-02-02 10:40:04.248 TableViewCellWithAutoLayout[95954:a0b] ObjectWithoutCopy.aString = Hello World
2014-02-02 10:40:04.249 TableViewCellWithAutoLayout[95954:a0b] ObjectWithoutCopy.aString after aMutableString was modified = Hello World and every other world

哇——我们的不可变字符串在ObjectWithoutCopy上被改变了?!?这是因为它实际上是一个NSMutableString,如果没有复制操作,我们只是指向传递进来的任何东西。因此,对于那个传递进来的对象所做的任何更改都将在类和函数变量中看到。这就是为什么经常建议在拥有NSString属性时使用(copy),因为你不希望NSString发生变化。
复制使得赋值语句变成这样:_aString = [passedInString copy];。如一些人所指出的,能够执行任何符合NSCopying协议的内容,但应创建独立副本以确保外部世界的更改不会影响您的类。因此,某人可以创建一个NSString子类(你可能永远不应该这样做)覆盖复制操作无效,你仍然可以看到NSString在你的控制下发生变化,但一般来说,NSCopying是正确的,至少这是你真正能做到的最好的。

第1版: 正如@David在他的评论中提到的(也许是他真正的问题?),[aMutableString copy]实际上返回一个类型为NSString的对象。我完全同意这很令人困惑。如果我正在制作NSMutableString,我会让复制返回一个NSMutableString。实际上,我认为真正的WTF在于Apple创建了一个不可变类的可变子类。在C#/Java中,你没有一个可变的字符串,而是使用另一个称为StringBuilder的类。
那么如果这么令人困惑,为什么还有那么多人在NSString属性上使用copy?因为它很可能做到了你想要的。如果您的类具有NSString属性,则选择该属性是因为您不想担心字符串在你的控制下改变。为此,您真正需要的是字符串副本。如果它是NSMutableString,则您可能希望得到一个NSString复件,因为您不希望将那个可变的字符串赋出去,并且使其在您的控制下发生更改。如果NSMutableString上的复制返回NSMutableString,我不认为人们会使用复制属性,但会创建一个自定义setter来设置_aString = [[NSString alloc] initWithString: inputString]。这可能更清晰,如果你认为它是这样,那就是你应该做的。但是此时在NSString属性上使用复制已经成为传统,所以我可能会继续使用它。您可能同意或不同意,但如果您的问题是为什么人们使用复制,那就是我个人的原因。
编辑2: @David问为什么不这样复制?“如果我复制一个字符串,那么我希望后续的调用能够改变它。”我认为,如果您真的想要副作用,您可能应该声明一个没有复制修饰符的NSMutableString (您可能永远不应该使用copy和NSMutableString,而是创建自己的设置器)。
在我近15年的编程中,我真的想不出为什么我不想让设置字符串的人产生副作用,但我想让任何获得字符串副作用的普通人。那真的很奇怪。
此外,我不能为了我想要的情况(但只有设置器(您不能控制)告诉您)而将其广告化为NSString。我的意思是为什么?为什么你想让你的不可变属性有时可变,但只有当设置器(你不控制)告诉你要 ,但你不希望来自设置对象的副作用。并且你不告诉任何人,他们必须自己弄清楚?我的头疼。
我猜这就是苹果所做的事情。但这只是猜测。
此外,如果您可以使用不可变的数据类型,那么应该这样做。它减少了所有类型的复杂性,特别是多线程。
当然,如果你真的非常想这样做,Objective-C (和大多数OOP语言)都可以让你这样做。这取决于您的程序员和团队。您还可以通过将手机撞击到头骨上来测试加速度计。我不认为苹果推荐任何一种方法。

没错,但实际上并不是这样的。NSMutableString 的复制会返回一个 NSString。在这种情况下是很好的,因为我们不希望任何人修改我们的字符串,即使我们将其传递给另一个对象。但如果你想要的话,你可以创建一个 setter 属性来检查是否传入了 NSMutableString 并执行 [[NSMutableString alloc] initWithString:passedIn]。如果你不喜欢 copy 的行为,就不要使用它。但你的问题是什么时候会使用它?如果你实际上想要一个 NSString,即使传入的是 NSMutableString,那就是一个例子。我相信还有其他情况。 - ansible
我不明白为什么这样做是好的。如果我有一个带有复制属性的NSMutableString属性,并且我将其复制,那么获得一个可以独立操作的新NSMutableString似乎是相当合理的。如果它只是一个NSString属性,那么深度复制也无关紧要。当然,我不一定非要使用它(叹气),但我想真正的问题是为什么NSMutableString的“copy”方法不返回一个新的NSMutableString,这将允许复制属性表现得合理(和直观)。 - David
@David - 我不能告诉你为什么苹果会这样做,但我可以解释为什么我仍然在使用NSString的复制,尽管他们这样做了。我在结尾处添加了一点来解释。 - ansible
@David - 我相信 copy 只影响 setter,它创建一个副本然后分配给所有人。没有更多的测试我不是100%确定。另外,我再次更新了我的答案以回应你的第一个问题。希望有所帮助。 - ansible
是的,我现在完全理解了——我最初只是因为我的第一个实验从NSMutableString返回了一个NSString而感到困惑。关键(我认为)是要认识到(copy)属性与发送NSObject copy消息(当然只会触发copyWithZone)是100%等效的,并且结果取决于该方法在特定类中的作用。 - David
显示剩余3条评论

0

是的,你会得到完全独立的对象。复制属性还有什么作用呢?


我希望它能够返回相同类型的对象! - David

0

我认为复制说明符在必要时会创建一个对象的新实例。例如,如果对象是可变的(当然是传递给setter的对象)。如果不是,它通常只是保留它。 请注意,复制意味着逻辑上复制了实例。实现方式(内存复制或仅额外保留)实际上并不那么重要。


0

copy关键字只是意味着在赋值时会调用对象的-copy方法(参见:NSCopying协议)。这个方法的目的通常是返回一个“完全独立的对象”,所以通常你会得到这样的结果。当然,也有例外。

例如,如果您的对象是一个不可变的值(NSString是典型的例子),那么没有理由去-copy它来获得一个“独立的对象”。因此,在这种情况下,为了优化,-copy只会导致retain,而不是新对象。

当然,任何自定义类或子类都可以重写-copy-copyWithZone:来执行任意数量的不可预测的破坏性操作。但是惯例是返回一个“独立的对象”——或者在“独立”的上下文中返回self


你可能需要注意的是,即使NSString的copy方法只执行保留操作,但如果传入了一个具有非平凡“-copy”函数的子类(如NSMutableString),仍然可能需要使用“copy”关键字。 - ansible
真的。您永远不应该试图“聪明地”超越属性说明符。沿着“我知道NSString-copy实现只会调用保留,所以我会将其标记为strong并节省一步”的思路是错误的,因为首先,您的接口应始终记录您的意图,而不是公开实现细节。其次,正如@ansible所指出的那样,没有什么可以阻止某人将NSMutableString设置为NSString属性 - 在这种情况下,-copy是必要的,以避免错误。 - jemmons

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接