Creating an abstract class in Objective-C

前端 未结 21 2483
感情败类
感情败类 2020-11-22 15:44

I\'m originally a Java programmer who now works with Objective-C. I\'d like to create an abstract class, but that doesn\'t appear to be possible in Objective-C. Is this poss

相关标签:
21条回答
  • 2020-11-22 16:06

    This thread is kind of old, and most of what I want to share is already here.

    However, my favorite method is not mentioned, and AFAIK there’s no native support in the current Clang, so here I go…

    First, and foremost (as others have pointed out already) abstract classes are something very uncommon in Objective-C — we usually use composition (sometimes through delegation) instead. This is probably the reason why such a feature doesn’t already exist in the language/compiler — apart from @dynamic properties, which IIRC have been added in ObjC 2.0 accompanying the introduction of CoreData.

    But given that (after careful assessment of your situation!) you have come to the conclusion that delegation (or composition in general) isn’t well suited to solving your problem, here’s how I do it:

    1. Implement every abstract method in the base class.
    2. Make that implementation [self doesNotRecognizeSelector:_cmd];
    3. …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”.
    4. 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.

    I personally prefer the macro version as that allows me to reduce the boilerplate as much as possible.

    Here it is:

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

    As you can see, the macro provides the full implementation of the abstract methods, reducing the necessary amount of boilerplate to an absolute minimum.

    An even better option would be to lobby the Clang team to providing a compiler attribute for this case, via feature requests. (Better, because this would also enable compile-time diagnostics for those scenarios where you subclass e.g. NSIncrementalStore.)

    Why I Choose This Method

    1. It get’s the job done efficiently, and somewhat conveniently.
    2. It’s fairly easy to understand. (Okay, that __builtin_unreachable() may surprise people, but it’s easy enough to understand, too.)
    3. It cannot be stripped in release builds without generating other compiler warnings, or errors — unlike an approach that’s based on one of the assertion macros.

    That last point needs some explanation, I guess:

    Some (most?) people strip assertions in release builds. (I disagree with that habit, but that’s another story…) Failing to implement a required method — however — is bad, terrible, wrong, and basically the end of the universe for your program. Your program cannot work correctly in this regard because it is undefined, and undefined behavior is the worst thing ever. Hence, being able to strip those diagnostics without generating new diagnostics would be completely unacceptable.

    It’s bad enough that you cannot obtain proper compile-time diagnostics for such programmer errors, and have to resort to at-run-time discovery for these, but if you can plaster over it in release builds, why try having an abstract class in the first place?

    0 讨论(0)
  • 2020-11-22 16:06

    In Xcode (using clang etc) I like to use __attribute__((unavailable(...))) to tag the abstract classes so you get an error/warning if you try and use it.

    It provides some protection against accidentally using the method.

    Example

    In the base class @interface tag the "abstract" methods:

    - (void)myAbstractMethod:(id)param1 __attribute__((unavailable("You should always override this")));
    

    Taking this one-step further, I create a macro:

    #define UnavailableMacro(msg) __attribute__((unavailable(msg)))
    

    This lets you do this:

    - (void)myAbstractMethod:(id)param1 UnavailableMacro(@"You should always override this");
    

    Like I said, this is not real compiler protection but it's about as good as your going to get in a language that doesn't support abstract methods.

    0 讨论(0)
  • 2020-11-22 16:07

    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)];
    

    If your method returns a value, it's a bit easier to use

    @throw [NSException exceptionWithName:NSInternalInconsistencyException
                                   reason:[NSString stringWithFormat:@"You must override %@ in a subclass", NSStringFromSelector(_cmd)]
                                 userInfo:nil];
    

    as then you don't need to add a return statement from the method.

    If the abstract class is really an interface (i.e. has no concrete method implementations), using an Objective-C protocol is the more appropriate option.

    0 讨论(0)
  • 2020-11-22 16:08

    (more of a related suggestion)

    I wanted to have a way of letting the programmer know "do not call from child" and to override completely (in my case still offer some default functionality on behalf of the parent when not extended):

    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
    

    The advantage is that the programmer will SEE the "override" in the declaration and will know they should not be calling [super ..].

    Granted, it is ugly having to define individual return types for this, but it serves as a good enough visual hint and you can easily not use the "override_" part in a subclass definition.

    Of course a class can still have a default implementation when an extension is optional. But like the other answers say, implement a run-time exception when appropriate, like for abstract (virtual) classes.

    It would be nice to have built in compiler hints like this one, even hints for when it is best to pre/post call the super's implement, instead of having to dig through comments/documentation or... assume.

    example of the hint

    0 讨论(0)
  • 2020-11-22 16:09

    Can't you just create a delegate?

    A delegate is like an abstract base class in the sense that you say what functions need to be defined, but you don't actually define them.

    Then whenever you implement your delegate (i.e abstract class) you are warned by the compiler of what optional and mandatory functions you need to define behavior for.

    This sounds like an abstract base class to me.

    0 讨论(0)
  • 2020-11-22 16:12

    From the Omni Group mailing list:

    Objective-C doesn't have the abstract compiler construct like Java at this time.

    So all you do is define the abstract class as any other normal class and implement methods stubs for the abstract methods that either are empty or report non-support for selector. For example...

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

    I also do the following to prevent the initialization of the abstract class via the default initializer.

    - (id)init
    {
         [self doesNotRecognizeSelector:_cmd];
         [self release];
         return nil;
    }
    
    0 讨论(0)
提交回复
热议问题