Reputation: 73
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]));
}
}
Upvotes: 7
Views: 2362
Reputation: 63667
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
Upvotes: 9
Reputation: 31
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.
Upvotes: 2
Reputation: 42588
I think you are misunderstanding of what copy does.
NSMutableString *string = [NSMutableString stringWithString:@"test"];
XYZPerson *guy = [XYZPerson init];
guy.firstName = string;
guy.lastName = string;
[string replaceCharactersInRange:NSMakeRange(1, 1) withString:@"x"];
[guy sayHello];
Output
This is test txst
In this example, firstName
is copy do it doesn't change when string
is changed, lastName
is not copy so it value is changed when the mutable string string
is changed.
What happened here is lastName
and string
are the same object, so when string
is changed lastName
is changed as a side effect. This is considered very bad and you never want this behavior. Using copy makes sure firstName
and string
are different objects and changes to string
cannot effect firstName
.
Upvotes: 0