Creating an abstract class in Objective-C
我最初是一个Java程序员,现在使用Objto-C工作。我想创建一个抽象类,但是在Objtovi-C中似乎不可能。这是可能的吗?
如果不是,我能在Objective-C中接近抽象类多少?
通常,Objective-C类只是在作者将一个类作为抽象类进行文档记录的情况下,按照约定是抽象的,只是不要在不进行子类化的情况下使用它。然而,没有编译时强制来阻止抽象类的实例化。实际上,没有什么可以阻止用户通过类别(即在运行时)提供抽象方法的实现。通过在抽象类中的这些方法实现中引发异常,可以强制用户至少重写某些方法:
1 2 | [NSException raise:NSInternalInconsistencyException format:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)]; |
如果你的方法返回一个值,它会更容易使用
1 2 3 | @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)] userInfo:nil]; |
因此,您不需要从方法中添加返回语句。
如果抽象类实际上是一个接口(即没有具体的方法实现),使用Objective-C协议是更合适的选择。
不,不能在Objective-C中创建抽象类。
您可以通过使方法/选择器调用doesNoteCongnizeSelector来模拟抽象类,从而引发异常,使类不可用。
例如:
1 2 3 4 5 | - (id)someMethod:(SomeObject*)blah { [self doesNotRecognizeSelector:_cmd]; return nil; } |
您也可以为init执行此操作。
只需快速浏览一下@barry wark上面的答案(并更新为iOS 4.3),然后留下这个供我自己参考:
1 2 | #define mustOverride() @throw [NSException exceptionWithName:NSInvalidArgumentException reason:[NSString stringWithFormat:@"%s must be overridden in a subclass/category", __PRETTY_FUNCTION__] userInfo:nil] #define methodNotImplemented() mustOverride() |
那么在你的方法中你可以使用这个
1 2 3 | - (void) someMethod { mustOverride(); // or methodNotImplemented(), same thing } |
注意:不确定是否让宏看起来像C函数是一个好主意,但我会一直保留它直到相反的学习。我认为使用
我提出的解决方案是:
通过这种方式,编译器会对协议中任何未由子类实现的方法发出警告。
它不像Java那样简洁,但您确实得到了所需的编译器警告。
从Omni Group邮件列表中:
Objy-C不具有像Java那样的抽象编译器构造。这次。
所以你所要做的就是将抽象类定义为任何其他普通类并为抽象方法实现方法存根空或报告不支持选择器。例如。。。
1 2 3 4 5 | - (id)someMethod:(SomeObject*)blah { [self doesNotRecognizeSelector:_cmd]; return nil; } |
我还执行以下操作以防止对摘要的初始化通过默认初始值设定项初始化。
1 2 3 4 5 6 | - (id)init { [self doesNotRecognizeSelector:_cmd]; [self release]; return nil; } |
不要试图创建一个抽象基类,而是考虑使用一个协议(类似于Java接口)。这允许您定义一组方法,然后接受所有符合协议的对象并实现这些方法。例如,我可以定义一个操作协议,然后具有如下功能:
1 2 3 4 | - (void)performOperation:(id<Operation>)op { // do something with operation } |
其中op可以是实现操作协议的任何对象。
如果您需要抽象基类做的不仅仅是定义方法,那么您可以创建一个常规的Objective-C类并防止它被实例化。只需重写-(id)init函数并使其返回nil或assert(false)。这不是一个非常干净的解决方案,但是由于Objective-C是完全动态的,所以实际上没有直接等价于抽象基类的方法。
这条线有点旧,我想分享的大部分已经在这里了。
然而,我最喜欢的方法没有提到,而阿法克在当前的clang中没有本地支持,所以我来……
首先,也是最重要的(正如其他人已经指出的那样),抽象类在Objective-C中是非常少见的——我们通常使用组合(有时通过委托)来代替。这可能就是为什么在语言/编译器中不存在这样一个特性的原因——除了
但考虑到这一点(在仔细评估了你的处境之后!)你得出的结论是,代表团(或一般的组成)不适合解决你的问题,我是这样做的:
我个人更喜欢宏版本,因为它允许我尽可能地减少样板文件。
这里是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | // 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)的场景启用编译时诊断。)
我为什么选择这个方法最后一点需要解释,我想:
一些(大多数?)人们在发布版本中去掉断言。(我不同意这个习惯,但那是另一个故事…)未能实现所需的方法-然而-是坏的,可怕的,错误的,基本上是你的程序的宇宙末日。在这方面,您的程序无法正常工作,因为它是未定义的,而未定义的行为是有史以来最糟糕的事情。因此,能够在不生成新诊断的情况下删除这些诊断将是完全不可接受的。
对于这样的程序员错误,您不能获得适当的编译时诊断已经够糟糕了,并且必须在运行时对这些错误进行发现,但是如果您可以在发布版本中对其进行覆盖,为什么首先尝试使用抽象类呢?
使用
在Xcode(使用clang等)中,我喜欢使用
它提供了一些防止意外使用该方法的保护。
例子在基类
1 | - (void)myAbstractMethod:(id)param1 __attribute__((unavailable("You should always override this"))); |
更进一步,我创建了一个宏:
1 | #define UnavailableMacro(msg) __attribute__((unavailable(msg))) |
这样可以做到:
1 | - (void)myAbstractMethod:(id)param1 UnavailableMacro(@"You should always override this"); |
正如我所说,这不是真正的编译器保护,但它和您使用不支持抽象方法的语言一样好。
问题的答案分散在已经给出答案的评论中。所以,我只是在这里总结和简化。
选项1:协议如果要创建不带实现的抽象类,请使用"协议"。继承协议的类必须实现协议中的方法。
1 2 3 | @protocol ProtocolName // list of methods and properties @end |
选项2:模板方法模式
如果您想创建一个抽象类,并使用"模板方法模式"之类的部分实现,那么这就是解决方案。目标-模板方法模式?
另一种选择
只要检查抽象类中的类,并断言或异常,不管您喜欢什么。
1 2 3 4 5 6 7 8 9 10 | @implementation Orange - (instancetype)init { self = [super init]; NSAssert([self class] != [Orange class], @"This is an abstract class"); if (self) { } return self; } @end |
这就消除了覆盖
(更多相关建议)
我希望有一种方法让程序员知道"不要从孩子那里调用",并完全重写(在我的情况下,在不扩展时仍然代表家长提供一些默认功能):
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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",并且知道不应该调用
当然,必须为此定义单独的返回类型是很难看的,但它提供了足够好的视觉提示,您很容易就不能在子类定义中使用"override_uuu"部分。
当然,当扩展是可选的时,类仍然可以有一个默认实现。但和其他答案一样,在适当的时候实现一个运行时异常,比如抽象(虚拟)类。
最好是有这样的内置编译器提示,甚至提示什么时候最好调用super的实现,而不是必须挖掘注释/文档或…假定。
如果您习惯于编译器捕获其他语言中的抽象实例化冲突,那么objective-c行为是令人失望的。
作为一种后期的绑定语言,显然Objective-C不能对类是否是抽象的(您可能在运行时添加函数…)做出静态决策,但对于典型的用例,这似乎是一个缺点。我更喜欢编译器完全阻止抽象类的实例化,而不是在运行时抛出错误。
下面是一个模式,我们使用一些技巧隐藏初始值设定项来获取这种类型的静态检查:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | // // 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 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | // // 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 |
1 2 3 4 5 6 7 | // // Derived.h #import"Base.h" @interface Derived : Base -(instancetype) initDerived; // We cannot use init here :( @end |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | // // 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建议的方法(进行一些修改):
1 2 | #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() |
在这里,您将收到如下信息:
1 2 | <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' |
或断言:
1 | NSAssert(![self respondsToSelector:@selector(<MethodName>)], @"Not implemented"); |
在这种情况下,您将得到:
1 | <Date> ProjectName[7926:1967491] *** Assertion failure in -[<Class Name> <Method name>], /Users/kirill/Documents/Projects/root/<ProjectName> Services/Classes/ViewControllers/YourClass:53 |
您也可以使用协议和其他解决方案,但这是最简单的解决方案之一。
可能这种情况只应该发生在开发时,因此这可能有效:
1 2 3 4 | - (id)myMethodWithVar:(id)var { NSAssert(NO, @"You most override myMethodWithVar:"); return nil; } |
可可不提供任何抽象的东西。我们可以创建一个类抽象,它只在运行时检查,而在编译时不检查。
我通常只在我想要抽象的类中禁用init方法:
1 | - (instancetype)__unavailable init; // This is an abstract class. |
每当您调用该类的init时,这将在编译时生成一个错误。然后,我将类方法用于其他所有内容。
Objective-C没有用于声明抽象类的内置方式。
创建抽象类的简单示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 | // 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 |
实际上,Objective-C没有抽象类,但是您可以使用协议来实现相同的效果。这是样品:
自定义协议.h1 2 3 4 5 6 7 8 | #import <Foundation/Foundation.h> @protocol CustomProtocol <NSObject> @required - (void)methodA; @optional - (void)methodB; @end |
测试协议
1 2 3 4 5 6 | #import <Foundation/Foundation.h> #import"CustomProtocol.h" @interface TestProtocol : NSObject <CustomProtocol> @end |
测试协议
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | #import"TestProtocol.h" @implementation TestProtocol - (void)methodA { NSLog(@"methodA..."); } - (void)methodB { NSLog(@"methodB..."); } @end |
通过应用@dottoString的评论,稍微改变一下@redfood的建议,您实际上已经得到了Instagram的iglistkit所采用的解决方案。
因为
当然,这并不能阻止未经授权的开发人员添加只引用
现实世界的例子:iglistkit有一个基础类
你不能创建一个代理吗?
委托就像一个抽象的基类,从某种意义上说,你需要定义什么函数,但实际上你没有定义它们。
然后,每当实现委托(即抽象类)时,编译器就会警告您需要为哪些可选和强制函数定义行为。
对我来说,这听起来像一个抽象的基类。