'copy' seems not working


#1

In the code below, I expected NSLog outputs would be the following:

“str: Backpack, itemName: Backpack …”
“str: Frontpack, itemName: Backpack …”

However, itemName seems not copied even with ‘copy’ property attribute, resulting in the same string as the mutable string changed.

“str: Backpack, itemName: Backpack …”
“str: Frontpack, itemName: Frontpack …”

Any idea? Thanks.


BNRItem.h

@property (nonatomic, copy) NSString *itemName;


main.m

        NSMutableString *mutableString = [[NSMutableString alloc] initWithString:@"Backpack"];
        
        BNRItem *item = [[BNRItem alloc] initWithItemName:mutableString
                                             valueInDollars:5
                                               serialNumber:@"4F2W7"];
        NSLog(@"str: %@, itemName: %@", mutableString, item.itemName);
        [mutableString replaceOccurrencesOfString:@"Back"
                                       withString:@"Front"
                                          options:0
                                            range:NSMakeRange(0, [mutableString length])];
        NSLog(@"str: %@, itemName: %@", mutableString, item.itemName);

#2

What does your initWithItemName:valueInDollars:serialNumber: method look like in BNRItem? The code you have posted doesn’t use the setter for your property.


#3

Great! Thanks. It works after changing to use accessor instead of ivar. The book may need additional comments since it says “We typically set the instance variables directly in initializes, instead of calling accessor methods.”


- (instancetype)initWithItemName:(NSString *)name ... ...
<<        _itemName = name;
>>        self.itemName = name;

#4

There are different opinions on whether or not to use properties from inside of init methods. See here for one article on the subject: http://qualitycoding.org/objective-c-init/.
If you decide to go with using instance variables (ivars), you would need to use the following:

_itemName = [name copy];

#5

Oh, good point. Thanks.

To sum, it does not work if we use ‘copy’ with direct access to ivar in init since the accessor doing copy is not called, although it’s likely we use it believing copy would work.

@property (nonatomic, copy) NSString *itemName;
_itemName = name;    // init

And it is not recommended to use self-dot in init though it calls the accessor doing copy.

@property (nonatomic, copy) NSString *itemName;
self.itemName = name;    // init

So, we need to do copying directly in init for every object having mutable subclass. Since it looks forgettable, do we need to use ‘copy’ for every object since immutable class does not actually copy, though it sounds confusing? And ‘copy’ attribute seems never being used with direct access in initializer.

@property (nonatomic, strong) NSString *itemName;
_itemName = [name copy];     // init

#6

You only need to use copy for objects for which a mutable subclass exists. You’re right that copy doesn’t really do anything for an immutable object, but it doesn’t need to because you can’t change it in the first place. Remember that the reason you’re making the copy of a potentially mutable object is so that it can’t be changed afterwards.


#7

The book is misleading in that it gives the specific example above with the NSMutableString (Loc 2631 in the Kindle version), saying that adding the “copy” attribute to the itemName property will protect it from NSMutableStrings from messing up the immutable property of BNRItem. The thing is, they never ask you to either 1) change the init method to either use the itemName accessor, or 2) change the init method to use explicitly copy. It implies, instead, that by simply using the “copy” attribute, the initWithItemName function will magically copy the values. In their sample BNR Solutions answer file, they similarly don’t make any change in the initWithItemName. Thus, the example they give with NSMutableString still breaks.