来自NSObject中的isKindOfClass:方法文档:
在类集群表示的对象上使用此方法时要小心.由于类集群的性质,您获得的对象可能并不总是您期望的类型.
然后,文档继续给出一个示例,说明为什么不应该像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的答案特别感兴趣,但我有兴趣知道它是否可以推广.
文档中的警告使用了该NSMutableArray
示例.假设某些开发人员将其NSMutableArray
用作自己实现新类型数组的基类CoolFunctioningArray
.这种CoolFunctioningArray
设计不可变.然而,isKindOfClass
将返回YES
到isKindOfClass:[NSMutableArray class]
,这是事实,但设计是没有的.
isKindOfClass:
YES
如果接收器某处继承自作为参数传递的类,则返回.isMemberOfClass:
将返回YES
只有接收方是否是作为参数而已,即不包括子传递的类的实例.
通常isKindOfClass:
用于测试会员资格是不安全的.如果一个实例返回YES
的isKindOfClass:[NSString class]
,你只知道它会在定义的所有方法回应NSString
类,但你不会肯定知道这些方法的实施可能会做.有人可能已经将子类NSString
化为长度方法引发异常.
我认为你可以用isKindOfClass:
这种测试,特别是如果你正在使用你自己的代码,你(作为一个好的撒玛利亚人)以这样的方式设计它将以我们所有人期望的方式响应(例如长度方法)一个NSString
子类返回它所代表的字符串的长度,而不是引发异常).如果你正在使用许多奇怪的开发人员的外部库(比如CoolFunctioningArray
应该被开发的开发人员),你应该isKindOfClass
谨慎使用该方法,并且最好使用该isMemberOfClass:
方法(可能多次测试一组的成员资格)类).
让我们考虑以下代码:
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);
实际上,在ALL 4变体中,dict的真正类别将是__NSCFDictionary.所有4种变体都将在第一个代码片段中通过测试.但是在2个案例中(NSDictionary和CFDictionaryRef声明),我们将使用类似的日志崩溃:
*由于未捕获的异常'NSInternalInconsistencyException'终止应用程序,原因:' - [__ NSCFDictionary setObject:forKey:]:发送到immutable对象的mutating方法'
所以,事情变得更加清晰.在2种情况下,我们可以修改对象,而在2种情况下则不允许.它取决于创建函数,并且可能由__NSCFDictionary对象本身跟踪可变性状态.
但是为什么我们为可变和不可变对象使用相同的类?可能的答案 - 由于C语言的限制(而CoreFoundation是一个C API,我们知道).我们在C有什么问题?可变和不可变字典类型的声明应反映以下内容:CFMutableDictionaryRef类型是CFDictionaryRef的子类型.但是C没有这方面的机制.但是我们希望能够在函数中传递CFMutableDictionary对象,期望CFDictionary对象,并且最好没有编译器发出恼人的警告.我们要做什么?
让我们看看以下CoreFoundation类型声明:
typedef const struct __CFDictionary * CFDictionaryRef; typedef struct __CFDictionary * CFMutableDictionaryRef;
正如我们在这里看到的,CFDictionary和CFMutableDictionary由相同的类型表示,并且仅在const修饰符中有所不同.所以,这里开始麻烦.
这同样适用于NSArray,但情况稍微复杂一些.直接创建NSArray或NSMutableArray时,您将获得一些特殊类(分别为__NSArrayI和__NSArrayM).对于这个类崩溃没有转载.但是当你创建CFArray或CFMutableArray时,事情仍然是一样的.所以要小心!
你正在阅读警告错误.它只是因为它在内部被表示为可变的东西,并不意味着你应该试图改变它,因为改变它可能会违反你和你从阵列中得到的任何人之间的合同; 它可能违反了他们不会改变它的假设.
但是,给出的测试isKindOfClass:
肯定仍然有效.如果[something isKindOfClass:[NSMutableArray class]]
,那么它是它的一个实例NSMutableArray
或子类,这几乎意味着它是可变的.
这是Apple文档中的一个奇怪的警告.这就像是说:"小心这个功能,因为如果你将它与其他破坏的代码一起使用,它将无法工作." 但你可以说任何事情.
如果设计遵循替换原则,它应该是,那么isKindOfClass将起作用.(维基百科:Liskov替代原则)
如果某些代码违反了这个原则,通过返回一个不响应addObject:和其他NSMutableArray方法的NSMutableArray子类的实例,那么该代码就有问题...除非它只是一个小规模的临时黑客......