我最初是一名Java程序员,现在使用Objective-C。我想创建一个抽象类,但这在Objective-C中似乎不可能。这可能吗?

如果不是,在Objective-C中,我能有多接近抽象类?


当前回答

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

其他回答

使用@property和@dynamic也可以。如果您声明了一个动态属性,但没有给出匹配的方法实现,那么所有内容仍然会在没有警告的情况下编译,并且如果您试图访问它,将在运行时得到一个无法识别的选择器错误。这本质上与调用[self doesNotRecognizeSelector:_cmd]是一样的,但是输入要少得多。

另一种替代方法

只要在抽象类和断言或异常中检查类,无论你喜欢什么。

@implementation Orange
- (instancetype)init
{
    self = [super init];
    NSAssert([self class] != [Orange class], @"This is an abstract class");
    if (self) {
    }
    return self;
}
@end

这消除了重写init的必要性

不,在Objective-C中无法创建抽象类。

你可以模拟一个抽象类——通过让methods/ selectors调用doesNotRecognizeSelector:从而引发一个异常,使该类不可用。

例如:

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

你也可以为init这样做。

这个帖子有点老了,我想分享的大部分内容已经在这里了。

然而,我最喜欢的方法没有被提及,而且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()可能会让人感到惊讶,但它也很容易理解。) 在发布版本中,如果不生成其他编译器警告或错误,就无法剥离它——这与基于断言宏之一的方法不同。

我想最后一点需要解释一下:

一些(大多数?)人在发布版本中剥离断言。(我不同意这种习惯,但那是另一个故事了……)然而,未能实现所需的方法是糟糕的、可怕的、错误的,基本上是你的程序的宇宙末日。在这方面,您的程序不能正确工作,因为它是未定义的,而未定义的行为是最糟糕的事情。因此,在不生成新的诊断的情况下剥离这些诊断是完全不可接受的。

对于这样的程序员错误,您无法获得适当的编译时诊断,并且不得不求助于运行时发现,这已经够糟糕的了,但是如果您可以在发布版本中掩盖它,为什么要首先尝试使用抽象类呢?

如果你习惯了编译器在其他语言中捕捉抽象实例化的冲突,那么Objective-C的行为是令人失望的。

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

下面是我们使用的一个模式,使用一些技术来隐藏初始化式来获得这种类型的静态检查:

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