3

I had assumed that the purpose of the copy attribute was so that I could just assign one property to another (say) and have the underlying getter/setter methods handle everything properly. But having reviewed a number of articles (including some on stackoverflow) it's not clear to me that there's any real benefit over simply using the appropriate methods to copy stuff explicitly. For a start, I discovered that assigning a mutable string to another mutable string still leaves you with a non-mutable string, so what good is that?

Does this work properly for other classes? In other words, in general, if I have

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

can I generally write

f1 = f2
f3 = f2

and get proper copies, i.e. completely independent objects?

David
  • 5,991
  • 5
  • 33
  • 39
  • 3
    What do you mean by working properly? And what is your problem with a mutable string? - If f1 is declared as `@property (copy) Foo* f1;` then `self.f1 = self.f2` does (I am simplifying things here!): `_f1 = [_f2 copy]`. How a `copy` works depends on the class `Foo`. – Martin R Feb 02 '14 at 16:17
  • @MartinR I think his "problem with a mutable string" is that it's unintuitive that calling `copy` on an NSMutableString would return an immutable NSString. – Aaron Brager Feb 02 '14 at 16:55
  • If that's the case, allow me to introduce @david to [`-mutableCopy`](https://developer.apple.com/library/mac/documentation/cocoa/reference/foundation/Classes/NSObject_Class/Reference/Reference.html) ;-) – jemmons Feb 02 '14 at 21:41
  • Yeah, that's essentially the issue and once I ran into that, I wanted to make sure that that a getter for a property with the copy attribute is (at least theoretically) supposed to make an independent copy of an object using the appropriate 'copy' (clone would be a better term) method – David Feb 02 '14 at 21:43
  • I get completely that I can explicitly use appropriate copy methods myself (so I could use mutableCopy explicitly) but the issue seems to be that the (copy) attribute for a MutableString is NOT using that -mutableCopy in the the getter. – David Feb 02 '14 at 21:45

4 Answers4

6

(copy) will call the copy method on the item being set, so it will depend on the object. Let's see an example with NSString and see if that helps?

Two classes ObjectWithCopy

@interface ObjectWithCopy : NSObject

@property (copy) NSString *aString;

@end

and ObjectWithoutCopy

@interface ObjectWithoutCopy : NSObject

@property (strong) NSString *aString;

@end

Pretty easy so far, and we all know NSString is immutable, so what will "copy" do? Well remember NSMutableString is a subclass of NSString. So we could pass that in to the setter instead. What happens when we do this?

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);

The output?

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

Whoa - our immutable string was changed on ObjectWithoutCopy!?!? That's because it really was a NSMutableString after all and without copy we are simply pointing to whatever was passed in. So any changes that occurred with that passed in object will be seen in both the class and the function variable. That is why it's so often suggested that when you have an NSString property you use (copy), you don't expect an NSString to change.

Copy makes the assign statement this _aString = [passedInString copy];. As some have pointed out can do anything so long as it conforms to the NSCopying protocol, but should create an independent copy of the object to make sure change the outside world makes doesn't affect your class. So someone could create a subclass of NSString (which you probably should never do) that overrides copy to do nothing and you could still see your NSString change out from under you, but in general it's a pretty safe bet that NSCopying was done correctly. At least that's the best you can really do.

Edit 1: As @David mentions in his comment (and is maybe his real question?), [aMutableString copy] actually returns a object of type NSString. I completely agree this is confusing. If I was making NSMutableString I would have copy return a NSMutableString. Actually I think the real WTF here is that Apple has created a mutable subclass of a non-mutable class. In C#/Java, you don't have a mutable string, instead you use another class called StringBuilder.

So if this is so confusing, why do so many people use copy on NSString properties? Because it most likely does what you actually want it to. If your class has an NSString property, you picked that because you don't want to worry about the string changing out from under you. To do that you really want a copy of the string passed in. And if it's a NSMutableString you probably want an NSString copy because you don't want to give that mutable string out and have it changed out from under you. If copy on a NSMutableString returned an NSMutableString I don't think people would use the copy attribute, but create a custom setter that set _aString = [[NSString alloc] initWithString: inputString]. This is probably clearer, and if you think it is that it is maybe what you should do. But at this point it's convention to use copy on NSString properties, so I'll probably continue to use it. You may or may not agree with it, but your question is why do people use copy then this is why I do personally.

Edit 2: @David asks why doesn't copy work this way? "If I copy a string then I want a subsequent call to be able to change it." I think if you really want the side effects you probably should be declaring an NSMutableString without the copy modifier (you probably should never use copy and NSMutableString, but create your own setter).

I really can't think of a reason in my nearly 15 years of programming why I would not want the person setting the string's side effect but I would want any old person who get's the string's side effect. That's really weird.

Further I can't for the life of me think of why I would want that situation but advertise it as an NSString instead. I mean why? Why would want for your immutable property to be sometimes mutable, but only when the setter (which you don't control) tells you too but you don't want the side effects from from object that was set with. AND you don't tell any one about it and they have to just figure it out? My head hurts.

My guess is this is why Apple did what it did. But it's only a guess.

Further, if you can get away with an immutable data type you should. It reduces all sorts of complexity especially with multithreading.

Of course Objective-C (and most OOP languages) let you do all of this if you really really want to. That's up to you programmer and your team. You can also test the accelerometer by bashing your phone against your skull. I don't think Apple recommends either approach though.

Community
  • 1
  • 1
ansible
  • 3,569
  • 2
  • 18
  • 29
  • Yeah, I get that but it seems that the (copy) attribute on a MutableString returns an immutable string. I guess I expect (copy) to behave more like a (clone) so that I get back the same kind of object. – David Feb 02 '14 at 21:47
  • Yeah, fair enough, but that isn't what happens. A `NSMutableString` copy returns an `NSString`. Which in this case is good, because we don't want anyone messing with our string, even if we pass it out to another object. But if you wanted to you could create setter property that checked for NSMutableString being passed in and does `[[NSMutableString alloc] initWithString:passedIn]`. If you don't like what copy does, don't use it. But your question was when would you use it? If you actually wanted an `NSString`, even if `NSMutableString` was passed in is one example. I'm sure there are others. – ansible Feb 02 '14 at 22:39
  • I don't see why it's good. If I have an NSMutableString property with copy attribute and I copy it, then it seems quite reasonable to get a new NSMutableString that can be manipulated indepdently. If it's just an NSString property, then a deep copy would be irrelevant anyway. Of course I don't have to use it (sigh) but I guess the real question is why the 'copy' method of an NSMutableString doesn't return a new NSMutableString which would then allow the copy attribute to behave sensibly (and intuitively) – David Feb 03 '14 at 01:45
  • @David - I can't tell you why Apple did what it did, but I can explain why I still use copy on NSString despite what they did. I added a bit at the end of my to explain. – ansible Feb 03 '14 at 05:44
  • I just reread your (tremendously useful) explanation but I guess I'm still questioning the motivation. I get completely that one might not want someone else messing with a string that you get back from a mutable copy, but it's not obvious to me that that requirement should be under the control of a copy attribute. Ine could just as rationally justify the opposite requirement, I.e. if I copy a string then I want a subsequent call to be able to change it. – David Feb 03 '14 at 11:13
  • By the way, does the copy attribute apply to the setter as well, I.e. If I assign a string to a property with a copy attribute, then a new string is created? – David Feb 03 '14 at 11:14
  • @David - I believe copy ONLY effects the setter, it creates one copy that is then given out to everyone. Not 100% sure without more testing. Also I updated my answer again to respond to you first question. Hope it helps. – ansible Feb 03 '14 at 15:32
  • Yes, I understand it completely now ---- I was just confused originally after my first experiment returned an NSString from an NSMutableString. The key (I believe) is recognizing that the (copy) attribute is 100% equivalent to sending the NSObject copy message (which of course just triggers copyWithZone) and the results depend on what that method does in particular classes. – David Feb 03 '14 at 19:50
0

Yes, you get completely independent objects. What else would the copy attribute do?

ArtOfWarfare
  • 20,617
  • 19
  • 137
  • 193
0

I think that the copy specifier creates a new instance of an object if it is necessary. For example if the object is mutable (object passed to the setter of course). If not it usually just retains it. Consider that copy means that the instance is logically copied. The way it is implemented (memory copied or just extra retain) does not matter that much actually.

Vincent Zgueb
  • 1,491
  • 11
  • 11
0

The copy specifier just means that the -copy method of the object in question will be called during assignment (see: the NSCopying protocol). This method's purpose is usually to return a "completely independent object" so that's usually what you'll get. There are exceptions though.

For instance, if your object is an immutable value (NSString being the canonical example), then there's no reason to -copy it to get an "independent object". So in that case, as an optimization, -copy only results in a retain, not a new object.

Of course, any custom class or subclass can override -copy or -copyWithZone: to do any number of unpredictable, destructive things. But the convention is to return, as you say, an "independent object" — or self in contexts where "independent" doesn't mean anything.

jemmons
  • 18,605
  • 8
  • 55
  • 84
  • You may want to note though that even though the copy method for NSString only does a retain, you still may want a `copy` specifier incase a subclass, like NSMutableString is passed in that does have a non-trivial `-copy` function. – ansible Feb 02 '14 at 20:09
  • True. You should never try to "outsmart" your property specifier. Thinking along the lines of, "I know that `NSString`'s implementation of `-copy` is just going to call retain, so I'll mark this as `strong` and save a step" is wrong for two reasons. First, your interface should always document your intent, not expose implementation details. Second, as @ansible notes, there's nothing stopping someone from setting an `NSMutableString` to an `NSString` property — in which case the `-copy` is necessary to avoid bugs. – jemmons Feb 02 '14 at 21:36