代码之家  ›  专栏  ›  技术社区  ›  Jonathan Arbogast

在Objective-C中创建抽象类

  •  498
  • Jonathan Arbogast  · 技术社区  · 17 年前

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

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

    21 回复  |  直到 9 年前
        1
  •  625
  •   mblackwell8    14 年前

    通常情况下,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];
    

    因此,您不需要从方法中添加返回语句。

    如果抽象类实际上是一个接口(即没有具体的方法实现),使用Objective-C协议是更合适的选择。

        2
  •  267
  •   Peter Mortensen Pieter Jan Bonestroo    9 年前

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

    您可以通过使方法/选择器调用doesNoteCongnizeSelector来模拟抽象类,从而引发异常,使类不可用。

    例如:

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

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

        3
  •  60
  •   Dan Rosenstark    14 年前

    只需快速浏览一下@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 文档)。

        4
  •  41
  •   Peter Mortensen Pieter Jan Bonestroo    9 年前

    我提出的解决方案是:

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

    通过这种方式,编译器会对协议中任何未由子类实现的方法发出警告。

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

        5
  •  34
  •   Peter Mortensen Pieter Jan Bonestroo    9 年前

    Omni Group mailing list :

    Objy-C不具有像Java那样的抽象编译器构造。 这次。

    所以你所要做的就是将抽象类定义为任何其他普通类 并为抽象方法实现方法存根 空或报告不支持选择器。例如。。。

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

    我还执行以下操作以防止对摘要的初始化 通过默认初始值设定项初始化。

    - (id)init
    {
         [self doesNotRecognizeSelector:_cmd];
         [self release];
         return nil;
    }
    
        6
  •  21
  •   Barry Wark    17 年前

    不要试图创建一个抽象基类,而是考虑使用一个协议(类似于Java接口)。这允许您定义一组方法,然后接受所有符合协议的对象并实现这些方法。例如,我可以定义一个操作协议,然后具有如下功能:

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

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

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

        7
  •  19
  •   danyowdee    13 年前

    这条线有点旧,我想分享的大部分已经在这里了。

    然而,我最喜欢的方法没有被提及,而且在当前的clang中,afaik没有本地支持,所以我来这里讨论一下。

    首先,也是最重要的(正如其他人已经指出的那样),抽象类在目标中是非常罕见的——我们通常使用组合(有时通过委托)来代替。这可能就是为什么除了 @dynamic 属性,在引入coredata的objc 2.0中添加了IIRC。

    但考虑到这一点(在仔细评估了你的处境之后!)您得出的结论是,代表团(或一般组成)不太适合解决您的问题,这里是 做到这一点:

    1. 实现基类中的每个抽象方法。
    2. 实现 [self doesNotRecognizeSelector:_cmd]; 艾斯
    3. _ __builtin_unreachable(); 若要使非void方法的警告静音,告诉您__control已到达非void函数的末尾,但没有返回_。
    4. 结合步骤2。3。在宏中,或注释 -[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
    

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

    一个更好的选择是 lobby the Clang team 通过功能请求为这种情况提供编译器属性。(更好的方法是,因为这样还可以为那些子类(如nsincrementalStore)的场景启用编译时诊断。)

    我为什么选择这个方法

    1. 它使工作高效、方便地完成。
    2. 这很容易理解。好吧 __builtin_unreachable() 可能会让人惊讶,但也很容易理解。)
    3. 与基于断言宏的方法不同,它不能在没有生成其他编译器警告或错误的情况下在发布版本中被剥离。

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

    一些(大多数?)人们在发布版本中去掉断言。(我不同意这个习惯,但那是另一个故事,没有实现一个必要的方法,但是 坏的 , 可怕的 , 错误的 基本上是宇宙的尽头 为你的计划。在这方面,您的程序无法正常工作,因为它是未定义的,而未定义的行为是有史以来最糟糕的事情。因此,能够在不生成新诊断的情况下删除这些诊断将是完全不可接受的。

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

        8
  •  12
  •   Cameron Spickert    15 年前

    使用 @property @dynamic 也可以工作。如果您声明了一个动态属性,并且没有给出匹配的方法实现,那么编译时仍然没有警告,并且您将得到一个 unrecognized selector 如果尝试访问,则在运行时出错。这基本上与呼叫相同 [self doesNotRecognizeSelector:_cmd] 但打字的次数要少得多。

        9
  •  7
  •   bioffe John Petrone    13 年前

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

    正如我所说,这不是真正的编译器保护,但它和您使用不支持抽象方法的语言一样好。

        10
  •  7
  •   Community Mohan Dere    9 年前

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

    选项1:协议

    如果要创建不带实现的抽象类,请使用“协议”。继承协议的类必须实现协议中的方法。

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

    选项2:模板方法模式

    如果您想创建一个抽象类,并使用“模板方法模式”之类的部分实现,那么这就是解决方案。 Objective-C - Template methods pattern?

        11
  •  6
  •   bigkm    12 年前

    另一种选择

    只要检查抽象类中的类,并断言或异常,不管您喜欢什么。

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

    这样就不必重写 init

        12
  •  5
  •   Ivan Dossev    14 年前

    (更多相关建议)

    我希望有一种方法让程序员知道“不要从孩子那里调用”,并完全重写(在我的情况下,在不扩展时仍然代表家长提供一些默认功能):

    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_uuu”部分。

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

    最好是有这样的内置编译器提示,甚至提示什么时候最好调用super的实现,而不是必须挖掘注释/文档或…假定。

    example of the hint

        13
  •  4
  •   Peter Mortensen Pieter Jan Bonestroo    9 年前

    如果您习惯于编译器捕获其他语言中的抽象实例化冲突,那么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
    
        14
  •  3
  •   juanjo    13 年前

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

    - (id)myMethodWithVar:(id)var {
       NSAssert(NO, @"You most override myMethodWithVar:");
       return nil;
    }
    
        15
  •  3
  •   Peter Mortensen Pieter Jan Bonestroo    9 年前

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

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

        16
  •  2
  •   Peter Mortensen Pieter Jan Bonestroo    9 年前

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

        17
  •  1
  •   The_Androctonus    11 年前

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

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

    每当您调用该类的init时,这将在编译时生成一个错误。然后,我将类方法用于其他所有内容。

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

        18
  •  0
  •   Gobe    9 年前

    通过应用@dottoString的评论,稍微改变一下@redfood的建议,您实际上已经得到了Instagram的解决方案。 IGListKit .

    1. 为所有在基(抽象)类中定义没有意义的方法创建一个协议,即它们需要在子类中进行特定的实现。
    2. 创建一个基(抽象)类 实现这个协议。您可以向这个类中添加任何其他有意义的实现方法。
    3. 如果你的孩子来自 AbstractClass 必须是某个方法的输入或输出,键入为 AbstractClass<Protocol> 相反。

    因为 抽象类 不实现 Protocol 唯一的方法是 抽象类<协议> 实例是通过子类化实现的。AS 抽象类 单独一个不能在项目中的任何地方使用,它变得抽象。

    当然,这并不能阻止未经授权的开发人员添加简单引用 抽象类 最后会允许抽象类的一个实例。

    现实世界示例: IListKIT 有一个基类 IGListSectionController 它不能实现协议 IGListSectionType 但是,每个需要该类实例的方法实际上都要求该类型 IGListSectionController<IGListSectionType> . 因此,无法使用类型为的对象 iglistcontroller(iglistcontroller) 在他们的框架中有用的任何东西。

        19
  •  0
  •   Peter Mortensen Pieter Jan Bonestroo    9 年前

    实际上,objective-c没有抽象类,但是可以使用 协议 达到同样的效果。这是样品:

    自定义协议.h

    #import <Foundation/Foundation.h>
    
    @protocol CustomProtocol <NSObject>
    @required
    - (void)methodA;
    @optional
    - (void)methodB;
    @end
    

    测试协议

    #import <Foundation/Foundation.h>
    #import "CustomProtocol.h"
    
    @interface TestProtocol : NSObject <CustomProtocol>
    
    @end
    

    测试协议

    #import "TestProtocol.h"
    
    @implementation TestProtocol
    
    - (void)methodA
    {
      NSLog(@"methodA...");
    }
    
    - (void)methodB
    {
      NSLog(@"methodB...");
    }
    @end
    
        20
  •  0
  •   Say2Manuj    9 年前

    创建抽象类的简单示例

    // 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
  •  -3
  •   user1709076    12 年前

    你不能创建一个代理吗?

    委托就像一个抽象的基类,从某种意义上说,你需要定义什么函数,但实际上你没有定义它们。

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

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