在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是:
- 为您的“抽象”类中的所有内容创build一个协议
- 创build一个实现协议的基类(或者称之为抽象的)。 对于所有你想要“抽象”的方法在.m文件中实现它们,而不是.h文件。
- 让你的子类从基类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。
但是,考虑到(仔细评估你的情况后),你得出的结论是,代表团(或一般组合)不适合解决你的问题,下面是我该怎么做:
- 实现基类中的每个抽象方法。
- 做这个实现
[self doesNotRecognizeSelector:_cmd];
… - …之后是
__builtin_unreachable();
要沉默你将得到的非无效方法的警告,告诉你“控制达到非无效函数的结束而不返回”。 - 要么将一个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这种方法
- 它有效地完成了工作,而且有点方便。
- 这很容易理解。 (好的,
__builtin_unreachable()
可能让人感到惊讶,但也很容易理解)。 - 它不能在发布版本中被剥离,而不会产生其他的编译器警告或错误 – 与基于断言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。
- 为所有在基类(抽象)类中定义的没有意义的方法创build协议,即它们需要在子节点中具体实现。
- 创build一个不实现这个协议的基类(抽象)类。 你可以添加其他任何有意义的方法来实现一个共同的实现。
- 在项目的任何地方,如果
AbstractClass
的子项必须被某种方法input或输出,请改为inputAbstractClass<Protocol>
。
因为AbstractClass
没有实现Protocol
,所以拥有AbstractClass<Protocol>
实例的唯一方法是通过子类化。 由于AbstractClass
本身不能在项目的任何地方使用,因此变得抽象。
当然,这并不妨碍无人看pipe的开发人员添加新的方法,只是简单地指向AbstractClass
,最终将允许(不再是)抽象类的实例。
真实世界的例子: IGListKit有一个基类IGListSectionController
,它没有实现协议IGListSectionType
,但是每一个需要该类实例的方法,实际上要求IGListSectionController<IGListSectionType>
types。 因此,无法在其框架中使用IGListSectionController
types的对象来实现任何有用的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。
这听起来像是一个抽象的基类。