我正在玩可变性,我想出了下面的代码,其中一个不可变对象可以被转换为可变对象。
- (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。我既关心安全性又关注防范粗心编码。
答案 0 :(得分:4)
你给出的例子具有误导性。
您正在将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
发送到不可变对象会返回相同的对象,而不是实际的副本。通过这种方式,你可以吃蛋糕并吃掉它,因为副本只在需要时才会进行。
答案 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];