我应该在Objective C中使用防御性副本吗?

时间:2013-11-04 02:26:09

标签: objective-c immutability

我正在玩可变性,我想出了下面的代码,其中一个不可变对象可以被转换为可变对象。

- (NSString *) getaString {
    NSMutableString * string = [[NSMutableString alloc] init];
    [string appendString:@"This "];
    [string appendString:@"was mutable."];
    return string;                          
}

- (void)viewDidLoad {
    [super viewDidLoad];
    //Get a string returns an NSString, which is then cast to a mutable string with a compler warning.
    NSMutableString * string = [self getaString];
    [string appendString:@" And apparently still is"];
    _showText.text = string;
}

或没有编译器警告

- (NSArray *) getaString {
    NSMutableString * string = [[NSMutableString alloc] init];
    [string appendString:@"This "];
    [string appendString:@"was mutable."];
    //Cast to NSString.
    NSString * immutableString = string;
    NSArray * array = [[NSArray alloc] initWithObjects:immutableString, nil];
    return array;                          
}

- (void)viewDidLoad {
    [super viewDidLoad];
    NSMutableString * string = [[self getaString] objectAtIndex:0];
    [string appendString:@" And apparently still is"];
    _showText.text = string;
}

UITextField显示整个字符串“This is mutable。显然仍然是”,没有编译器警告。我已经看到多个SO帖子建议简单地将可变对象作为不可变对象进行转换或使用,但正如我刚才所示,这可能很危险。此外,演员阵容仍然可以在没有阵列的情况下工作,但我确实收到了编译器警告。

我的问题是,我应该考虑使用java风格的防御性副本吗?在目标C中我没有看到任何防御性副本的提及,我在Apple的文档中找到的只是模糊地提到it’s best to adopt some defensive programming practices。我既关心安全性又关注防范粗心编码。

3 个答案:

答案 0 :(得分:4)

你给出的例子具有误导性。

您正在将NSMutableString存储在数组中,然后将其取出。为什么你希望字符串在那之后是不可变的?

然而,在某些情况下,防御性副本几乎是Objective-C的标准。

考虑一个类(我们称之为CustomClass)定义NSString的属性以及打印它的方法:

@property (nonatomic, strong) NSString *aString;
- (void)printTheString;

现在,由于NSMutableStringNSString的子类,因此该类的客户端可能会执行以下操作:

CustomClass *anObject = [CustomClass new];
NSMutableString *aMutableString = [NSMutableString stringWithString:@"Hey!"];
anObject.aString = aMutableString;
[anObject printTheString]; // Hey!
[aMutableString appendString:@" Got you!"];
[anObject printTheString]; // Hey! Got you!

在某些情况下可能会有危险。

对于具有可变子类的不可变类,使用copy属性而不是strong已成为惯例。

@property (nonatomic, copy) NSString *aString;

通过这种方式,在分配字符串时会生成防御性副本,防止客户端稍后弄乱该对象。

之前的示例将同时打印Hey!

值得注意的是,对于大多数此类,将copy发送到不可变对象会返回相同的对象,而不是实际的副本。通过这种方式,你可以吃蛋糕并吃掉它,因为副本只在需要时才会进行。

答案 1 :(得分:0)

我不确定你是否应该在Obj-c中制作防御性副本,但是可以制作不可变字符串的可变副本,并且可以制作不可变版本一个可变的字符串。

顺便说一句,代码中没有任何不可变的字符串。 NSArray中的NSMutableStrings仍然是可变的,因此您不进行任何转换。数组是不能附加对象的东西。

原因如下:数组只是将指针保存到NSMutableString,而不是实际对象,因此您不会通过更改字符串的内容来更改数组的内容,因为数组指向的内存地址保持不变。

示例:

NSString *original = @"Hello, world!";

// putting the original in a mutable string
NSMutableString *copy = [original mutableCopy];

NSArray *array = [NSArray arrayWithObjects:copy, nil];

// the object in the array can still be modified
[[array objectAtIndex:0] appendString:@" Goodbye, cruel world!"];

// make an immutable version of the mutable string
NSString *copyOfTheCopy =[NSString stringWithString:[array objectAtIndex:0]];

NSLog(@"%@", [array objectAtIndex:0]);

// just to make sure...
[[array objectAtIndex:0] appendString:@"Got you!"];

NSLog(@"%@", [array objectAtIndex:0]);
NSLog(@"%@", copyOfTheCopy);

输出应为:

2013-11-03 21:16:06.099 SomeProject[7045:303] Hello, world! Goodbye, cruel world!
2013-11-03 21:16:06.100 SomeProject[7045:303] Hello, world! Goodbye, cruel world!Got you!
2013-11-03 21:16:06.100 SomeProject[7045:303] Hello, world! Goodbye, cruel world!

Gabriele Petronella 给出的另一个答案让我做了一个改变,用指针摆脱了其中一个陷阱。这个不使用指向NSMutableString的指针,而是从旧的字符串创建一个新字符串。对不起,我还不能投票给你,你教过我一些东西:)

答案 2 :(得分:0)

如果您希望从NSString获取不可变NSMutableString,可以执行以下操作:

NSString *anImmutableString = [NSString stringWithString: aMutableString];

因此,如果我们在问题中使用您的原始代码,并且我将假设您的viewDidLoad是您想要的不可变字符串:

- (NSArray *) getaString {
    NSMutableString * string = [[NSMutableString alloc] init];
    [string appendString:@"This "];
    [string appendString:@"was mutable."];
    NSArray * array = [[NSArray alloc] initWithObjects:string, nil];
    return array;                          
}

- (void)viewDidLoad {
    [super viewDidLoad];
    NSString *string = [NSString stringWithString:[[self getaString] objectAtIndex:0]];
    //[string appendString:@" And apparently still is"];
    _showText.text = string;
}

现在,如果您仍想在string中追加到viewDidLoad,则可以,但您必须以不同方式执行此操作(与您追加任何其他NSString的方式相同)。它看起来像这样:

NSString *newString = [string stringByAppendingString:@" And apparently still is"];

现在看一下变量的内容。

string = "This was mutable." //as you built it from an NSMutableString in the getaString method
newString = "This was mutable. And apparently still is" //as built from calling "stringByAppendingString" method on your variable string in viewDidLoad

重要的是要注意newString的内容在这里有误导性。 newString不是NSMutableString。这是一个常规NSString。调用stringByAppendingString会返回NSString,这是将您发送的参数附加到您正在调用方法的NSString末尾的结果。 (并且你调用方法的NSString的内容保持不变 - 它是不可变的。)

如果您希望newString成为NSMutableString,则必须执行以下操作:

NSMutableString *newString = [[string stringByAppendingString:" And apparently still is"] mutableCopy];