在 Objective-C 中实现纯虚方法

Posted

技术标签:

【中文标题】在 Objective-C 中实现纯虚方法【英文标题】:Implement a pure virtual method in Objective-C 【发布时间】:2011-07-10 11:23:51 【问题描述】:

我想去那里。说真的,如何以“Apple”的方式实现纯虚拟方法?您是否在基类中使用协议并在这些方法上引发异常?

【问题讨论】:

对于来自强大的 OO C++ 背景的人来说,这是一个很好的问题。 【参考方案1】:

当你在 Objective-C 中编程时,你需要清除脑海中的虚方法之类的东西。您不会在 Objective-C 对象上调用方法,而是向它们发送消息。对象要么响应消息,要么不响应消息,但由于动态绑定,直到运行时您才能知道这一点。

因此,您可以在基础对象上声明方法而不提供实现,没问题(编译器警告除外),但是当您使用此类方法直接实例化对象时,您不能让编译器标记并且它不会在运行时抛出错误,除非您实际将该消息发送到对象。

创建“虚拟”基类的最佳方式(在我看来)是声明该方法并为其提供一个引发适当异常的存根实现。

【讨论】:

抱歉,假装在 ObjC 中“发送消息”与函数调用有某种神奇的不同并且需要它自己的名字是很愚蠢的。也许在过去似乎是这样,在我们都知道函数绑定不必是静态的之前,但今天我们都知道它们都只是函数,无论绑定机制如何。 @GlennMaynard 不,这并不傻。它是 Objective-C 从 Smalltalk 继承的 OO 模型的基础。选择器和方法是不同的东西。 “选择器”不过是一流的函数签名,而“发送消息”不过是查找和调用具有该签名的函数。都是一样的。 虚拟方法应该是这样的: - (UIImage*)getImage @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:@"You must override %@ in a subclass", NSStringFromSelector(_cmd) ]用户信息:无]; @GlennMaynard 我认为没有人在假装。他们只是在编写大量 ObjC 时才意识到细微的差异。它确实与签名查找和方法调用非常相似,但是如果您将自己限制在这种思维模型中,您就会错过 ObjC 的许多细节。 “发送消息”不仅仅是查找和调用具有该签名的函数。例如,您可以拥有诸如 NSProxy 之类的东西来转换通过它们传递的消息,或者 JSON 解析器伪对象将 getter 消息(不存在的方法)视为对 JSON 哈希键值的访问。【参考方案2】:

在 Objective-C 中,没有像 C++ 中那样的纯虚拟支持。

模拟是您在接口中声明了一个方法,但不在 .m 文件中实现它。当然,您会收到编译器警告,但 IIRC 您可以将其关闭。但是,如果您不在 C++ (IIRC) 中获得的子类中覆盖它们,您将不会收到警告/错误。

另一种方法是仅使用 NSAssert(NO, @"Subclasses need to overwrite this method"); 正文来实现它们。不过,您只能在运行时而不是编译时捕获它。

【讨论】:

【参考方案3】:

根据您正在做的事情,委托模式可能比子类更合适,其中委托被定义为id<YourDelegateProtocol>。如果委托协议中所需的方法未实现,编译器将生成警告。

在 Objective-C 中通常避免子类化,因为对象不能从多个超类继承,但它们可以实现多个协议。

【讨论】:

Objective-C 中的每个对象都是 NSObject 的子类。所以我会说子类化非常重要,而不是“通常避免”。每次我制作自定义视图、表格或控制器时都会发生这种情况。 @Stephen:是的,但通常避免的是子类的深层层次结构。与 Java 和 C++ 等语言相比,它的使用频率要低得多 @JeremyP,这是真的。就个人而言,我认为这是一件好事。 @Stephan Furlani 不是 objc 中的 每个 类都是 NSObject 的子类。 a) 并非所有 objc 系统/库都是从 nextstep 派生或继承的 b) 即使仅与 Apple 的库交互,您仍然可以在野外遇到根类(出于各种原因); NSProxy 就是一个公开的例子。【参考方案4】:

你应该使用:

- (void)doesNotRecognizeSelector:(SEL)aSelector method. 

正如 Apple 所说,这里:https://developer.apple.com/library/mac/#documentation/cocoa/reference/Foundation/Classes/NSObject_Class/Reference/Reference.html

【讨论】:

【参考方案5】:

你有几个选择,但你在正确的轨道上。

ObjC 不直接支持这一点,强制子类实现协议是编译时检查它的最佳方式。

“秘密”在基类中实现该方法并断言是我所做的,以确认子类在运行时已正确子类化。有些人对断言有复杂的感觉,或者必须让它们保持活跃,所以这并不总是一个好的解决方案。

您还可以强制子类使用特定的类构造函数和初始化序列,然后在返回实例之前验证它们是否已实现所需的一切,以防编译器警告无法解决。

但是 ObjC 缺少一些语言特性,这些特性允许客户在脚下开枪,或者解决他们希望的问题......你不应该太拘泥于强制执行它。

注意:异常在 ObjC 中非常少见(也有点不安全)。

【讨论】:

如果子类未能实现必须强制执行的方法,则抛出异常非常好。 @JeremyP 不,他们不是无条件的。实际上,当异常跨越模块边界时,您正在请求 UB。任何抛出异常的人都会假设(可能是错误的)客户端已在所有模块中启用了对 objc 异常的支持。如果客户端未能满足 api 的要求,则返回 0 (并断言和/或记录消息)要安全得多,也更惯用得多。如果您的客户发货了,您将不会与客户交朋友——即使他们未能满足您的对象的要求。 @JeremyP:除了 Justin 提出的观点之外,还有内存问题:如果您在 GC 环境中,这不是问题,但在“传统”引用计数环境中,您将面临内存泄漏的问题。考虑:分配一个对象,调用一个方法“foo”,释放对象。如果“foo”现在抛出异常,则永远不会释放分配的对象。 @DarkDust, @Justin:这将被视为编程错误,因此无法真正恢复,就像范围异常或空指针取消引用一样。理想情况下,程序会在此时终止,因此内存泄漏不是问题。 @JeremyP:如果您想要的只是应用程序终止,那么可以,在这种情况下(在调用未实现的方法时抛出)是可以的。不过,让我们希望没有人能抓住它;-)【参考方案6】:

虚拟方法是一种方法,其行为可以在继承类中被具有相同签名(即具有相同参数数量和参数类型的相同名称)的函数覆盖。

示例:-

@implementation BaseClass

-(void)viewDidLoad

 [self virtualMethod:123];


-(void)virtualMethod:(int)param

 //implement this method in subclass


@end

/////////////////////////////////////// /////

@interface ChildClass:BaseClass
@end

@implementation ChildClass

-(void)virtualMethod:(int)param

 NSLog(@"There is no keyword "Virtual" in Objective C.");

@end

输出:-

“Objective C 中没有关键字“Virtual”。”

【讨论】:

以上是关于在 Objective-C 中实现纯虚方法的主要内容,如果未能解决你的问题,请参考以下文章

在某些派生类中实现虚函数的正确方法是啥?

抽象类

解口的定义与实现

在 Pentaho 中实现纯 SCD 类型 6

快速调用objective-c类方法

C++ 虚函数(virtual) 和纯虚函数(pure virtual) 的区别