我对iOS开发和Objective-C编程非常陌生。我一直在做应用程序开发库的练习。
这是我想要理解的当前练习。3.测试如果将一个可变字符串设置为该人的名字会发生什么,然后在调用修改后的sayHello方法之前对该字符串进行修改。通过添加copy属性来改变NSString属性声明,然后再次测试。
我尝试这样做,然而,我修改的NSString实际上改变了,尽管使用了copy property属性。
这是我的声明和实现,以及我的测试代码。
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]));
}
}
考虑这个较短的示例(在coderrunner中运行):
#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
}
}
我将名称指向一个可变字符串,然后附加一些字符。结果,对象内部的名称发生了变化。但是,如果在声明属性时将strong替换为copy,则将仅为Person对象创建一个新的不可变字符串。
这个故事的寓意是,当有人传递一个对象,然后该对象发生变化时,使用复制可以防止副作用。
消息-[NSString copy]
在传递可变字符串(NSMutableString)时产生副本,当传递不可变字符串(NSString)时保留副本。因此,在声明NSString属性时总是复制:
@property (nonatomic,copy) NSString *string; // OK
@property (nonatomic,strong) NSString *string; // strong should be copy
我在写同一本书的时候遇到了这个问题。我添加了copy,同样的事情发生了,当我向用于firstName的NSMutableString变量添加一些东西时,它一直在变化。然后我读了这一段:
如果你需要直接设置copy属性的实例变量,例如在初始化方法中,不要忘记设置原始对象的副本:
-(id)initWithSomeOriginalString:(NSString *)aString { self = [super init]; if (self) { _instanceVariableForCopyProperty = [aString copy]; } return self; }
所以,我回到了我的XYZPerson。查看我的初始化代码。
我改变了:
- (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;
}
很快,它的工作方式是正确的!它复制了我使用过的NSMutableString,当我在方法调用之前在它的末尾添加了一些东西时,它不会发生变化。
我想你误解了copy的作用。
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
不是复制的,所以当可变字符串string
改变时它的值会改变。
这里发生的是lastName
和string
是同一个对象,所以当string
被改变时,lastName
作为副作用被改变。这被认为是非常糟糕的,你永远不会想要这种行为。使用复制可以确保firstName
和string
是不同的对象,并且对string
的更改不会影响firstName
。