Jon*_*ast 501 abstract-class objective-c
我原来是一名Java程序员,现在使用Objective-C.我想创建一个抽象类,但在Objective-C中似乎不可能.这可能吗?
如果没有,我可以在Objective-C中获得一个抽象类的接近程度?
Bar*_*ark 627
通常,Objective-C类只是按照惯例抽象 - 如果作者将一个类记录为抽象,只是在没有子类化的情况下不使用它.但是,没有编译时强制实施可以防止抽象类的实例化.实际上,没有什么可以阻止用户通过类别(即在运行时)提供抽象方法的实现.您可以通过在抽象类中的那些方法实现中引发异常来强制用户至少覆盖某些方法:
[NSException raise:NSInternalInconsistencyException
format:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)];
Run Code Online (Sandbox Code Playgroud)
如果您的方法返回一个值,则使用起来会更容易一些
@throw [NSException exceptionWithName:NSInternalInconsistencyException
reason:[NSString stringWithFormat:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)]
userInfo:nil];
Run Code Online (Sandbox Code Playgroud)
因为那时你不需要从方法中添加一个return语句.
如果抽象类实际上是一个接口(即没有具体的方法实现),那么使用Objective-C协议是更合适的选择.
Gro*_*hal 267
不,没有办法在Objective-C中创建一个抽象类.
您可以模拟一个抽象类 - 通过使方法/选择器调用doNotRecognizeSelector:并因此引发一个异常,使该类不可用.
例如:
- (id)someMethod:(SomeObject*)blah
{
[self doesNotRecognizeSelector:_cmd];
return nil;
}
Run Code Online (Sandbox Code Playgroud)
您也可以为init执行此操作.
Dan*_*ark 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()
Run Code Online (Sandbox Code Playgroud)
然后在你的方法中你可以使用它
- (void) someMethod {
mustOverride(); // or methodNotImplemented(), same thing
}
Run Code Online (Sandbox Code Playgroud)
注意:不确定将宏看起来像是一个C函数是不是一个好主意,但我会保持它直到相反的学习.我认为使用NSInvalidArgumentException(而不是NSInternalInconsistencyException)更正确,因为这是运行时系统为响应doesNotRecognizeSelector被调用而抛出的内容(请参阅NSObjectdocs).
red*_*ood 42
我想出的解决方案是:
这样,编译器将为您的子类未实现的协议中的任何方法提供警告.
它不像Java那样简洁,但你确实得到了所需的编译器警告.
小智 35
Objective-C目前没有像Java这样的抽象编译器结构.
因此,您所做的就是将抽象类定义为任何其他普通类,并为抽象方法实现方法存根,这些抽象方法为空或报告不支持选择器.例如...
- (id)someMethod:(SomeObject*)blah
{
[self doesNotRecognizeSelector:_cmd];
return nil;
}
Run Code Online (Sandbox Code Playgroud)
我还执行以下操作以防止通过默认初始化程序初始化抽象类.
- (id)init
{
[self doesNotRecognizeSelector:_cmd];
[self release];
return nil;
}
Run Code Online (Sandbox Code Playgroud)
Ben*_*tow 21
不要尝试创建抽象基类,而应考虑使用协议(类似于Java接口).这允许您定义一组方法,然后接受符合协议的所有对象并实现这些方法.例如,我可以定义一个Operation协议,然后有一个这样的函数:
- (void)performOperation:(id<Operation>)op
{
// do something with operation
}
Run Code Online (Sandbox Code Playgroud)
其中op可以是实现Operation协议的任何对象.
如果您需要抽象基类不仅仅是定义方法,那么您可以创建一个常规的Objective-C类并防止它被实例化.只需覆盖 - (id)init函数并使其返回nil或assert(false).它不是一个非常干净的解决方案,但由于Objective-C是完全动态的,因此实际上没有直接等同于抽象基类.
dan*_*dee 19
这个帖子有点陈旧,我要分享的大部分内容已经在这里了.
但是,我没有提到我最喜欢的方法,AFAIK在当前的Clang中没有原生支持,所以我在这里......
首先,最重要的是(正如其他人已经指出的那样)抽象类在Objective-C中非常罕见 - 我们通常使用组合(有时通过委托)来代替.这可能是语言/编译器中尚未存在此类功能的原因 - 除了@dynamic属性之外,随着CoreData的引入,在ObjC 2.0中添加了IIRC.
但考虑到这一点(在仔细评估了你的情况之后!)你得出的结论是,委托(或一般的作文)不太适合解决你的问题,这就是我如何做到的:
[self doesNotRecognizeSelector:_cmd];......__builtin_unreachable();静音你将得到的非空方法的警告,告诉你"控制到达无效函数的结束而没有返回".-[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
Run Code Online (Sandbox Code Playgroud)
如您所见,宏提供了抽象方法的完整实现,将必要的样板量减少到绝对最小值.
更好的选择是游说 Clang团队通过功能请求为此案例提供编译器属性.(更好,因为这也可以为您子类化的例如NSIncrementalStore启用编译时诊断.)
__builtin_unreachable()可能会给人们带来惊喜,但也很容易理解.)我想最后一点需要一些解释:
一些(大多数?)人在发布版本中删除断言.(我不同意这种习惯,但那是另一个故事......)未能实现所需的方法 - 然而 - 是坏的,可怕的,错误的,并且基本上是你的程序的宇宙结束.你的程序在这方面无法正常工作,因为它是未定义的,未定义的行为是最糟糕的事情.因此,能够在不产生新诊断的情况下剥离这些诊断将是完全不可接受的.
很糟糕的是,你无法为这样的程序员错误获得正确的编译时诊断,并且不得不求助于at-run-time发现这些,但是如果你可以在发布版本中使用它,那么为什么要尝试在第一名?
Cam*_*ert 12
使用@property,@dynamic也可以工作.如果声明一个动态属性并且没有给出匹配的方法实现,那么所有内容仍然会在没有警告的情况下进行编译,unrecognized selector如果您尝试访问它,则会在运行时收到错误.这与调用基本相同[self doesNotRecognizeSelector:_cmd],但输入的次数要少得多.
在Xcode中(使用clang等)我喜欢使用__attribute__((unavailable(...)))标记抽象类,以便在尝试使用它时收到错误/警告.
它提供了一些防止意外使用该方法的保护.
在基类@interface标记中的"抽象"方法:
- (void)myAbstractMethod:(id)param1 __attribute__((unavailable("You should always override this")));
Run Code Online (Sandbox Code Playgroud)
更进一步,我创建了一个宏:
#define UnavailableMacro(msg) __attribute__((unavailable(msg)))
Run Code Online (Sandbox Code Playgroud)
这可以让你这样做:
- (void)myAbstractMethod:(id)param1 UnavailableMacro(@"You should always override this");
Run Code Online (Sandbox Code Playgroud)
就像我说的,这不是真正的编译器保护,但它与你不会使用不支持抽象方法的语言一样好.
这个问题的答案分散在已经给出的答案的评论中.所以,我只是在这里总结和简化.
如果要创建一个没有实现的抽象类,请使用"Protocols".继承协议的类必须实现协议中的方法.
@protocol ProtocolName
// list of methods and properties
@end
Run Code Online (Sandbox Code Playgroud)
如果要创建一个具有部分实现的抽象类,如"模板方法模式",那么这就是解决方案. Objective-C - 模板方法模式?
另一种选择
只需检查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
Run Code Online (Sandbox Code Playgroud)
这消除了覆盖的必要性 init
(更多相关建议)
我希望有一种方法让程序员知道"不要从孩子打电话"并完全覆盖(在我的情况下,代表父母在未扩展时仍提供一些默认功能):
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
Run Code Online (Sandbox Code Playgroud)
优点是程序员将在声明中看到"覆盖"并且知道他们不应该打电话[super ..].
当然,为此定义单独的返回类型是很难看的,但它可以作为一个足够好的视觉提示,并且您很容易不在子类定义中使用"override_"部分.
当然,当扩展是可选的时,类仍然可以具有默认实现.但是像其他答案一样,在适当的时候实现运行时异常,比如抽象(虚拟)类.
建立像这样的编译器提示会很好,甚至提示什么时候最好预先/后调用超级工具,而不是必须通过评论/文档挖掘或...假设.
