在Objective-C中创建抽象类


507

我最初是一名Java程序员,现在使用Objective-C。我想创建一个抽象类,但是在Objective-C中似乎不可能。这可能吗?

如果没有,我在Objective-C中能接近一个抽象类吗?


18
下面的答案很好。我发现抽象类的问题与私有方法有切线关系-两者都是用于限制客户端代码可以执行的方法,而Objective-C中都不存在。我认为这有助于理解该语言本身的思维方式与Java根本不同。看到我的回答: stackoverflow.com/questions/1020070/#1020330
Quinn Taylor 2009年

感谢您提供与其他语言相对的Objective-C社区心态的信息。确实可以解决我遇到的许多相关问题(例如,为什么没有针对私有方法的直接机制,等等)。
乔纳森·阿博加斯特

1
因此,请看一下CocoaDev网站,该网站为其提供了Java比较功能cocoadev.com/index.pl?AbstractSuperClass

2
尽管Barry提到它是事后的想法(如果我读错了,请原谅我),但我认为您正在目标C中寻找协议。例如,请参阅什么是协议?
jww 2014年

Answers:


633

通常,Objective-C类仅按惯例是抽象的-如果作者将一个类记录为抽象的类,请不要在不对其进行子类化的情况下使用它。但是,没有编译时强制执行阻止抽象类的实例化。实际上,没有什么可以阻止用户通过类别(即在运行时)提供抽象方法的实现。您可以通过在抽象类的那些方法实现中引发异常来强制用户至少重写某些方法:

[NSException raise:NSInternalInconsistencyException 
            format:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)];

如果您的方法返回一个值,则使用起来会更容易一些

@throw [NSException exceptionWithName:NSInternalInconsistencyException
                               reason:[NSString stringWithFormat:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)]
                             userInfo:nil];

这样一来,您无需从方法中添加return语句。

如果抽象类确实是一个接口(即没有具体的方法实现),则使用Objective-C协议是更合适的选择。


18
我认为答案中最合适的部分是提到您可以使用@protocol来仅定义方法。
乍得·斯图尔特

13
需要说明的是:您可以在定义中声明方法@protocol,但不能在其中定义方法。
理查德

使用NSException上的类别方法,+ (instancetype)exceptionForCallingAbstractMethod:(SEL)selector可以很好地工作。
Patrick Pijnappel 2014年

这对我来说是可行的,因为恕我直言,对其他开发人员来说,抛出异常比更为明显,这是期望的行为doesNotRecognizeSelector
克里斯(Chris)

使用协议(对于完全抽象的类)或模板方法模式,其中抽象的类具有部分实现/流程逻辑,如此处stackoverflow.com/questions/8146439/…所示。请参阅下面的答案。
hadaytullah 2015年

267

不,没有办法在Objective-C中创建抽象类。

您可以模拟抽象类-通过使方法/选择器调用didNotRecognizeSelector:,并因此引发异常,使该类不可用。

例如:

- (id)someMethod:(SomeObject*)blah
{
     [self doesNotRecognizeSelector:_cmd];
     return nil;
}

您也可以针对init执行此操作。


5
@Chuck,我没有投票,但是NSObject参考文献建议在您不想继承方法而不是强制重写方法的情况下使用此方法。尽管这些可能是同一回事,但也许是:)
Dan Rosenstark 2011年

您为什么不想在这种情况下仅使用协议?对我来说,很高兴只知道方法存根,而不是整个抽象类。用例似乎很有限。
lewiguez 2014年

我没有反对,但是建议您应该在init方法中引发异常是最可能的原因。子类的最常见格式将通过调用self = [super init]来启动其自己的init方法-这会乖乖地抛出异常。这种格式适用于大多数方法,但是我绝不会在子类称之为超级实现的任何地方使用它。
Xono 2014年

5
您绝对可以在Objective-C中创建抽象类,这很常见。有许多Apple框架类可以做到这一点。苹果的抽象类通常通过调用NSInvalidAbstractInvocation()引发特定的异常(NSInvalidArgumentException)。调用抽象方法是编程错误,这就是为什么它引发异常的原因。抽象工厂通常被实现为类集群。
2014年

2
@quellish:如您所说:调用抽象方法是编程错误。应该这样处理,而不是依赖运行时错误报告(NSException)。我认为对于来自其他语言的开发人员来说,这是最大的问题,在Obj-C中,抽象意味着“无法实例化此类型的对象”,这意味着“实例化此类时,运行时会出错”。
Cross_

60

只需参考上面的@Barry Wark的答案(并针对iOS 4.3进行更新),然后将其留给我自己参考即可:

#define mustOverride() @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%s must be overridden in a subclass/category", __PRETTY_FUNCTION__] userInfo:nil]
#define methodNotImplemented() mustOverride()

然后您可以使用此方法

- (void) someMethod {
     mustOverride(); // or methodNotImplemented(), same thing
}



注意:不确定使宏看起来像C函数是否是一个好主意,但我会一直保留它,直到相反为止。我认为使用NSInvalidArgumentException(而不是NSInternalInconsistencyException)更正确,因为这是运行时系统响应doesNotRecognizeSelector调用而抛出的内容(请参阅NSObject文档)。


1
肯定的是,@ TomA,我希望它能为您提供其他可以宏化的代码的想法。我最常用的宏是对单例的简单引用:代码说,universe.thing但扩展为[Universe universe].thing
极大的

大。不过进行了一些更改:#define mustOverride() @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%s must be overridden in a subclass/category", __PRETTY_FUNCTION__] userInfo:nil]
noamtm 2012年

1
@Yar:我不这么认为。我们__PRETTY_FUNCTION__通过DLog(...)宏在各处使用,如此处建议的:stackoverflow.com/a/969291/38557
noamtm 2012年

1
更多细节-#define setMustOverride() NSLog(@"%@ - method not implemented", NSStringFromClass([self class])); mustOverride()
gbk的

1
如果您添加基类,然后继承该类,例如10次,并且忘记在一个类中实现该类,则您将收到消息,该消息的基类名称不被继承,例如,Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[BaseDynamicUIViewController localizeUI] must be overridden in a subclass/category'如果我提议您也可以HomeViewController - method not implemented从Home继承HomeViewController的地方-这将提供更多信息
gbk

42

我想出的解决方案是:

  1. 为您的“抽象”类中的所有内容创建协议
  2. 创建实现该协议的基类(或称其为抽象类)。对于您要“抽象”在.m文件而不是.h文件中实现的所有方法。
  3. 让您的子类从基类继承并实现协议。

这样,编译器将针对您的子类未实现的协议中的任何方法向您发出警告。

它不像Java中那样简洁,但是您确实得到了所需的编译器警告。


2
+1这实际上是最接近Java中抽象类的解决方案。我自己使用了这种方法,效果很好。甚至可以将协议命名为与基类​​相同的名称(就像Apple一样NSObject)。然后,如果将协议和基类声明放在同一个头文件中,则它几乎与抽象类没有区别。
codingFriend1

嗯,但是我的抽象类实现了协议的一部分,其余部分由子类实现。
Roger CS Wernersson

1
您可以只让子类实现协议,而将超类方法放在一起而不是空白。然后具有Superclass <MyProtocol>类型的属性。同样,为了增加灵活性,您可以在协议中使用@optional前缀方法。
dotToString

这在Xcode 5.0.2中似乎不起作用。它只会为“抽象”类生成警告。不扩展“抽象”类会生成正确的编译器警告,但显然不允许您继承方法。
Topher Fangio 2014年

我更喜欢这种解决方案,但确实不喜欢它,它在项目中确实不是一个好的代码结构。 //应该将此作为子类的基本类型。typedef BaseClass <BaseClassProtocol> BASECLASS; 这只是一个星期的规则,我不喜欢它。
Itachi

35

Omni Group邮件列表

目前,Objective-C没有像Java这样的抽象编译器构造。

因此,您要做的就是将抽象类定义为任何其他普通类,并为空或报告不支持选择器的抽象方法实现方法存根。例如...

- (id)someMethod:(SomeObject*)blah
{
     [self doesNotRecognizeSelector:_cmd];
     return nil;
}

我还执行以下操作以防止通过默认的初始化程序初始化抽象类。

- (id)init
{
     [self doesNotRecognizeSelector:_cmd];
     [self release];
     return nil;
}

1
我没想到要使用-doesNotRecognizeSelector:而且我在某种程度上喜欢这种方法。有谁知道使编译器对以这种方式或通过引发异常创建的“抽象”方法发出警告的方法?那太棒了……
Quinn Taylor

26
dosNotRecognizeSelector方法可防止Apple建议的self = [super init]模式。
dlinsin

1
@david:我很确定,重点是尽快提出异常。理想情况下,它应该在编译时,但是由于无法执行此操作,因此他们选择了运行时异常。这类似于断言失败,首先不应在生产代码中引发断言。实际上,一个assert(false)实际上可能会更好,因为更清楚的是,该代码永远不应运行。用户无法采取任何措施对其进行修复,而开发人员必须对其进行修复。因此,在这里引发异常或断言失败听起来是个好主意。
有意义的2010年

2
我认为这不是一个可行的解决方案,因为子类可能对[super init]具有完全合法的调用。
拉菲·哈查杜安

@dlinsin:当不应该通过init初始化抽象类时,这正是您想要的。它的子类大概知道要调用的超级方法,但这可以停止通过“ new”或“ alloc / init”的粗心调用。
gnasher729

21

与其尝试创建抽象基类,不如考虑使用协议(类似于Java接口)。这使您可以定义一组方法,然后接受所有符合协议的对象并实现这些方法。例如,我可以定义一个操作协议,然后具有如下功能:

- (void)performOperation:(id<Operation>)op
{
   // do something with operation
}

其中op可以是实现Operation协议的任何对象。

如果您需要抽象基类做的事情不仅仅是简单地定义方法,则可以创建常规的Objective-C类并防止实例化它。只需覆盖-(id)init函数并使它返回nil或assert(false)。这不是一个很干净的解决方案,但是由于Objective-C是完全动态的,所以实际上没有直接等效于抽象基类的方法。


对我来说,这似乎是使用抽象类的合适方法,至少在它真正表示“接口”时(如在C ++中)。这种方法有任何隐藏的缺点吗​​?
febeling 2011年

1
@febeling,至少在Java中,抽象类不仅仅是接口。它们还定义了一些(或大多数)行为。不过,这种方法在某些情况下可能会很好。
丹·罗森斯塔克2011年

我需要一个基类来实现我的子类都共享的某些功能(删除重复项),但是我还需要一个协议,用于该基类不应该处理的其他方法(抽象部分)。因此,我需要同时使用,这就是要确保子类正确实现自身的棘手问题。
LightningStryk

19

这个线程有点陈旧,我想分享的大部分内容已经在这里。

但是,没有提到我最喜欢的方法,并且AFAIK当前的Clang中没有本机支持,所以我在这里…

首先,也是最重要的(正如其他人已经指出的那样),抽象类在Objective-C中很少见-我们通常使用组合(有时通过委派)代替。这可能是为什么语言/编译器中不存在这样的功能的原因-除了@dynamic属性,IIRC在CoreData引入时已在ObjC 2.0中添加了IIRC。

但是,考虑到这一点(在仔细评估您的情况之后!),您得出的结论是,委派(或总体而言,委派)不适合解决您的问题,因此,我将这样做:

  1. 在基类中实现每个抽象方法。
  2. 实施[self doesNotRecognizeSelector:_cmd];...
  3. …其次是 __builtin_unreachable();使您对非空方法所得到的警告保持沉默,告诉您“控制已到达非空函数的末尾而没有返回”。
  4. 可以将第2步和第3步合并到一个宏中,也-[NSObject doesNotRecognizeSelector:]可以__attribute__((__noreturn__))没有实现的类别中使用注释,以免替换该方法的原始实现,并将该类别的标头包括在项目的PCH中。

我个人更喜欢宏版本,因为它允许我尽可能减少样板。

这里是:

// Definition:
#define D12_ABSTRACT_METHOD {\
 [self doesNotRecognizeSelector:_cmd]; \
 __builtin_unreachable(); \
}

// Usage (assuming we were Apple, implementing the abstract base class NSString):
@implementation NSString

#pragma mark - Abstract Primitives
- (unichar)characterAtIndex:(NSUInteger)index D12_ABSTRACT_METHOD
- (NSUInteger)length D12_ABSTRACT_METHOD
- (void)getCharacters:(unichar *)buffer range:(NSRange)aRange D12_ABSTRACT_METHOD

#pragma mark - Concrete Methods
- (NSString *)substringWithRange:(NSRange)aRange
{
    if (aRange.location + aRange.length >= [self length])
        [NSException raise:NSInvalidArgumentException format:@"Range %@ exceeds the length of %@ (%lu)", NSStringFromRange(aRange), [super description], (unsigned long)[self length]];

    unichar *buffer = (unichar *)malloc(aRange.length * sizeof(unichar));
    [self getCharacters:buffer range:aRange];

    return [[[NSString alloc] initWithCharactersNoCopy:buffer length:aRange.length freeWhenDone:YES] autorelease];
}
// and so forth…

@end

如您所见,该宏提供了抽象方法的完整实现,从而将必要的样板数量减少到绝对最小。

更好的选择是游说 Clang团队通过功能请求为此情况提供编译器属性。(更好的是,因为这还将为您子类化例如NSIncrementalStore的那些方案启用编译时诊断。)

为什么选择这种方法

  1. 它可以高效,方便地完成工作。
  2. 这很容易理解。(好的,这__builtin_unreachable()可能会让人们感到惊讶,但也很容易理解。)
  3. 在不生成其他编译器警告或错误的情况下,不能在发行版本中删除它-与基于一个断言宏之一的方法不同。

我想最后一点需要一些解释:

一些(大多数?)人在发行版本中剥离断言。(我不同意这种习惯,但这是另一个故事……)但是,未能实施所需的方法是不好的可怕的错误的,而且基本上是宇宙的终结程序。在这方面,您的程序无法正常运行,因为它是未定义的,并且未定义的行为是最糟糕的事情。因此,能够剥离那些诊断而不产生新的诊断将是完全不能接受的。

您无法获得针对此类程序员错误的正确编译时诊断,并不得不在运行时发现这些错误,这非常糟糕,但是如果您可以在发行版本中使用它,那么为什么尝试在第一名?


这是我最喜欢的新解决方案__builtin_unreachable();-gem使它完美地工作。该宏使其具有自记录性,并且行为与如果您在对象上调用缺少的方法所发生的行为相匹配。
亚历克斯MDC

12

使用@property@dynamic也可以。如果声明动态属性并且不提供匹配的方法实现,则所有内容仍将在没有警告的情况下编译,并且unrecognized selector如果尝试访问它,则会在运行时出现错误。这基本上与call相同[self doesNotRecognizeSelector:_cmd],但是键入少得多。


7

在Xcode中(使用clang等),我喜欢使用__attribute__((unavailable(...)))标记抽象类,因此,如果尝试使用它,则会收到错误/警告。

它提供了一些防止意外使用该方法的保护。

在基类@interface标签中,“抽象”方法:

- (void)myAbstractMethod:(id)param1 __attribute__((unavailable("You should always override this")));

更进一步,我创建了一个宏:

#define UnavailableMacro(msg) __attribute__((unavailable(msg)))

这使您可以执行此操作:

- (void)myAbstractMethod:(id)param1 UnavailableMacro(@"You should always override this");

就像我说的那样,这不是真正的编译器保护,但与使用不支持抽象方法的语言一样好。


1
我听不懂 我申请我的基类,你的建议-init方法和现在的Xcode不允许我创建继承的类的实例,并发生编译时错误不可用......。您能再解释一下吗?
anonim

确保-init子类中有一个方法。
理查德·斯特林

2
它与NS_UNAVAILABLE相同,它将在每次您尝试调用带有该属性标记的方法时触发错误。我不知道如何在抽象类上使用它。
Ben Sinclair 2014年

7

问题的答案分散在已经给出答案的评论中。因此,我只是在这里进行总结和简化。

选项1:协议

如果要创建没有实现的抽象类,请使用“协议”。继承协议的类必须实现协议中的方法。

@protocol ProtocolName
// list of methods and properties
@end

选项2:模板方法模式

如果要使用部分实现(例如“模板方法模式”)创建抽象类,那么这就是解决方案。 Objective-C-模板方法模式?


6

另一种选择

只需检查Abstract类中的类以及Assert或Exception,即可随心所欲。

@implementation Orange
- (instancetype)init
{
    self = [super init];
    NSAssert([self class] != [Orange class], @"This is an abstract class");
    if (self) {
    }
    return self;
}
@end

这消除了覆盖的必要 init


仅返回nil效率高吗?还是会引发异常/其他错误?
user2277872 2014年

好吧,这只是为了吸引程序员直接使用该类,我认为这充分利用了断言。
bigkm 2014年

5

(更多相关建议)

我想让程序员知道“不要从孩子那里打电话”并完全覆盖(在我的情况下,当未扩展时,它仍代表父母提供一些默认功能):

typedef void override_void;
typedef id override_id;

@implementation myBaseClass

// some limited default behavior (undesired by subclasses)
- (override_void) doSomething;
- (override_id) makeSomeObject;

// some internally required default behavior
- (void) doesSomethingImportant;

@end

这样做的好处是,程序员将在声明中看到“替代”,并且知道他们不应该调用[super ..]

诚然,为此必须定义单独的返回类型是很丑陋的,但是它可以作为足够好的视觉提示,并且您不能轻易在子类定义中使用“ override_”部分。

当然,当扩展是可选的时,类仍然可以具有默认实现。但是就像其他答案所说的那样,在适当的时候实现一个运行时异常,就像抽象(虚拟)类一样。

最好有这样的内置编译器提示,甚至可以提示何时最好先/后调用super的工具,而不必深入研究注释/文档或...假设。

提示示例


4

如果您习惯了编译器捕获其他语言中的抽象实例冲突,那么Objective-C的行为将令人失望。

作为一种较晚的绑定语言,很明显,Objective-C无法对类是否真正是抽象的做出静态决定(您可能会在运行时添加函数...),但是对于典型的用例来说,这似乎是一个缺点。我希望编译器完全阻止抽象类的实例化,而不是在运行时抛出错误。

这是我们用来通过两种技术隐藏初始化程序的静态检查类型的模式:

//
//  Base.h
#define UNAVAILABLE __attribute__((unavailable("Default initializer not available.")));

@protocol MyProtocol <NSObject>
-(void) dependentFunction;
@end

@interface Base : NSObject {
    @protected
    __weak id<MyProtocol> _protocolHelper; // Weak to prevent retain cycles!
}

- (instancetype) init UNAVAILABLE; // Prevent the user from calling this
- (void) doStuffUsingDependentFunction;
@end

//
//  Base.m
#import "Base.h"

// We know that Base has a hidden initializer method.
// Declare it here for readability.
@interface Base (Private)
- (instancetype)initFromDerived;
@end

@implementation Base
- (instancetype)initFromDerived {
    // It is unlikely that this becomes incorrect, but assert
    // just in case.
    NSAssert(![self isMemberOfClass:[Base class]],
             @"To be called only from derived classes!");
    self = [super init];
    return self;
}

- (void) doStuffUsingDependentFunction {
    [_protocolHelper dependentFunction]; // Use it
}
@end

//
//  Derived.h
#import "Base.h"

@interface Derived : Base
-(instancetype) initDerived; // We cannot use init here :(
@end

//
//  Derived.m
#import "Derived.h"

// We know that Base has a hidden initializer method.
// Declare it here.
@interface Base (Private)
- (instancetype) initFromDerived;
@end

// Privately inherit protocol
@interface Derived () <MyProtocol>
@end

@implementation Derived
-(instancetype) initDerived {
    self= [super initFromDerived];
    if (self) {
        self->_protocolHelper= self;
    }
    return self;
}

// Implement the missing function
-(void)dependentFunction {
}
@end

3

这种情况可能只发生在开发时,所以这可能起作用:

- (id)myMethodWithVar:(id)var {
   NSAssert(NO, @"You most override myMethodWithVar:");
   return nil;
}

3

您可以使用@Yar提出的方法(进行一些修改):

#define mustOverride() @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%s must be overridden in a subclass/category", __PRETTY_FUNCTION__] userInfo:nil]
#define setMustOverride() NSLog(@"%@ - method not implemented", NSStringFromClass([self class])); mustOverride()

在这里,您将收到类似以下的消息:

<Date> ProjectName[7921:1967092] <Class where method not implemented> - method not implemented
<Date> ProjectName[7921:1967092] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[<Base class (if inherited or same if not> <Method name>] must be overridden in a subclass/category'

或断言:

NSAssert(![self respondsToSelector:@selector(<MethodName>)], @"Not implemented");

在这种情况下,您将获得:

<Date> ProjectName[7926:1967491] *** Assertion failure in -[<Class Name> <Method name>], /Users/kirill/Documents/Projects/root/<ProjectName> Services/Classes/ViewControllers/YourClass:53

您也可以使用协议和其他解决方案-但这是最简单的协议之一。



1

我通常只是在要抽象的类中禁用init方法:

- (instancetype)__unavailable init; // This is an abstract class.

每当您在该类上调用init时,这将在编译时生成错误。然后,我将类方法用于其他所有方面。

Objective-C没有内置的方法来声明抽象类。


但是,当我从该抽象类的派生类调用[super init]时,出现错误。怎么解决呢?
Sahil Doshi

@SahilDoshi我想这就是使用这种方法的缺点。当我不想允许实例化一个类并且该类没有任何继承时,我会使用它。
mahoukov '16

是的,我明白这一点。但是您的解决方案将有助于创建类似singleton类的内容,而我们不希望任何人调用init。据我所知,在抽象类的情况下,某个类将继承它。否则,抽象类的用途是什么。
Sahil Doshi

0

通过应用@dotToString的注释稍微更改@redfood的建议,实际上您具有Instagram的IGListKit采用的解决方案。

  1. 为没有必要在基类(抽象类)中定义的所有方法创建协议,即它们需要在子代中进行特定的实现。
  2. 创建基础(抽象)类,实现此协议。您可以在类中添加有意义的通用实现方法。
  3. 在项目中的任何地方,如果AbstractClass必须通过某种方法将子项输入或输出,请AbstractClass<Protocol>改为输入。

因为AbstractClass没有实现Protocol,所以拥有AbstractClass<Protocol>实例的唯一方法是通过子类化。由于AbstractClass无法在项目的任何地方单独使用,因此它变得抽象。

当然,这不会阻止不建议使用的开发人员添加仅引用的新方法AbstractClass,最终将允许(不再有)抽象类的实例。

实际示例:IGListKit有一个IGListSectionController不实现协议的基类IGListSectionType,但是,每个需要该类实例的方法实际上都要求该类型IGListSectionController<IGListSectionType>。因此,无法将类型的对象IGListSectionController用于其框架中有用的任何东西。


0

实际上,Objective-C没有抽象类,但是您可以使用Protocols达到相同的效果。这是示例:

CustomProtocol.h

#import <Foundation/Foundation.h>

@protocol CustomProtocol <NSObject>
@required
- (void)methodA;
@optional
- (void)methodB;
@end

测试协议

#import <Foundation/Foundation.h>
#import "CustomProtocol.h"

@interface TestProtocol : NSObject <CustomProtocol>

@end

测试协议

#import "TestProtocol.h"

@implementation TestProtocol

- (void)methodA
{
  NSLog(@"methodA...");
}

- (void)methodB
{
  NSLog(@"methodB...");
}
@end

0

创建抽象类的简单示例

// Declare a protocol
@protocol AbcProtocol <NSObject>

-(void)fnOne;
-(void)fnTwo;

@optional

-(void)fnThree;

@end

// Abstract class
@interface AbstractAbc : NSObject<AbcProtocol>

@end

@implementation AbstractAbc

-(id)init{
    self = [super init];
    if (self) {
    }
    return self;
}

-(void)fnOne{
// Code
}

-(void)fnTwo{
// Code
}

@end

// Implementation class
@interface ImpAbc : AbstractAbc

@end

@implementation ImpAbc

-(id)init{
    self = [super init];
    if (self) {
    }
    return self;
}

// You may override it    
-(void)fnOne{
// Code
}
// You may override it
-(void)fnTwo{
// Code
}

-(void)fnThree{
// Code
}

@end

-3

您不能只创建一个代表吗?

委托就像一个抽象基类,从某种意义上说,您需要定义哪些功能,但实际上并没有定义它们。

然后,无论何时实现委托(即抽象类),编译器都会警告您需要为其定义行为的可选函数和强制函数。

在我看来,这听起来像是一个抽象基类。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.