Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Usage of the "copy" property attribute to maintain an immutable NSString

I am very new to iOS development and programming in Objective-C. I have been doing the exercises on the app dev library.

This is the current exercise that I am trying to understand. 3. Test what happens if you set a mutable string as the person’s first name, then mutate that string before calling your modified sayHello method. Change the NSString property declarations by adding the copy attribute and test again.

I attempt to do this however, the NSString that I modify does in fact change despite the use of the copy property attribute.

Here are my declarations and implementations as well as my test code.

XYZPerson.h
#import <Foundation/Foundation.h>

@interface XYZPerson : NSObject

@property (copy) NSString *firstName;
@property NSString *lastName;
@property NSDate *dob;

- (void)sayHello;
- (void)saySomething:(NSString *)greeting;

+ (id)init;
+ (id)personWithFirstName:(NSString *)firstName lastName:(NSString *)lastName dob:(NSDate   *)dateOfBirth;


@end

//XYZPerson.m
#import "XYZPerson.h"

@implementation XYZPerson

@synthesize firstName = _firstName;
@synthesize lastName = _lastName;
@synthesize dob = _dob;


- (void)sayHello {
    [self saySomething:@"Hello World!"];
    NSLog(@"This is %@ %@", self.firstName, self.lastName);
}

- (void)saySomething:(NSString *)greeting {
    NSLog(@"%@", greeting);
}

+ (id)init {
    return [self personWithFirstName:@"Yorick" lastName:@"Robinson" dob:8/23/1990];
}

+ (id)personWithFirstName:(NSString *)firstName lastName:(NSString *)lastName dob:(NSDate   *)dateOfBirth{
    XYZPerson *person = [[self alloc] init];
    person.firstName = firstName;
    person.lastName = lastName;
    person.dob = dateOfBirth;

    return person;
}

@end

//Test code
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
#import "XYZPerson.h"
#import "XYZShoutingPerson.h"


int main(int argc, char *argv[])
{
    @autoreleasepool {
        XYZPerson *guy = [XYZPerson init];
        [guy sayHello];

        //I thought that this change would never be made, but it is everytime I run the code.
        guy.firstName = @"Darryl";
        [guy sayHello];

        XYZShoutingPerson *girl = [XYZShoutingPerson init];
        [girl sayHello];
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}
like image 821
smitty Avatar asked Dec 23 '12 02:12

smitty


2 Answers

Consider this shorter example (which runs in CodeRunner btw):

#import <Foundation/Foundation.h>

@interface Person : NSObject
@property (nonatomic,strong) NSString *name; // strong should be copy
@end

@implementation Person
@end

int main(int argc, char *argv[]) {
    @autoreleasepool {
        Person *p = [Person new];

        NSMutableString *name = [[NSMutableString alloc] initWithString:@"Alice"];
        p.name = name;
        NSLog(@"%@",p.name); // prints Alice

        [name appendString:@"xxx"];
        NSLog(@"%@",p.name); // prints Alicexxx
    }
}

I'm pointing the name to a mutable string, then appending some characters. As a result, the name has changed inside the object. However, if you replace strong with copy when declaring the property, a new immutable string will be created just for the Person object.

The moral of the story is, using copy prevents side effects when someone passes an object and then that object changes.

The message -[NSString copy] results in a copy when a mutable string is passed (NSMutableString) or retain when it is immutable (NSString). Therefore, always copy when declaring NSString properties:

@property (nonatomic,copy)   NSString *string;  // OK
@property (nonatomic,strong) NSString *string;  // strong should be copy 
like image 114
Jano Avatar answered Nov 04 '22 04:11

Jano


I ran into this problem when I was doing the same book. I added copy and the exact same thing happened, it kept mutating when I appending something to the NSMutableString variable that I used for firstName. Then I read this section:

If you need to set a copy property’s instance variable directly, for example in an initializer method, don’t forget to set a copy of the original object:

-(id)initWithSomeOriginalString:(NSString *)aString { self = [super init]; if (self) { _instanceVariableForCopyProperty = [aString copy]; } return self; }

So, I went back into my XYZPerson.m and looked at my init code.

I changed:

- (id)initWithFirstName:(NSMutableString *)aFirstName lastName:(NSString *)aLastName
        dateOfBirth:(NSDate *)aDate    {
self = [super init];

if (self) {
    _firstName = aFirstName;
    _lastName = aLastName;
    _dateOfBirth = aDate;
}

return self;

}

To:

- (id)initWithFirstName:(NSMutableString *)aFirstName lastName:(NSString *)aLastName
        dateOfBirth:(NSDate *)aDate    {
self = [super init];

if (self) {
    _firstName = [aFirstName copy];
    _lastName = aLastName;
    _dateOfBirth = aDate;
}

return self;

}

And presto-chango: it worked the correct way! It made a copy of the NSMutableString that I had used that did not mutate when I appended something to the end of it before the method call.

like image 42
Josh Gel Avatar answered Nov 04 '22 04:11

Josh Gel