在 Objective-C 中,给定一个 id,我如何知道它指向的对象类型?

目标-C 新手问题。给出以下(虚构的)代码:

id mysteryObject = [anotherObject mysteriousMethod];

如何在运行时确定 mysteryObject是什么类?

58913 次浏览

您可以使用 isKindOfClassisMemberOfClass

例如:

if ([foo isMemberOfClass:[NSBar class]])

[mysteryObject class]

将得到类对象。但是,通常您希望执行一些 OOPy 操作,比如检查与某些协议或接口的一致性。

在 Objective-C (或 Python 或 Ruby)这样的动态类型语言中,通常无法通过 想要了解对象的类型。考虑对象是否响应您希望发送的消息通常更有效率; 如果响应了,您就不应该关心它实例化了什么类,如果不响应,您就必须处理大小写,而不管实例的类型如何。这就是所谓的“鸭子打字”... 如果它像鸭子一样嘎嘎叫,那它就是鸭子。

您可以像下面这样测试一个对象是否响应特定的消息(在 Objective-C 中称为选择器) :

if([mysteryInstance respondsToSelector:@selector(messageIWishToSend)]) {
[mysteryInstance messageIWishToSend];
} else {
//handle case where instance doesn't respond to the desired message
}

比测试单个选择器更好的方法是定义一个 @protocol来描述您希望为类使用的 API:

// MyProtocol.h
@protocol MyProtocol
- (void)methodInMyProtocol;
@end


//MyClass.h


#import "MyProtocol.h"


@interface MyClass <MyProtocol> {


}
- (void)methodInMyProtocol;
@end

您可以像下面这样测试一个实例是否实现了 MyProtocol协议:

if([mysteryInstance conformsToProtocol:@protocol(MyProtocol)]) {
[mysteryInstance methodInMyProtocol];
} else {
// ...
}

对于那些来自 Java 或 C + + 等静态类型语言的人来说,这种做事方式常常让人感到不舒服。您失去了为您检查类型的编译器。然而,动态类型使得许多事情变得更加容易,包括测试,因为您可以在测试时轻松地用赝品替换实例。因此,动态语言的方法是多测试,少担心类型。你有很好的单元测试覆盖率,不是吗?

如果您确实必须在运行时确定一个实例的类(您可能确实不需要) ,那么您可以使用 -[NSObject isKindOfClass:]来测试一个实例是否是一个类的实例或者它的任何子类或者 -[NSObject isMemberOfClass:]来测试一个实例是否是一个特定类的实例。您可以直接检查 Class对象作为 -[NSObject class]的返回值,并且可以使用 NSStringFromClass([mysteryInstance class])获得实例类的字符串名称。

当与@协议中定义的方法一起使用时,我发现必须强制转换回 id。

例如,self. listener 是一个 id 数组

如果我这么做..。

for(id<PropertyListener> listener in self.listeners) {
if ( [ [ listener class]  respondsToSelector:@selector(propertyChanged:propertyName:)]) {

我得到一个错误“没有选择器‘ class’的已知实例方法”。然而,当我将本我从一个本我转换到另一个本我时,它起作用了... ... 为什么我不明白。

[ ((id)listener) class] respondsToSelector ....

这是完整的循环。

for(id<PropertyListener> listener in self.listeners) {
if ( [ [ ((id)listener) class]  respondsToSelector:@selector(propertyChanged:propertyName:)]) {
[listener propertyChanged: self propertyName:@"thePropName"];
} else {
[listener propertyChanged: self];
}
}