7

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

这是我试图理解的当前练习。3. 测试如果将可变字符串设置为人的名字会发生什么,然后在调用修改后的 sayHello 方法之前改变该字符串。通过添加复制属性来更改 NSString 属性声明并再次测试。

但是,我尝试这样做,尽管使用了复制属性,我修改的 NSString 实际上确实发生了变化。

这是我的声明和实现以及我的测试代码。

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

3 回答 3

9

考虑这个较短的示例(顺便说一句在CodeRunner中运行):

#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 对象创建一个新的不可变字符串。

这个故事的寓意是,当有人通过一个对象然后该对象发生变化时,使用复制可以防止副作用。

当传递一个可变字符串(NSMutableString)时,该消息-[NSString copy]会生成一个副本,或者当它是不可变的(NSString)时会保留该消息。因此,在声明 NSString 属性时总是复制:

@property (nonatomic,copy)   NSString *string;  // OK
@property (nonatomic,strong) NSString *string;  // strong should be copy 
于 2012-12-23T03:26:31.370 回答
2

我在写同一本书时遇到了这个问题。我添加了副本并发生了完全相同的事情,当我将某些内容附加到我用于 firstName 的 NSMutableString 变量时,它一直在变化。然后我读了这个部分:

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

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

所以,我回到我的 XYZPerson.m 并查看了我的初始化代码。

我变了:

- (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:它以正确的方式工作!它复制了我使用过的 NSMutableString 的副本,当我在方法调用之前将某些内容附加到它的末尾时,它并没有发生变异。

于 2014-02-25T15:45:40.503 回答
0

我认为您误解了 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是同一个对象,所以 when stringis lastNamechanged 作为副作用而改变。这被认为是非常糟糕的,你永远不会想要这种行为。使用副本确保不同的对象firstNamestring更改string无法生效firstName

于 2012-12-23T03:30:26.220 回答