【问题标题】:Cocoa: Testing to find if an NSString is immutable or mutable?Cocoa:测试一个 NSString 是不可变的还是可变的?
【发布时间】:2010-01-19 10:08:08
【问题描述】:

这会产生一个不可变的字符串对象:

NSString* myStringA = @"A";  //CORRECTED FROM: NSMutableString* myStringA = @"A";

这会产生一个可变的字符串对象:

NSMutableString* myStringB = [NSMutableString stringWithString:@"B"];

但是这两个对象被报告为同一种对象,“NSCFString”:

NSLog(@"myStringA is type: %@, myStringB is type: %@", 
[myStringA class], [myStringB class]);

那么是什么在内部区分这些对象,我该如何测试,以便在对它做坏事之前轻松确定一个神秘的字符串变量是不可变的还是可变的?

【问题讨论】:

  • Philippe 下面的代码 -- if ([myStringB isKindOfClass:[NSMutableString class]]) -- 工作并解决了实际问题。
  • 我仍然很好奇不可变字符串和可变字符串之间的区别是如何在内部表示的,以及是否可以直接检测到(使用 NSLog 打印的实际“对象类型”)。跨度>
  • 更正:我弄错了,代码 -- if ([myStringB isKindOfClass:[NSMutableString class]]) -- 毕竟不起作用。无论字符串是 NSString 还是 NSMutableString,它都返回 true。正如 Philippe 在下面指出的(在他现在用不同代码编辑的答案中),显然没有记录在案的方法可以在运行时检测一般的可变对象和不可变对象。

标签: objective-c cocoa types immutability mutable


【解决方案1】:

文档包含一个相当长的解释,说明为什么 Apple 不希望您这样做,以及为什么他们在 Receiving Mutable Objects 中明确不支持它。总结是:

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

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

  1. 将您的 NSMutableArray 公开为 NSArray。
  2. 在请求时始终制作非可变副本
  3. 将元素存储为 NSArray 并在每次发生变异时创建一个新数组。

我已经在不同的时间点完成了所有这些工作。 #1 是迄今为止最简单和最快的解决方案。这也很危险,因为数组可能会在调用者背后发生变异。但 Apple 表示他们在某些情况下会这样做(请注意 NSView 中对 -subviews 的警告)。我可以确认,虽然 #2 和 #3 更安全,但它们可能会造成严重的性能问题,这可能是 Apple 选择不在 -subviews 等经常访问的成员上使用它们的原因。

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

解决此问题的方法是使用可变数据结构的某种快速写入时复制不可变版本。这样#2可能会以不错的性能完成。我可以想象 NSArray 集群的更改将允许这样做,但它在今天的 Cocoa 中不存在(并且在正常情况下可能会影响 NSArray 性能,使其无法启动)。即使我们拥有它,也可能有太多代码依赖于当前行为,以至于无法信任可变性自省。

【讨论】:

  • 您可以通过实现和公开变异数组访问器 (developer.apple.com/documentation/Cocoa/Conceptual/ModelObjects/…) 并在其他类中使用它们来解决 #3 的性能问题。这样,对可变数组的直接访问仍然是私有的,但您不会创建和丢弃临时数组。
  • @PH 这是真的,我应该介绍 KVC 解决方案。它带来的问题是,在很多情况下调用者确实需要一个集合(通常是传递给其他需要集合的东西)。我经常通过暴露 elementEnumerator 之类的东西来解决这个问题,调用者可以根据需要将其转换为快照。这种方法的问题是调用者和被调用者都需要大量的额外代码。令人鼓舞的是,即使在 UIView 的新代码中,Apple 也选择仅将 NSMutableArray“子视图”公开为 NSArray,而不提供完整的 KVC。
  • 嗯?我不清楚您在谈论什么问题或您的解决方案是什么。博客文章和/或 pastebin?
【解决方案2】:

没有(记录在案的)方法可以确定字符串在运行时是否可变。

您会认为以下其中一项会起作用,但它们都不起作用:

[[s class] isKindOfClass:[NSMutableString class]]; // always returns false
[s isMemberOfClass:[NSMutableString class]]; // always returns false
[s respondsToSelector:@selector(appendString)]; // always returns true

更多信息在这里,虽然它不能帮助你解决问题:

http://www.cocoabuilder.com/archive/cocoa/111173-mutability.html

【讨论】:

  • 不起作用:NSMutableString* lala = @"lala"; if ([lala isKindOfClass:[NSMutableString class]]) NSLog(@"Mutable class"); - 显示日志消息,但对象是不可变的
  • 当然不行,因为代码不对。 NSMutableString *lala = @"lala" 是非法代码。
  • 对不起各位 -- NSMutableString* myStringA = @"A";是一个错字——现在在原始消息中更正了。
  • 是的,这是非法的,但是 -isKindOfClass 不允许您检查它 - 不可变对象通过了检查。使用 -isMemberOfClass 检查是可行的。
  • 你是对的,但 -isKindOfClass 也不起作用。当针对 NSMutableString 进行测试时,它为 myStringA 和 myStringB 返回 false
【解决方案3】:

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

BOOL isMutable(id object)
{
   id copy = [object copy];
   BOOL copyIsADifferentObject = (copy != object);
   [copy release];
   return copyIsADifferentObject;
}

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-11-20
    • 2011-07-01
    • 2014-08-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多