0

我在玩可变性,我想出了以下代码,其中可以将不可变对象强制转换为可变对象。

- (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 显示“这是可变的。显然仍然是”的整个字符串,没有编译器警告。我已经看到多个 SO 帖子建议简单地将可变对象转换或使用为不可变对象,但正如我刚刚展示的那样,这可能很危险。此外,演员在没有数组的情况下仍然有效,但我确实收到了编译器警告。

我的问题是,我应该考虑使用 java 风格的防御性副本吗?我没有在目标 C 中看到任何关于防御性副本的提及,而我在 Apple 的文档中只能找到一个模糊的提及,即最好采用一些防御性编程实践。我担心安全性和防止粗心编码。

4

3 回答 3

4

您给出的示例具有误导性。

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

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

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

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

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

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到不可变对象的类返回相同的对象,而不是实际的副本。通过这种方式,您可以吃蛋糕也可以吃,因为只有在需要时才会执行复制。

于 2013-11-04T04:02:24.867 回答
0

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

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

现在,如果您仍想追加到stringin 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];
于 2013-11-04T15:43:14.300 回答
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 的指针,而是从旧字符串创建一个新字符串。对不起,我还不能投票给你,你教会了我一些东西:)

于 2013-11-04T03:18:10.733 回答