Objective c Cocoa:测试NSString是不可变的还是可变的?

Objective c Cocoa:测试NSString是不可变的还是可变的?,objective-c,cocoa,types,immutability,mutable,Objective C,Cocoa,Types,Immutability,Mutable,这将生成一个不可变的字符串对象: NSString* myStringA = @"A"; //CORRECTED FROM: NSMutableString* myStringA = @"A"; NSMutableString* myStringB = [NSMutableString stringWithString:@"B"]; 这将生成一个可变字符串对象: NSString* myStringA = @"A"; //CORRECTED FROM: NSMutableString*

这将生成一个不可变的字符串对象:

NSString* myStringA = @"A";  //CORRECTED FROM: NSMutableString* myStringA = @"A";
NSMutableString* myStringB = [NSMutableString stringWithString:@"B"];
这将生成一个可变字符串对象:

NSString* myStringA = @"A";  //CORRECTED FROM: NSMutableString* myStringA = @"A";
NSMutableString* myStringB = [NSMutableString stringWithString:@"B"];
但这两个对象报告为同一类型的对象“NSCFString”:

那么,如何在内部区分这些对象,以及如何进行测试,以便在对神秘字符串变量进行破坏之前轻松确定它是不可变的还是可变的?

没有(文档化的)方法来确定字符串在运行时是否可变

您可能希望以下其中一项能够起作用,但没有一项能够起作用:

[[s class] isKindOfClass:[NSMutableString class]]; // always returns false
[s isMemberOfClass:[NSMutableString class]]; // always returns false
[s respondsToSelector:@selector(appendString)]; // always returns true
此处提供更多信息,尽管这对解决问题没有帮助:


如果要检查调试目的,以下代码应该可以工作。不可变对象上的复制是它本身,而它是可变类型的真正副本,这就是代码所基于的。请注意,由于它正在调用
copy
,因此速度很慢,但应该可以进行调试。如果您想查看调试以外的任何其他原因,请参阅Rob答案(并忘掉它)


免责声明:对于不可变类型,当然不能保证copy等同于retain。您可以确定,如果
isMutable
返回NO,那么它是不可变的,因此该函数可能应该命名为
canBeMutable
。然而,在现实世界中,不可变类型(NSString、NSArray)将实现此优化是一个非常安全的假设。有很多代码,包括基本的东西,比如NSDictionary,它期望从不可变类型快速复制。

文档中包含了一个相当长的解释,解释了为什么苹果不希望您这样做,以及为什么他们明确不支持它。总结如下:

所以,不要对目标做出决定 基于什么内省的可变性 告诉你一个物体。对待 对象是可变的还是不基于 你在API上得到了什么 边界(即,基于 返回类型)。如果你需要 明确地将对象标记为 传递时是可变的还是不可变的 向客户传递该信息作为 与对象一起标记

我发现他们的NSView示例最容易理解,它说明了一个基本的Cocoa问题。您有一个名为“elements”的NSMutableArray,希望将其公开为数组,但不希望调用者弄乱它。您有几个选择:

  • 将NSMutableArray公开为NSArray
  • 在请求时始终制作不可更改的副本
  • 将元素存储为NSArray,并在每次发生变化时创建一个新数组
  • 我在不同的地方都做过这些#1是迄今为止最简单、最快的解决方案。这也很危险,因为数组可能在调用方背后发生变异。但苹果表示,在某些情况下,他们就是这么做的(请注意NSView中的警告)。我可以证实,虽然#2和#3更安全,但它们可能会产生重大性能问题,这可能就是为什么苹果选择不在经常访问的成员(如子视图)上使用它们的原因

    所有这些的结果是,如果你使用#1,那么内省会误导你。您将NSMutableArray转换为NSArray,内省将表明它是可变的(内省无法知道其他情况)。但你不能让它变异。只有编译时类型检查可以告诉您这一点,因此这是您唯一可以信任的


    解决这一问题的方法是某种可变数据结构的写时快速拷贝不可变版本。这样#2就有可能在性能良好的情况下完成。我可以想象对NSArray集群的更改会允许这样做,但它在Cocoa中并不存在(在正常情况下可能会影响NSArray的性能,使其无法启动)。即使我们有了它,也可能有太多的代码依赖于当前的行为,无法信任可变性内省。

    不起作用:NSMutableString*lala=@“lala”;如果([lala iskindof类:[NSMutableString类]])NSLog(@“Mutable类”);-显示日志消息,但是对象是不可变的。当然它不工作,因为代码是错误的。NSMutableString*lala=@“lala”是非法代码。对不起,伙计们——NSMutableString*myStringA=@“A”;是一个输入错误--现在在原始消息中更正。是的,它是非法的,但是-isKindOfClass不允许您检查它-immutable对象通过检查。尽管用-isMemberOfClass检查是有效的。你是对的,但-isKindOfClass也不起作用。当根据NSMutableStringPhilippe下面的代码--if([myStringB isKindOfClass:[NSMutableStringClass]])测试myStringA和myStringB时,它返回false,并解决了实际问题。我仍然好奇不可变字符串和可变字符串之间的差异是如何在内部表示的,如果可以直接检测到(用NSLog打印的实际“对象类型”)。更正:我错了,代码-if([myStringB isKindOfClass:[NSMutableString class]])根本不起作用。无论字符串是NSString还是NSMutableString,它都返回true。正如Philippe在下面指出的(在他现在用不同的代码编辑的答案中),显然没有记录在案的方法在运行时检测可变对象和不可变对象。您可以通过实现和公开可变数组访问器()并在其他类中使用它们来解决#3的性能问题。这样,对可变数组的直接访问仍然是私有的,但您并不是在创建和丢弃临时数组。@PH这是真的,我应该介绍一下KVC解决方案。它带来的问题是,在许多情况下,调用方确实需要一个集合(通常是传递给其他需要集合的对象)。我经常通过公开elementEnumerator之类的东西来解决这个问题,如果调用方愿意,可以将其转换为快照。这种方法的问题在于调用方和被调用方都需要大量额外的代码。苹果甚至在UIView的新代码中选择了