在Objective-C中创build一个抽象类

我原来是一个Java程序员,现在与Objective-C一起工作。 我想创build一个抽象类,但在Objective-C中看起来是不可能的。 这可能吗?

如果不是,我可以在Objective-C中得到多近的抽象类?

通常情况下,Objective-C类只是按照惯例抽象的 – 如果作者将一个类文档化为抽象类,就不要在没有子类的情况下使用它。 但是,没有编译时实施,可以防止抽象类的实例化。 事实上,没有任何东西可以阻止用户通过类别(即在运行时)提供抽象方法的实现。 您可以强制用户至less在抽象类的这些方法实现中引发exception,从而重写某些方法:

[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]; 

因为你不需要从方法中添加返回语句。

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

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

您可以模拟一个抽象类 – 通过使方法/select器调用doesNotRecognizeSelector:并因此引发exception,使类不可用。

例如:

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

您也可以为init执行此操作。

只是在@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 } 

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

我提出的解决scheme是:

  1. 为您的“抽象”类中的所有内容创build一个协议
  2. 创build一个实现协议的基类(或者称之为抽象的)。 对于所有你想要“抽象”的方法在.m文件中实现它们,而不是.h文件。
  3. 让你的子类从基类inheritance并实现协议。

这样编译器会给你一个警告,说明协议中没有被你的子类实现的任何方法。

这不像在Java中那样简洁,但你得到了所需的编译器警告。

来自Omni Group邮件列表 :

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

因此,您所做的只是将抽象类定义为任何其他常规类,并为抽象方法实现方法存根,这些方法为空或报告不支持select器。 例如…

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

我也做了以下操作来防止通过默认初始化程序初始化抽象类。

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

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

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

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

如果你需要你的抽象基类做的不仅仅是定义方法,你可以创build一个常规的Objective-C类,并阻止它被实例化。 只需重写 – (id)init函数,并返回nil或assert(false)。 这不是一个非常干净的解决scheme,但是由于Objective-C是完全dynamic的,所以真的没有直接的等价于抽象基类。

这个线程是有点老,我想分享的大部分已经在这里。

但是,我最喜欢的方法没有提到,AFAIK目前的铛没有本地支持,所以在这里我去…

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

但是,考虑到(仔细评估你的情况后),你得出的结论是,代表团(或一般组合)不适合解决你的问题,下面是该怎么做:

  1. 实现基类中的每个抽象方法。
  2. 做这个实现[self doesNotRecognizeSelector:_cmd];
  3. …之后是__builtin_unreachable(); 要沉默你将得到的非无效方法的警告,告诉你“控制达到非无效函数的结束而不返回”。
  4. 要么将一个macros中的步骤2和步骤3组合起来,要么在没有实现的类别中使用__attribute__((__noreturn__))注释-[NSObject doesNotRecognizeSelector:]以便不replace该方法的原始实现,类别在您的项目的PCH。

我个人更喜欢macros观版本,因为这样可以尽可能地减less样板。

这里是:

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

正如您所看到的,macros提供了抽象方法的完整实现,将必要的样板量减less到绝对最小值。

更好的select是游说 Clang团队通过function请求为这种情况提供编译器属性。 (更好的是,因为这也可以为那些子类如NSIncrementalStore的场景启用编译时诊断。)

为什么我select这种方法

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

最后一点需要一些解释,我猜:

一些(大多数?)人在发布版本中剥离断言。 (我不同意这种习惯,但这是另一回事……)然而,实施一种必要的方法是糟糕的可怕的错误的 ,并且基本上是你程序的宇宙的终结 。 你的程序在这方面不能正确工作,因为它是未定义的,未定义的行为是有史以来最糟糕的事情。 因此,能够剥离这些诊断而不产生新的诊断将是完全不可接受的。

已经足够糟糕了,你无法获得适合这种编程错误的编译时间诊断,并且不得不求助于运行时发现这些错误,但是如果你可以在发布版本中修改它,为什么要在第一名?

使用@dynamic@dynamic也可以工作。 如果你声明了一个dynamic属性,并且没有给出一个匹配的方法实现,那么所有东西都会在没有警告的情况下编译,如果你尝试访问它,你将在运行时得到一个unrecognized selector错误。 这与调用[self doesNotRecognizeSelector:_cmd]基本上是一样的,但是inputless得多。

在Xcode(使用clang等),我喜欢使用__attribute__((unavailable(...)))来标记抽象类,所以如果你尝试使用它,你会得到一个错误/警告。

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

在基类@interface标记“抽象”方法:

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

再进一步,我创build一个macros:

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

这可以让你这样做:

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

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

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

选项1:协议

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

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

选项2:模板方法模式

如果你想创build一个类似“模板方法模式”的部分实现的抽象类,那么这是解决scheme。 Objective-C – 模板方法模式?

另一种select

不pipe你喜欢什么,只要检查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的必要性

(更多相关build议)

我想让程序员知道“不要从孩子调用”并完全重写(在我的情况下,仍然提供一些默认的function代表父母不扩展时):

 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 ..]

当然,为这个定义单独的返回types是丑陋的,但是它是一个足够好的视觉提示,你可以很容易地不在子类定义中使用“override_”部分。

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

如果有这样的编译器提示,甚至可以提示何时最好是预先调用超级实现,而不是通过评论/文档或假设来挖掘,这将是很好的。

提示的例子

如果您习惯了使用其他语言捕获抽象实例违规的编译器,那么Objective-C的行为是令人失望的。

作为一种后期绑定语言,Objective-C显然不能对一个类是否真的是抽象的做出静态的决定(你可能会在运行时添加一些函数),但是对于典型的用例来说,这似乎是一个缺点。 我宁愿编译器不使用抽象类的实例,而是在运行时抛出一个错误。

这是我们正在使用的一种模式来获得这种types的静态检查,使用一些技术来隐藏初始值设定项:

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

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

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

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

也可以使用协议和其他解决scheme – 但这是最​​简单的一种。

cocoa不提供任何所谓的抽象。 我们可以创build一个只在运行时检查的类抽象,在编译时不检查。

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

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

每当你调用该类的init时,这将在编译时产生一个错误。 然后我使用类方法的一切。

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

通过应用@ dotToString的评论改变了@redfood的build议,你实际上已经有了Instagram的IGListKit所采用的解决scheme。

  1. 为所有在基类(抽象)类中定义的没有意义的方法创build协议,即它们需要在子节点中具体实现。
  2. 创build一个实现这个协议的基类(抽象)类。 你可以添加其他任何有意义的方法来实现一个共同的实现。
  3. 在项目的任何地方,如果AbstractClass的子项必须被某种方法input或输出,请改为inputAbstractClass<Protocol>

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

当然,这并不妨碍无人看pipe的开发人员添加新的方法,只是简单地指向AbstractClass ,最终将允许(不再是)抽象类的实例。

真实世界的例子: IGListKit有一个基类IGListSectionController ,它没有实现协议IGListSectionType ,但是每一个需要该类实例的方法,实际上要求IGListSectionController<IGListSectionType>types。 因此,无法在其框架中使用IGListSectionControllertypes的对象来实现任何有用的function。

实际上,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 

一个创build抽象类的简单例子

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

你不能只是创build一个委托?

一个委托就像一个抽象基类,就是说你需要定义哪些函数,但是实际上并没有定义它们。

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

这听起来像是一个抽象的基类。