在Objective-C中创建抽象类

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

如果不是,在Objective-C中,我能有多接近抽象类?

166955 次浏览

Omni群组邮件列表:

Objective-C没有像Java那样的抽象编译器结构 这一次。< / p > 所以你所做的就是将抽象类定义为任何其他正常的类 并为抽象方法实现方法存根 空或报告不支持选择器。例如…< / p >
- (id)someMethod:(SomeObject*)blah
{
[self doesNotRecognizeSelector:_cmd];
return nil;
}
我还执行以下操作来防止抽象的初始化

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

不,在Objective-C中无法创建抽象类。

你可以模拟一个抽象类——通过让methods/ selectors调用doesNotRecognizeSelector:从而引发一个异常,使该类不可用。

例如:

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

你也可以为init这样做。

通常,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协议是更合适的选择。

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

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

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

如果您需要抽象基类做的不仅仅是定义方法,那么您可以创建一个常规的Objective-C类并防止它被实例化。只需重写- (id)init函数,并使其返回nil或assert(false)。这不是一个非常干净的解决方案,但由于Objective-C是完全动态的,所以实际上没有与抽象基类直接等价的东西。

使用@property@dynamic也可以。如果你声明了一个动态属性并且没有给出一个匹配的方法实现,所有的东西仍然会在没有警告的情况下编译,如果你试图访问它,你会在运行时得到一个unrecognized selector错误。这本质上与调用[self doesNotRecognizeSelector:_cmd]是一样的,但是输入要少得多。

只是重复了上面@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
}
< p > < BR > < BR > 不确定是否使宏看起来像一个C函数是一个好主意,但我会保持它,直到学校相反。我认为使用NSInvalidArgumentException(而不是NSInternalInconsistencyException)更正确,因为这是运行时系统在调用doesNotRecognizeSelector时抛出的响应(参见NSObject文档)。

(更多相关的建议)

我想有一种方法让程序员知道“不要从子调用”,并完全覆盖(在我的情况下,仍然提供了一些默认功能,代表父时,未扩展):

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

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

当然,必须为此定义单独的返回类型是很难看的,但它可以作为一个足够好的视觉提示,并且您可以很容易地在子类定义中不使用“overrides”部分。

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

如果有像这样的内置编译器提示就好了,甚至提示什么时候最好预/后调用super的实现,而不是不得不挖掘注释/文档或…假设。

提示的例子

我想出的解决办法是:

  1. 为“抽象”类中需要的所有内容创建一个协议
  2. 创建一个实现协议的基类(也可以称之为抽象类)。对于所有你想要“抽象”的方法,在.m文件中实现它们,而不是在.h文件中实现。
  3. 让你的子类继承基类并实现协议。

这样,编译器就会对协议中没有由你的子类实现的任何方法发出警告。

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

在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");

就像我说的,这不是真正的编译器保护,但它和你在不支持抽象方法的语言中得到的一样好。

也许这种情况只会发生在开发阶段,所以这是可行的:

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

这个帖子有点老了,我想分享的大部分内容已经在这里了。

然而,我最喜欢的方法没有被提及,而且AFAIK在当前的Clang中没有原生支持,所以我在这里…

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

但是考虑到(在仔细评估你的情况之后!)你已经得出结论,委托(或一般的组合)不适合解决你的问题,下面是是如何做到的:

  1. 实现基类中的每个抽象方法。
  2. 使该实现[self doesNotRecognizeSelector:_cmd];
  3. …后面跟着__builtin_unreachable();来关闭你将得到的非void方法的警告,告诉你“控件到达非void函数的末尾而没有返回”。
  4. 或者结合步骤2。和3。在宏中,或在类别没有实现中使用__attribute__((__noreturn__))注释-[NSObject doesNotRecognizeSelector:],以便不替换该方法的原始实现,并在项目的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

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

更好的选择是游说 铿锵声团队,通过特性请求为这种情况提供一个编译器属性。(更好,因为这也将为那些你子类化NSIncrementalStore的场景启用编译时诊断。)

为什么选择这种方法

  1. 它能有效地完成工作,而且有点方便。
  2. 这很容易理解。(好吧,__builtin_unreachable()可能会让人惊讶,但它也很容易理解。)
  3. 在发布版本中,如果不生成其他编译器警告或错误,就无法剥离它——这与基于断言宏之一的方法不同。

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

一些(大多数?)人在发布版本中剥离断言。(我不同意这个习惯,但那是另一个故事…)未能实现所需的方法-然而-是可怕的错误的,和基本上是宇宙的终结为您的程序。在这方面,您的程序不能正确工作,因为它是未定义的,而未定义的行为是最糟糕的事情。因此,在不生成新的诊断的情况下剥离这些诊断是完全不可接受的。

对于这样的程序员错误,您无法获得适当的编译时诊断,并且不得不求助于运行时发现,这已经够糟糕的了,但是如果您可以在发布版本中掩盖它,为什么要首先尝试使用抽象类呢?

另一种替代方法

只要在抽象类和断言或异常中检查类,无论你喜欢什么。

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

这消除了重写init的必要性

问题的答案散落在已经给出答案的评论中。所以,我只是在这里总结和简化。

Option1:协议

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

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

选项2:模板方法模式

如果你想创建一个具有部分实现的抽象类,如“Template Method Pattern”,那么这就是解决方案。 Objective-C -模板方法模式? < / p >

事实上,Objective-C并没有抽象类,但是你可以使用协议来达到同样的效果。下面是例子:

CustomProtocol.h

#import <Foundation/Foundation.h>


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

TestProtocol.h

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


@interface TestProtocol : NSObject <CustomProtocol>


@end

TestProtocol.m

#import "TestProtocol.h"


@implementation TestProtocol


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


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

你不能创建一个委托吗?

委托就像一个抽象基类,你说需要定义什么函数,但实际上你不定义它们。

然后,每当你实现委托(即抽象类)时,编译器就会警告你需要为哪些可选函数和强制函数定义行为。

对我来说,这听起来像一个抽象基类。

可可不提供任何抽象的东西。我们可以创建一个只在运行时检查的类抽象,而在编译时不检查。

如果你习惯了编译器在其他语言中捕捉抽象实例化的冲突,那么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

你可以使用@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

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

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

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

这将在编译时在该类上调用init时生成一个错误。然后我用类方法来处理其他事情。

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

通过应用@dotToString的注释,稍微改变一下@redfood的建议,你实际上得到了Instagram的IGListKit所采用的解决方案。

  1. 为所有在基(抽象)类中定义没有意义的方法创建一个协议,即它们需要在子类中特定的实现。
  2. 创建一个基(抽象)类,实现此协议。您可以向该类添加任何其他具有公共实现的方法。
  3. 在项目中的任何地方,如果AbstractClass的子对象必须通过某种方法输入或输出,请将其键入为AbstractClass<Protocol>

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

当然,这并不能阻止不明智的开发人员添加简单地引用AbstractClass的新方法,这将最终允许(不再)抽象类的实例。

真实世界的例子:IGListKit有一个基类IGListSectionController,它不实现协议IGListSectionType,但是每个需要该类实例的方法实际上都要求类型IGListSectionController<IGListSectionType>。因此,在他们的框架中,没有办法使用IGListSectionController类型的对象来做任何有用的事情。

创建抽象类的简单示例

// 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