首页 文章

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

提问于
浏览
498

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

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

21 回答

  • 7

    通常,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协议是更合适的选择 .

  • 625

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

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

    例如:

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

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

  • -3

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

    Notes: 不确定是否使宏看起来像C函数是一个好主意,但是'll keep it until schooled to the contrary. I think it'更正确使用 NSInvalidArgumentException (而不是 NSInternalInconsistencyException ),因为这是运行时系统为响应 doesNotRecognizeSelector 而调用的内容(参见 NSObject docs) ) .

  • 3

    我想出的解决方案是:

    • 为"abstract"类中的所有内容创建协议

    • 创建实现协议的基类(或者可能称之为抽象) . 对于您想要的所有方法"abstract"在.m文件中实现它们,而不是.h文件 .

    • 让您的子类继承自基类并实现协议 .

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

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

  • 21

    来自Omni Group mailing list

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

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

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

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

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

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

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

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

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

  • 0

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

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

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

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

    • 实施基类中的每个抽象方法 .

    • 实现 [self doesNotRecognizeSelector:_cmd]; ......

    • ...后跟 __builtin_unreachable(); 以使非空方法的警告静音,告诉你“控制在没有返回的情况下达到非空函数的结束” .

    • 在宏中组合步骤2.和3.或在类别 without implementation 中使用 __attribute__((__noreturn__)) 注释 -[NSObject doesNotRecognizeSelector:] ,以便不替换该方法的原始实现,并在项目的PCH中包含该类别的 Headers .

    我个人更喜欢宏版本,因为它允许我尽可能地减少样板 .

    这里是:

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

    如您所见,宏提供了抽象方法的完整实现,将必要的样板量减少到绝对最小值 .

    更好的选择是lobby the Clang team通过功能请求为此案例提供编译器属性 . (更好,因为这也可以为您子类化的情况启用编译时诊断,例如NSIncrementalStore . )

    为什么选择此方法

    • 它可以有效地完成工作,而且有点方便 .

    • 这很容易理解 . (好吧, __builtin_unreachable() 可能会给人们带来惊喜,但也很容易理解 . )

    • 它不能在发布版本中被剥离而不会生成其他编译器警告或错误 - 与基于其中一个断言宏的方法不同 .

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

    一些(大多数?)人在发布版本中删除断言 . (我不同意这种习惯,但这是另一个故事......)未能实现所需的方法 - 但是 - 对于您的程序来说是 badterriblewrongbasically the end of the universe . 你的程序在这方面无法正常工作,因为它是未定义的,未定义的行为是最糟糕的事情 . 因此,能够在不产生新诊断的情况下剥离这些诊断将是完全不可接受的 .

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

  • 19

    使用 @property@dynamic 也可以 . 如果您声明了动态属性,并且如果您尝试访问它,请在运行时收到't give a matching method implementation, everything will still compile without warnings, and you'错误 . 这与调用 [self doesNotRecognizeSelector:_cmd] 基本相同,但键入的次数要少得多 .

  • 1

    在Xcode中(使用clang等)我喜欢使用 __attribute__((unavailable(...))) 标记抽象类,因此如果您尝试使用它,则会收到错误/警告 .

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

    示例

    在基类 @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");
    

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

  • 12

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

    选项1:协议

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

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

    选项2:模板方法模式

    如果你想创建一个像"Template Method Pattern"这样的部分实现的抽象类,那么这就是解决方案 . Objective-C - Template methods pattern?

  • 267

    另一种选择

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

  • 41

    (更多相关建议)

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

    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"并且知道他们不应该调用 [super ..] .

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

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

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

    example of the hint

  • 34

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

    可能这种情况应该只在开发时发生,所以这可能会起作用:

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

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

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

  • 2

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

  • 0

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

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

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

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

  • 6

    通过应用@dotToString 's comment, you actually have the solution adopted by Instagram' s IGListKit来改变@redfood的建议 .

    • 为所有在基础(抽象)类中定义无意义的方法创建协议,即它们需要在子代中具体实现 .

    • 创建一个不实现此协议的基类(抽象)类 . 您可以向此类添加任何其他有意义的方法以实现通用实现 .

    • 项目中的任何地方,如果必须通过某种方法输入或输出 AbstractClass 中的子项,请将其键入 AbstractClass<Protocol> .

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

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

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

  • 60

    实际上,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
    
  • 5

    创建抽象类的简单示例

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

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

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

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

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

相关问题