在Objective-C中创建一个抽象类

在Objective-C中没有直接的抽象类支持,但可以通过多种方式模拟抽象类的行为。这包括使用协议、使方法调用异常、在初始化方法中返回nil,以及在基类中实现协议并在子类中覆盖。虽然没有编译时强制,但可以利用编译器警告来提醒开发者实现必要的方法。
摘要由CSDN通过智能技术生成

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

如果没有,我可以在Objective-C中获得一个抽象类的接近程度?


#1楼

Omni Group邮件列表中

Objective-C目前没有像Java这样的抽象编译器结构。

因此,您所做的就是将抽象类定义为任何其他普通类,并为抽象方法实现方法存根,这些抽象方法为空或报告不支持选择器。 例如...

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

我还执行以下操作以防止通过默认初始化程序初始化抽象类。

- (id)init
{
     [self doesNotRecognizeSelector:_cmd];
     [self release];
     return nil;
}

#2楼

不,没有办法在Objective-C中创建一个抽象类。

您可以模拟一个抽象类 - 通过使方法/选择器调用doNotRecognizeSelector:并因此引发一个异常,使该类不可用。

例如:

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

您也可以为init执行此操作。


#3楼

通常,Objective-C类只是按照惯例抽象 - 如果作者将一个类记录为抽象,只是在没有子类化的情况下不使用它。 但是,没有编译时强制实施可以防止抽象类的实例化。 实际上,没有什么可以阻止用户通过类别(即在运行时)提供抽象方法的实现。 您可以通过在抽象类中的那些方法实现中引发异常来强制用户至少覆盖某些方法:

[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协议是更合适的选择。


#4楼

不要尝试创建抽象基类,而应考虑使用协议(类似于Java接口)。 这允许您定义一组方法,然后接受符合协议的所有对象并实现这些方法。 例如,我可以定义一个Operation协议,然后有一个这样的函数:

- (void)performOperation:(id<Operation>)op
{
   // do something with operation
}

其中op可以是实现Operation协议的任何对象。

如果您需要抽象基类不仅仅是定义方法,那么您可以创建一个常规的Objective-C类并防止它被实例化。 只需覆盖 - (id)init函数并使其返回nil或assert(false)。 它不是一个非常干净的解决方案,但由于Objective-C是完全动态的,因此实际上没有直接等同于抽象基类。


#5楼

(更多相关建议)

我希望有一种方法让程序员知道“不要从孩子打电话”并完全覆盖(在我的情况下,代表父母在未扩展时仍提供一些默认功能):

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 ..]

当然,为此定义单独的返回类型是很难看的,但它可以作为一个足够好的视觉提示,并且您很容易不在子类定义中使用“override_”部分。

当然,当扩展是可选的时,类仍然可以具有默认实现。 但是像其他答案一样,在适当的时候实现运行时异常,比如抽象(虚拟)类。

建立像这样的编译器提示会很好,甚至提示什么时候最好预先/后调用超级工具,而不是必须通过评论/文档挖掘或...假设。

提示的例子


#6楼

我想出的解决方案是:

  1. 为“抽象”类中的所有内容创建协议
  2. 创建一个实现协议的基类(或者可能称之为抽象)。 对于你想要“抽象”的所有方法,在.m文件中实现它们,而不是.h文件。
  3. 让您的子类继承自基类并实现协议。

这样,编译器将为您的子类未实现的协议中的任何方法提供警告。

它不像Java那样简洁,但你确实得到了所需的编译器警告。


#7楼

在Xcode中(使用clang等)我喜欢使用__attribute__((unavailable(...)))标记抽象类,以便在尝试使用它时收到错误/警告。

它提供了一些防止意外使用该方法的保护。

在基类@interface标签中的“抽象”方法:

- (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");

就像我说的,这不是真正的编译器保护,但它与你不会使用不支持抽象方法的语言一样好。


#8楼

可能这种情况应该只在开发时发生,所以这可能有效:

- (id)myMethodWithVar:(id)var {
   NSAssert(NO, @"You most override myMethodWithVar:");
   return nil;
}

#9楼

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

但是,我没有提到我最喜欢的方法,AFAIK在当前的Clang中没有原生支持,所以我在这里......

首先,最重要的是(正如其他人已经指出的那样)抽象类在Objective-C中非常罕见 - 我们通常使用组合(有时通过委托)来代替。 这可能是语言/编译器中尚未存在此类功能的原因 - 除了@dynamic属性之外, @dynamic CoreData的引入,IIRC已添加到ObjC 2.0中。

但考虑到这一点(在仔细评估了你的情况之后!)你得出的结论是委托(或一般的作文)不太适合解决你的问题,这就是如何做到的:

  1. 实现基类中的每个抽象方法。
  2. 进行实现[self doesNotRecognizeSelector:_cmd]; ...
  3. ...后跟__builtin_unreachable(); 沉默你会得到非空方法的警告,告诉你“控制到达非空虚函数的结束而没有返回”。
  4. 在一个宏中组合步骤2.和3.或者在没有实现的类别中使用__attribute__((__noreturn__))注释-[NSObject doesNotRecognizeSelector:]以便不替换该方法的原始实现,并包含该标题您项目的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启用编译时诊断。)

为什么我选择这种方法

  1. 它可以有效地完成工作,并且有些方便。
  2. 这很容易理解。 (好吧, __builtin_unreachable()可能会给人们带来惊喜,但也很容易理解。)
  3. 它不能在发布版本中被剥离而不会生成其他编译器警告或错误 - 与基于其中一个断言宏的方法不同。

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

一些(大多数?)人在发布版本中删除断言。 (我不同意这种习惯,但这是另一个故事......)未能实现所需的方法 - 然而 - 是坏的可怕的错误的 ,并且基本上是你的程序的宇宙结束 。 你的程序在这方面无法正常工作,因为它是未定义的,未定义的行为是最糟糕的事情。 因此,能够在不产生新诊断的情况下剥离这些诊断将是完全不可接受的。

很糟糕的是,你无法为这样的程序员错误获得正确的编译时诊断,并且不得不求助于at-run-time发现这些,但是如果你可以在发布版本中使用它,那么为什么要尝试在第一名?


#10楼

另一种选择

只需检查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的必要性


#11楼

这个问题的答案分散在已经给出的答案的评论中。 所以,我只是在这里总结和简化。

选项1:协议

如果要创建一个没有实现的抽象类,请使用“Protocols”。 继承协议的类必须实现协议中的方法。

@protocol ProtocolName
// list of methods and properties
@end

选项2:模板方法模式

如果要创建一个具有部分实现的抽象类,如“模板方法模式”,那么这就是解决方案。 Objective-C - 模板方法模式?


#12楼

实际上,Objective-C没有抽象类,但您可以使用Protocols来实现相同的效果。 这是样本:

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

#13楼

你不能只创建一个委托吗?

委托就像一个抽象基类,在某种意义上说你需要定义哪些函数,但实际上并没有定义它们。

然后,无论何时实现委托(即抽象类),编译器都会警告您需要为其定义行为的可选和必需函数。

这听起来像是一个抽象的基类。


#14楼

Cocoa不提供任何称为抽象的东西。 我们可以创建一个只在运行时检查的类抽象,并且在编译时不会检查它。


#15楼

如果您习惯于在其他语言中捕获抽象实例化违规的编译器,那么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

#16楼

您可以使用@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

您也可以使用协议和其他解决方案 - 但这是最​​简单的解决方案之一。


#17楼

我通常只是在我想要抽象的类中禁用init方法:

- (instancetype)__unavailable init; // This is an abstract class.

每当您在该类上调用init时,这将在编译时生成错误。 然后我将类方法用于其他一切。

Objective-C没有用于声明抽象类的内置方法。


#18楼

通过应用@ dotToString的评论来改变@redfood所建议的一点,你实际上已经获得了Instagram的IGListKit所采用的解决方案。

  1. 为所有在基础(抽象)类中定义无意义的方法创建协议,即它们需要在子代中具体实现。
  2. 创建一个实现此协议的基类(抽象)类。 您可以向此类添加任何其他有意义的方法以实现通用实现。
  3. 在项目的任何地方,如果必须通过某种方法输入或输出来自AbstractClass的子项,请将其键入为AbstractClass<Protocol>

因为AbstractClass不实现Protocol ,所以拥有AbstractClass<Protocol>实例的唯一方法是通过子类化。 由于AbstractClass本身不能在项目的任何地方使用,因此它变得抽象。

当然,这并不妨碍未经修改的开发人员添加简单引用AbstractClass新方法,这最终会允许(不再是)抽象类的实例。

真实世界的例子: IGListKit有一个基类IGListSectionController ,它没有实现协议IGListSectionType ,但是每个需要该类实例的方法实际上都要求IGListSectionController<IGListSectionType>类型IGListSectionController<IGListSectionType> 。 因此,没有办法使用IGListSectionController类型的对象来实现其框架中有用的任何东西。


#19楼

使用@property@dynamic也可以工作。 如果您声明了一个动态属性并且没有给出匹配的方法实现,那么一切都将在没有警告的情况下进行编译,如果您尝试访问它,您将在运行时遇到unrecognized selector错误。 这与调用[self doesNotRecognizeSelector:_cmd]基本相同,但键入的次数要少得多。


#20楼

创建抽象类的简单示例

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

#21楼

刚刚在@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
}



注意:不确定将宏看起来像是一个C函数是不是一个好主意,但我会保持它直到相反的学习。 我认为使用NSInvalidArgumentException (而不是NSInternalInconsistencyException )更为正确,因为这是运行时系统为响应被调用的doesNotRecognizeSelector而抛出的内容(请参阅NSObject文档)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值