我最初是一名Java程序员,现在使用Objective-C。我想创建一个抽象类,但这在Objective-C中似乎不可能。这可能吗?
如果不是,在Objective-C中,我能有多接近抽象类?
我最初是一名Java程序员,现在使用Objective-C。我想创建一个抽象类,但这在Objective-C中似乎不可能。这可能吗?
如果不是,在Objective-C中,我能有多接近抽象类?
当前回答
这个帖子有点老了,我想分享的大部分内容已经在这里了。
然而,我最喜欢的方法没有被提及,而且AFAIK在当前的Clang中没有原生支持,所以我在这里…
首先,也是最重要的(正如其他人已经指出的),抽象类在Objective-C中是非常不常见的——我们通常使用组合(有时通过委托)来代替。这可能就是为什么语言/编译器中还没有这样的特性的原因——除了@dynamic属性,IIRC在ObjC 2.0中随着CoreData的引入而添加了@dynamic属性。
但考虑到(在仔细评估你的情况后!)你已经得出结论,委托(或一般的组合)并不适合解决你的问题,以下是我是如何做的:
Implement every abstract method in the base class. Make that implementation [self doesNotRecognizeSelector:_cmd];… …followed by __builtin_unreachable(); to silence the warning you’ll get for non-void methods, telling you “control reached end of non-void function without a return”. Either combine steps 2. and 3. in a macro, or annotate -[NSObject doesNotRecognizeSelector:] using __attribute__((__noreturn__)) in a category without implementation so as not to replace the original implementation of that method, and include the header for that category in your project’s 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
如您所见,宏提供了抽象方法的完整实现,将必要的样板文件数量减少到绝对最小。
更好的选择是游说Clang团队通过特性请求为这种情况提供一个编译器属性。(更好,因为这也将为那些你子类化NSIncrementalStore的场景启用编译时诊断。)
为什么选择这种方法
它能有效地完成工作,而且有点方便。 这很容易理解。(好吧,__builtin_unreachable()可能会让人感到惊讶,但它也很容易理解。) 在发布版本中,如果不生成其他编译器警告或错误,就无法剥离它——这与基于断言宏之一的方法不同。
我想最后一点需要解释一下:
一些(大多数?)人在发布版本中剥离断言。(我不同意这种习惯,但那是另一个故事了……)然而,未能实现所需的方法是糟糕的、可怕的、错误的,基本上是你的程序的宇宙末日。在这方面,您的程序不能正确工作,因为它是未定义的,而未定义的行为是最糟糕的事情。因此,在不生成新的诊断的情况下剥离这些诊断是完全不可接受的。
对于这样的程序员错误,您无法获得适当的编译时诊断,并且不得不求助于运行时发现,这已经够糟糕的了,但是如果您可以在发布版本中掩盖它,为什么要首先尝试使用抽象类呢?
其他回答
与其尝试创建抽象基类,不如考虑使用协议(类似于Java接口)。这允许您定义一组方法,然后接受符合协议的所有对象并实现这些方法。例如,我可以定义一个操作协议,然后有一个这样的函数:
- (void)performOperation:(id<Operation>)op
{
// do something with operation
}
其中op可以是任何实现Operation协议的对象。
如果您需要抽象基类做的不仅仅是定义方法,那么您可以创建一个常规的Objective-C类并防止它被实例化。只需重写- (id)init函数,并使其返回nil或assert(false)。这不是一个非常干净的解决方案,但由于Objective-C是完全动态的,所以实际上没有与抽象基类直接等价的东西。
使用@property和@dynamic也可以。如果您声明了一个动态属性,但没有给出匹配的方法实现,那么所有内容仍然会在没有警告的情况下编译,并且如果您试图访问它,将在运行时得到一个无法识别的选择器错误。这本质上与调用[self doesNotRecognizeSelector:_cmd]是一样的,但是输入要少得多。
通过应用@dotToString的评论,稍微改变一下@redfood的建议,你实际上得到了Instagram的IGListKit所采用的解决方案。
为所有在基(抽象)类中定义没有意义的方法创建一个协议,即它们需要在子类中特定的实现。 创建一个不实现此协议的基(抽象)类。您可以向该类添加任何其他具有公共实现的方法。 在项目中的任何地方,如果必须通过某种方法输入或输出AbstractClass的子对象,请将其键入为AbstractClass<Protocol>。
因为AbstractClass没有实现Protocol,所以拥有AbstractClass<Protocol>实例的唯一方法是子类化。由于AbstractClass不能单独在项目中的任何地方使用,因此它变得抽象。
当然,这并不能阻止不明智的开发人员添加简单地引用AbstractClass的新方法,这最终将允许抽象类的实例(不再是)。
真实世界的例子:IGListKit有一个基类IGListSectionController,它不实现协议IGListSectionType,但是每个方法都需要这个类的实例,实际上要求类型IGListSectionController<IGListSectionType>。因此,在框架中没有办法使用IGListSectionController类型的对象来做任何有用的事情。
在Xcode中(使用clang等),我喜欢使用__attribute__((不可用(…)))来标记抽象类,所以如果你尝试使用它,你会得到一个错误/警告。
它提供了一些防止意外使用该方法的保护。
例子
在基类@interface标记中,"abstract"方法:
- (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");
就像我说的,这不是真正的编译器保护,但它和你在不支持抽象方法的语言中得到的一样好。
Typically, Objective-C class are abstract by convention only—if the author documents a class as abstract, just don't use it without subclassing it. There is no compile-time enforcement that prevents instantiation of an abstract class, however. In fact, there is nothing to stop a user from providing implementations of abstract methods via a category (i.e. at runtime). You can force a user to at least override certain methods by raising an exception in those methods implementation in your abstract class:
[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协议是更合适的选择。