使用“copy”属性来维护一个不可变的NSString

在Objective-C中,我对iOS开发和编程非常陌生。 我一直在做应用程序开发库的练习。

这是我目前正在尝试理解的练习。 3.testing如果将可变string设置为人的名字,会发生什么情况,然后在调用修改后的sayHello方法之前对该string进行变异。 通过添加复制属性更改NSString属性声明并再次testing。

我试图做到这一点,但是我修改的NSString实际上改变了,尽pipe使用了copy属性。

这里是我的声明和实现以及我的testing代码。

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

考虑这个较短的例子(运行在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 } } 

我将这个名字指向一个可变string,然后附加一些字符。 结果,名称在对象内部发生了变化。 但是,如果在声明属性时用强制replace为强制,则只会为Person对象创build一个新的不可变string。

这个故事的寓意是,当有人传递一个对象,然后该对象改变时,使用副本可以防止副作用。

消息-[NSString copy]会在传递可变string(NSMutableString)时产生副本,或者在不可变时(NSString)保留副本。 因此,在声明NSString属性时总是要复制:

 @property (nonatomic,copy) NSString *string; // OK @property (nonatomic,strong) NSString *string; // strong should be copy 

我在做同一本书时遇到了这个问题。 我添加了副本,发生了完全相同的事情,当我将某些东西添加到用于firstName的NSMutableStringvariables时,它会不断变化。 然后我读了这个部分:

如果您需要直接设置复制属性的实例variables,例如在初始化方法中,请不要忘记设置原始对象的副本:

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

所以,我回到了我的XYZPerson.m,看着我的init代码。

我变了:

 - (id)initWithFirstName:(NSMutableString *)aFirstName lastName:(NSString *)aLastName dateOfBirth:(NSDate *)aDate { self = [super init]; if (self) { _firstName = aFirstName; _lastName = aLastName; _dateOfBirth = aDate; } return self; 

}

至:

 - (id)initWithFirstName:(NSMutableString *)aFirstName lastName:(NSString *)aLastName dateOfBirth:(NSDate *)aDate { self = [super init]; if (self) { _firstName = [aFirstName copy]; _lastName = aLastName; _dateOfBirth = aDate; } return self; 

}

而presto-chango:它是正确的方式! 它创build了一个NSMutableString的副本,当我在方法调用之前将其附加到它的末尾时,我没有使用它。

我想你是误解了什么副本。

 NSMutableString *string = [NSMutableString stringWithString:@"test"]; XYZPerson *guy = [XYZPerson init]; guy.firstName = string; guy.lastName = string; [string replaceCharactersInRange:NSMakeRange(1, 1) withString:@"x"]; [guy sayHello]; 

产量

 This is test txst 

在这个例子中, firstName是副本,当string被改变时它不会改变, lastName不是复制,所以当可变stringstring被改变时,值被改变。


这里发生的事情是lastNamestring是同一个对象,所以当string被改变时, lastName被改变为副作用。 这被认为是非常糟糕的,你永远不会想要这样的行为。 使用副本确保firstNamestring是不同的对象,对string更改不能影响firstName