简体   繁体   中英

Preventing subclasses overriding methods

Assume there is an object that initialises like so

- (void)doInit
{
    NSLog(@"In BaseClass init");
}

- (id)init
{
    self = [super init];
    [self doInit];

    return self;
}

and it has a subclass which is inited in a similar way

- (void)doInit
{
    NSLog (@"In SubClass init");
}

- (id)init
{
    self = [super init];
    [self doInit];

    return self;
}

Now if I create an instance of child class then I receive the following output:

In SubClass init
In SubClass init

when really, what I meant to happen is

In BaseClass init
In SubClass init

Is there a way to mark doInit to say that it shouldn't be overridden or do I need to create a unique name for all methods in a subclass?

I'm not entirely sure how I haven't come across this issue before, but there you go.

Edit:

I understand why this is happening, I hadn't expected that the base class would be able to call the overridden function.

I also can't just call [super doInit]; from the Subclass method because the BaseClass still needs to call doInit so that creating an instance of BaseClass will still work. If I called [super doInit], I'd still end up getting SubClass's doInit called twice.

It appears the answer is no and I'll just have to uniquely name each doInit like doBaseClassInit and doSubClassInit.

If you have a method that you don't want to by dynamically bound (ie don't want a subclass method to be called if it exists), you need to do it as a C function instead. So, you could do this instead:

In the base class:

static void DoInit(BaseClass *self)
{
    NSLog(@"In BaseClass init");
}

- (id)init
{
    self = [super init];
    if (self) {
        DoInit(self);
    }
    return self;
}

in the subclass:

static void DoInit(SubClass *self)
{
    NSLog(@"In SubClass init");
}

- (id)init
{
    self = [super init];
    if (self) {
        DoInit(self);
    }
    return self;
}

Note that both the DoInit methods are marked as static , so they are only visible each compilation unit (.m file) and don't conflict with each other.

You could, perhaps, try something like this in your base class. It would mean any time the init implementation inside BaseClass executed, the doInit implementation for BaseClass would be called.

- (void)doInit
{
    NSLog(@"In BaseClass init");
}

- (id)init
{
    self = [super init];

    Class baseClass = [BaseClass class];
    SEL selector = @selector(doInit);
    IMP baseClassImplementation = class_getInstanceMethod(baseClass, selector);
    baseClassImplementation(self, selector);

    return self;
}

As I mentioned in my comment, if that's the narrowness of your need this should work as it gets around the dynamic method lookup involved with sending a message. Hope this helps!

EDIT:

Disclaimer - if you're in this situation it's probably not a good sign for the longevity of your design. This technique will get you up and running for now but please document it carefully, and consider ways to refactor your code so this is no longer used. Consider fixes like these to really be used only when extremely urgent.

The reason why you are not getting the "In BaseClass init" console message is because your subclass is not calling the super's implementation of doInit.

If you don't want doInit overridden the 'best' way to avoid doing so is to not publish the existence of this method. Remove it from your header and uniquely name the method so that a collision is unlikely. For example, many of the private methods in Apple's frameworks have a leading underscore. So, for example, you could call your method _doInit and it will be very unlikely that a subclass accidentally create it's own overiding implementation.

Nope, there's no enforceable way to prevent a subclass from overriding a method. The best you can do is to avoid putting it in the public header file for the class so someone is not likely to try to override it. If the method has to be public, you just put a note in the documentation for the method that it shouldn't be overridden or that subclasses should call super's implementation whichever the case may be. You'll find these kind of instructions all over in the documentation for Apple's own classes.

If you want your subclass to use the baseclass version of doInit then in the subclass don't declare a doInit method. Here's what I mean:

ClassA:

@interface ClassA : 

-(void) doInit;
-(id) init;

@implementation

-(void) doInit {
    NSLog(@"ClassA doInit");
}

-(id) init {
    self = [super init];

    if (self != NULL)
        [self doInit];

    return self;
}

ClassB

@interface ClassB : ClassA

-(id) init;

@implementation

-(id) init {
    self = [super init];

    if (self != NULL)
        [self doInit];

    return self;
}

And really you don't need to override the init method as well unless there's some special code that you want that class to do.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM