【问题标题】:Is it safe to use isKindOfClass: against an NSString instance to determine type?对 NSString 实例使用 isKindOfClass: 来确定类型是否安全?
【发布时间】:2010-11-08 23:12:25
【问题描述】:

来自isKindOfClass: method documentation in NSObject:

在类簇表示的对象上使用此方法时要小心。由于类簇的性质,您返回的对象可能并不总是您期望的类型。

文档接着给出了一个示例,说明为什么你永远不应该询问类似 NSArray 实例的以下内容:

// DO NOT DO THIS!
if ([myArray isKindOfClass:[NSMutableArray class]])
{
    // Modify the object
}

现在举一个不同用途的例子,假设我有一个 NSObject 实例,我想确定我是否有一个 NSString 或 NSArray。

这两种类型都是类集群 - 但从上面的文档看来,危险在于 isKindOfClass 的答案:过于肯定(有时当你真的没有可变数组时回答是)而询问关于集群中的简单成员资格仍然有效。

一个例子:

NSObject *originalValue;

// originalValue gets set to some instance

if ( [originalValue isKindOfClass:[NSString class]] )
   // Do something with string

这个假设正确吗?使用 isKindOfClass: 针对类集群实例来确定成员资格真的安全吗?我对无所不在的 NSString、NSArray 和 NSDictionary 的答案特别感兴趣,但我很想知道它是否可以推广。

【问题讨论】:

  • 在这 3 年的时间里,您是否对此有所了解?什么样的愚蠢NSMutableArray 子类不会是可变的?
  • 从实际经验来看,这并不重要;正如你所说,我只见过可变的 NSMutableArrays,而且我认为我从未使用过子类(至少是由第三方制作的)。 Objective-C 的很多内容都是关于不明确的约定,我认为您可以确定您可能遇到的任何 NSMutableArray 子类的行为方式对任何其他 Objective-C 程序员都有意义。不过,我还应该补充一点,我没有遇到过必须进行类似检查的情况——任何数据流动的路径都应该始终是可变的或不可变的。
  • 同意,当您阅读 API 时,这是一种边缘问题,但在野外很少出现。对我来说,这属于“不要在 else 条件没有意义的情况下编写 if”或类似的更大类别。

标签: objective-c


【解决方案1】:

文档中的警告使用NSMutableArray 示例。假设某个开发人员使用NSMutableArray 作为他自己实现一种新型数组CoolFunctioningArray 的基类。这个CoolFunctioningArray 在设计上是不可变的。但是,isKindOfClass 会将 YES 返回到 isKindOfClass:[NSMutableArray class],这是正确的,但设计并非如此。

isKindOfClass: 将返回 YES 如果某处的接收者继承自作为参数传递的类。 isMemberOfClass: 将返回 YES 仅当接收者是仅作为参数传递的类的实例时,即不包括子类。

通常isKindOfClass: 用于测试会员资格是不安全的。如果一个实例为isKindOfClass:[NSString class] 返回YES,您只知道它将响应NSString 类中定义的所有方法,但您无法确定这些方法的实现可能会做什么。有人可能将NSString 子类化为长度方法引发异常。

我认为您可以使用isKindOfClass: 进行此类测试,特别是如果您正在使用自己的代码,您(作为一个好撒玛利亚人)设计的代码会以我们都期望的方式响应to(例如,NSString 子类的 length 方法返回它所代表的字符串的长度,而不是引发异常)。如果你在使用大量怪异开发者的外部库(比如CoolFunctioningArray的开发者,应该被枪毙)你应该谨慎使用isKindOfClass方法,最好使用isMemberOfClass:方法(可能多次测试一组类的成员资格)。

【讨论】:

  • +1 用于区分 isKindOfClass 和 isMemberOfClass。我恰好也非常需要这个答案。
  • 有人可以做这个子类化——这打破了类应该工作的方式——但是有人会疯狂。如你所说。在这种情况下,newacct 的答案是真正的“原因”。
  • 帖子和此答案中省略了Apple警告的重要部分-示例:在类簇表示的对象上使用此方法时要小心。 [...] 例如,如果一个方法返回一个指向 NSArray 对象的指针,则不应使用该方法来查看数组是否可变,如下面的代码所示: 这演示了一个故意违反合同而不是设计不良的类,因为返回类型是 NSArray。
【解决方案2】:

您读错了警告。它只是说,仅仅因为它在内部被表示为可变的东西,并不意味着你应该尝试改变它,因为改变它可能会违反你和你从谁那里得到数组的人之间的契约;它可能会违反他们的假设,即您不会对其进行变异。

但是,isKindOfClass: 给出的测试肯定仍然有效。如果[something isKindOfClass:[NSMutableArray class]],那么它是NSMutableArray 或其子类的实例,这几乎意味着它是可变的。

【讨论】:

    【解决方案3】:

    这是 Apple 文档中的一个奇怪警告。这就像在说:“小心这个功能,因为如果你将它与其他损坏的代码一起使用,它将无法工作。”但你可以用任何方式说出来。

    如果设计遵循应有的替换原则,那么 isKindOfClass 将起作用。 (wikipedia: Liskov Substitution Principle)

    如果某些代码违反了原则,通过返回不响应 addObject: 和其他 NSMutableArray 方法的 NSMutableArray 子类的实例,那么该代码就有问题......除非它只是一个小规模的临时 hack ...

    【讨论】:

      【解决方案4】:

      让我们考虑以下代码:

      if ([dict isKindOfClass:[NSMutableDictionary class]])
      {
          [(NSMutableDictionary*)dict setObject:@1 forKey:@"1"];
      }
      

      我认为问题中引用的 Apple 注释的真正目的是该代码很容易导致崩溃。这里的问题在于与 CoreFoundation 的免费桥接。让我们更详细地考虑 4 个变体:

      NSDictionary* dict = [NSDictionary new];
      NSMutableDictionary* dict = [NSMutableDictionary new];
      CFDictionaryRef dict = CFDictionaryCreate(kCFAllocatorDefault, NULL, NULL, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
      CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
      

      确实,在所有 4 个变体中,dict 的真实类将是 __NSCFDictionary。并且所有 4 个变体都将通过第一个代码 sn-p 中的测试。但在 2 种情况下(NSDictionary 和 CFDictionaryRef 声明),我们会因类似这样的日志而崩溃:

      * 由于未捕获的异常“NSInternalInconsistencyException”而终止应用程序,原因:“-[__NSCFDictionary setObject:forKey:]: mutating method sent to immutable object”

      所以,事情变得更清楚了。在 2 种情况下,我们可以修改对象,在 2 种情况下不允许。它取决于创建函数,并且可能由 __NSCFDictionary 对象本身跟踪可变状态。

      但是为什么我们对可变对象和不可变对象使用同一个类呢? 可能的答案 - 因为 C 语言的限制(正如我们所知,CoreFoundation 是一个 C API)。我们在 C 中有什么问题?可变和不可变字典类型的声明应反映以下内容: CFMutableDictionaryRef 类型是 CFDictionaryRef 的子类型。但是 C 对此没有任何机制。但是我们希望能够在期望 CFDictionary 对象的函数中传递 CFMutableDictionary 对象,并且最好不要让编译器发出烦人的警告。我们必须做什么?

      让我们看看以下 CoreFoundation 类型声明:

      typedef const struct __CFDictionary * CFDictionaryRef;
      typedef struct __CFDictionary * CFMutableDictionaryRef;
      

      正如我们在此处看到的,CFDictionary 和 CFMutableDictionary 用相同的类型表示,仅在 const 修饰符上有所不同。所以,这里开始麻烦了。

      这同样适用于 NSArray,但情况稍微复杂一些。当你直接创建 NSArray 或 NSMutableArray 时,你会得到一些特殊的类(分别为 __NSArrayI 和 __NSArrayM)。对于此类崩溃,无法复制。但是当您创建 CFArray 或 CFMutableArray 时,情况保持不变。所以要小心!

      【讨论】:

      • NSDictionary* dict = [NSDictionary new]; 不会通过测试。但是,感谢您解释桥接的东西
      猜你喜欢
      • 2011-07-31
      • 1970-01-01
      • 1970-01-01
      • 2011-08-23
      • 2010-11-22
      • 2012-08-27
      • 1970-01-01
      • 2010-09-18
      • 1970-01-01
      相关资源
      最近更新 更多