如何将“Class A”转换为其子类“Class B” - Objective-C

27

我正在使用一个定义了并使用了'ClassA'的框架,'ClassA'是NSObject的子类。为了添加一些变量和功能,我自然会创建'ClassB',它是'ClassA'的子类。

现在我的问题是,这个框架中许多方法返回'ClassA'的实例,而我希望将其强制转换为我的子类。

例如,考虑以下方法:

- (ClassA *)doSomethingCool:(int)howCool

现在在我的代码中,我尝试这样做:

ClassB * objB;
objB = (ClassB *)doSomethingCool(10); 

NSLog(@"objB className = %@", [objB className]);

这段代码完全没有问题,没有编译错误或运行时错误等。但是让我感到非常奇怪的是输出:

>> "objB className = ClassA"

强制类型转换明显失败了。目前不确定发生了什么事情...... objB被定义为'ClassB',但它的className是'ClassA',而且无法响应任何'ClassB'方法。

我不确定这怎么可能... 有人知道我在这里做错了什么吗?

我找到了一个类似的帖子,正好与我要问的相反 这里


你能展示一下在 doSomethingCool 中如何创建 ClassB 吗? - stefanB
5个回答

28
在Objective-C中,将对象变量进行强制类型转换通常是个错误(有一些情况下是对的,但不包括这种情况)。需要注意的是,你并没有将一个对象进行类型转换,而是将一个指向对象的指针强制类型转换了。这样你就得到了一个ClassB*类型的指针,但它仍然指向ClassA的同一个实例。指向的东西没有任何改变。
如果你真的想将ClassA的实例转换为ClassB的实例,你需要编写一个ClassB构造方法,从ClassA创建一个ClassB实例。如果你确实需要添加实例变量,这可能是最好的选择。
正如Jason所说,尝试使用分类是一个很好的选择。

请记住,方法返回的 ClassA 实例可能实际上是 ClassB 类型的对象,该对象将响应 ClassB 对象所理解的消息。 - mipadi
我认为这个解决方案对我来说最有意义。我还有一个问题,为什么当我将objB指向ClassA的实例时,我没有收到RTE,而objB明确定义为指向“ClassB”的指针? - nrj
1
你没有收到警告,是因为你进行了强制转换。当你进行升级转换而没有隐式转换时,编译器会发出警告。当你明确地告诉编译器该对象ClassB时,编译器会认为你知道自己在做什么,并保持沉默。另外,如果你要采用这种解决方案,你应该创建一个容器类来专门关联你的新属性/功能与ClassA的实例,而不是ClassA的子类。 - Jason Coco
你总是可以进行isa交换,虽然在大多数情况下并不是非常有用,但当你想创建代理对象时,这会非常有趣。 - Richard J. Ross III
1
@RichardJ.RossIII:没错,但我很少建议使用isa-swapping。只有在满足以下所有先决条件时才是一个好主意:1)您需要更改某些类的实例的行为,但不是全部;2)您的子类不需要额外的状态;3)您无法访问原始类的源代码;4)您有多个现有的直接引用对象,没有单一的权威来源;5)您对父类的实现和角色足够熟悉,以确信您的isa-swap不会破坏任何东西。 - Chuck
显示剩余2条评论

19
您不能简单地将超类转换为其子类。它实际上不会实现您添加的任何变量/属性或方法。一旦您尝试使用子类上定义的方法来向其发送消息,您将获得运行时异常,应用程序将退出。您只能在一个方向上安全地进行强制类型转换:从更具体到更一般。也就是说,您可以将ClassB安全地转换为ClassA,仅使用ClassA的方法和属性,但不能反过来转换。
以这种方式考虑:您有一辆汽车(父类)和一辆四门轿车(子类)。每辆汽车都有一个发动机和两个车门。现在,假设您从某个地方获取了一辆车,并且您真正了解的只有这一点。您告诉操作员,那辆车是一辆FourDoorSedan,但实际上它不是。所以当操作员执行像“打开后排乘客车门”这样的操作时,会发生什么?只有两个车门!!这在这里也是一样的。
如果您只想向ClassA添加一些功能,请查看Objective-C类别,它们可能是您想要的,而且不需要进行强制类型转换。但请仔细阅读文档,因为它们并非没有缺点。

4
如果doSomethingCool:确实返回了一个类型为ClassB的对象,那么你可以进行强制转换(cast)。虽然它的返回类型是ClassA,但它实际上可以返回一个ClassB对象(因为ClassB“is-a” ClassA)。如果你确信doSomethingCool:返回了一个ClassB类型的对象(如果不确定,可以使用[objB isKindOfClass:[ClassB class]]来检查),那么就可以安全地进行强制转换,并向objB发送消息告诉它执行一个ClassB方法。你也可以使用respondsToSelector来确保更加安全。 - mipadi
重新阅读问题后,如果该框架是一个第三方框架,完全不知道ClassB的任何信息(并且未被Nick修改),那么它将不会返回真正是ClassB实例的对象。 - mipadi
确切地说,这是一个第三方框架,所以它肯定不会返回ClassB。而且,我们可以确定它不会返回ClassB,因为[objB class]返回的是ClassA。 - Jason Coco
我以前用过类别,它们会有所帮助。唯一的问题是我想添加一些额外的ivars,据我所知,类别只允许您“混合”方法。我猜现在我很好奇... 此线程的答案是否错误?因为当您说“您只能安全地从更具体的对象向更一般的对象转换”时,它似乎与您在这里的答案相矛盾。 - nrj
这并不与我说的相矛盾,他特别不想转换类型。实际上,他想将该类转换为子类,而答案是:你不应该这样做。向下强制转换完全可行,并且是面向对象设计中的重要组成部分。 - Jason Coco
实际上,这并不正确。您可以通过简单地将对象的isa更改为目标类来在运行时更改任何对象的类。尽管如此,如果使用不当,它可能会导致崩溃,但您可以通过这种方式创建一些非常酷的代理对象(例如,通过交换isa来重新定义NSArray的某个实例所做的事情)。 - Richard J. Ross III

6
如果您只想向现有对象添加方法,那么子类化不是正确的方法。您可以使用一种名为category的语言功能向现有类(及其实例)添加方法。
例如:
//"ClassA+doSomethingCool.h"
@interface ClassA (doSomethingCool)
- (ClassA *)doSomethingCool:(int)howCool;
@end


//"ClassA+doSomethingCool.m"
@implementation ClassA (doSomethingCool)
- (ClassA *)doSomethingCool:(int)howCool
{

}
@end

2

类型转换并不会将一个对象从一种类型转换为另一种类型。除非使用某些工厂模式,否则您不能强制某些库更改对象的类型并创建另一种类型的对象。


0
当您在返回的对象上调用其中一个子类方法时会发生什么?
ClassB * objB;
objB = (ClassB *)doSomethingCool(10);
[objB subClassMethod];

Obj-C在类型方面相当自由,甚至不需要事先知道类型,例如,您可以将所有ClassB引用更改为id。 我知道了解预期类型更安全,但如果您的代码正确,那么这并不重要。


他的代码不正确。他得到了一个ClassA对象,只是装扮成ClassB对象,因为他更喜欢ClassB。这就是为什么你不能随意地向上转型。在你的例子中,他的应用程序将在第3行崩溃。 - Jason Coco
我假设他在某个地方通过了他的ClassB,框架将其传递回给他,这种情况下代码应该没问题。如果不是这种情况,那么这个问题就没有任何意义。你不能神奇地把一个类转换成另一个类。分类也行不通。 - Henry
我认为他只是对类型转换感到困惑,根据他所写的内容,我不认为他传递了任何参数。如果他这样做了,他会得到那个参数,而不是 ClassA 的实例。然而,他想要“添加一些功能”到 ClassA,这正是分类的用途。 - Jason Coco

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接