copy vs. retain example using NSString/NSMutableString

As part of my new ventures into Objective-C I was looking at the difference between copy and retain for properties. In the process I wrote a little test app which I hope might also be of use for others since an example usually says more than a couple of words.

So, I’ve got a basic command line OSX project in XCode with a main.mm and an objective-c class called SJLItem in SJLItem.h/mm. SJLItem contains an NSString* property that I will use to test copy/retain semantics with;

@interface SJLItem : NSObject
@property (nonatomic,copy) NSString* itemName;
-(instancetype)initWithName:(NSString*)name;
-(NSString*) description;
@end

Note; the string property is marked as being “copy“.

SJLItem.mm looks like this;

#import "SJLItem.h"

@implementation SJLItem
-(instancetype) initWithItemName:(NSString *)name {
 self = [super init];
 if ( self ) {
 self.itemName = name; //<NOTE! If you use _itemName things behave differently
 }
 return self;
}

-(NSString*) description {
 void* stringPtr = (__bridge void*)_itemName; //< so that I can print out the address of the pointer
 NSString* desc = [NSString stringWithFormat:@"\"%@\", _itemName*: %p",_itemName,stringPtr];
 return desc;
}
@end

I then wrote some test code in main.mm that creates an “SJLItem” instance with the name passed as strings that are either NSMutableString or NSString instances;

// a mutable string; we can change it after it's been created 
NSMutableString* mutableItemName = [NSMutableString stringWithFormat:@"mutable"];
// an immutable string; we can't change it once it's created
NSString* immutableItemName = [NSString stringWithFormat:@"immutable"];
 
// create an item with the immutable string 
SJLItem* anItem1 = [[SJLItem alloc] initWithItemName:immutableItemName];
// print some info (see nelow)
printStringAndItemInfo(imutableItemName, anItem1);

// create an item with a mutable string
SJLItem* anItem2 = [[SJLItem alloc] initWithItemName:mutableItemName];
printStringAndItemInfo(mutableItemName, anItem2);

// change the mutable string 
[mutableItemName appendFormat:@" is mutated"];
// see what happened
printStringAndItemInfo(mutableItemName, anItem2);

Where “printStringAndItemInfo” is a helper which prints out the item and the address of the string passed in;

void printStringAndItemInfo(NSString* str, SJLItem* item)
{
 void* strPtr = (__bridge void*)str;
 NSLog(@"Item: %@, NSString*: %p",item,strPtr);
}

Running the test app I got the following output (this is now with the itemName property being “copy”) on my machine;

...Item: "immutable", _itemName*: 0x100200120, NSString*: 0x100200120
...Item: "mutable", _itemName*: 0x100109b90, NSString*: 0x100200210
...Item: "mutable", _itemName*: 0x100109b90, NSString*: 0x100200210

As you can see;

  • When an immutable string (NSString*) is passed to the designated initializer the pointer (or reference) held in the SJLItem instance remains the same as the one for the string itself. The compiler has correctly determined that the string is immutable and that it is therefore safe to just store the pointer directly to the string
  • When a mutable string (NSMutableString*) is used the itemName property in the SJLItem instance points to a different string (i.e. memory location) which has been allocated because the compiler has determined that it is not safe to keep the pointer, since the contents of this string type can change later.
  • To confirm I change the mutable string and as you can see the SJLItem’s itemName property remains unchanged, as is expected.

Changing the property to be “retain” changes the output to the following;

...Item: "imutable", _itemName*: 0x1001064e0, NSString*: 0x1001064e0
...Item: "mutable", _itemName*: 0x10010a380, NSString*: 0x10010a380
...Item: "mutable is mutated", _itemName*: 0x10010a380, NSString*: 0x10010a380

As you can see the pointers are all the same now (i.e. the SJLItem points to the same location as both strings, mutable or not) and therefore the change we make to the string after we’ve initialized the SJLItem is picked up by the item. This is as it should be.

It’s pretty simple really, but it’s always good to “do the exercise yourself” to see that things really work the way you expect them to.

Note

In my original initializer the line where the itemName property is assigned looked like this;

_itemName = name;

This does not work as expected because we are now assigning the property value directly, without checking if the source is mutable or immutable. I was, quite frankly, a bit surprised at how easy it was to bypass the safety net of property doing this and was at least expecting there to be a warning from the compiler but there wasn’t so it’s one to keep in mind for later.

 

Advertisements