Categories
programming

ObjectiveC: how to make an abstract class / forbid the “init” method

Abstract classes: saving programmers from each other

For trivial apps, no-one cares. But most libraries take huge advantage of the concept of “subclassing”, and programmers using those libraries need to make intelligent choices about “which subclass do I use?”.

Thanks to auto-complete, or “because it sounded what I needed at that moment in time” – or simply “because I was tired” – your base class gets instantiated when it shouldn’t have been. And strange bugs come from it, wasting everyone’s time. You might argue “not MY time”, but I’m a strong believer in writing code that EITHER does the obvious OR protects the people using it – my code doesn’t crash, it checks for obvious mistakes (e.g. checks that a file exists before loading it!), etc.

In the long run, that frequently comes back to help you: when YOU then re-use your own code, and make a dumb mistake because it’s been a long time and you’d forgotten how to use it.

In some languages, you can create “abstract base classes” that allow other classes to share type, but cannot be used on their own. This makes it obvious to other programmers that they should look for subclasses and pick one – instead of trying to use the superclass.

Unfortunately, ObjectiveC has no support for “abstract classes”.

…or does it?

What’s an abstract class?

An abstract class is one that cannot be instantiated. To achieve that in Objective-C, all you have to do is:

@implementation DontAllowInit

- (id)init
{
    NSAssert(false, @"You cannot init this class directly. Instead, use a 
subclass e.g. AcceptableSubclass");

    // NB: I prefer to use NSAssert because this is aimed at programmers, and 
    //     ObjC programmers should generally be using assertions during dev!
 
    // You could instead use more fancy approaches, like raising an NSException
    //     - but Apple/Cocoa are very anti-exception, and don't support them well.

    return nil;
}
@end

…but this causes a problem. Because as soon as someone creates a subclass, they’ll find their code crashes:

@interface SubClass : DontAllowInit
@end

@implementation SubClass

- (id)init
{
    self = [super init]; // CRASH!
    if( self != nil )
    {
        // all the normal setup code
    }
    return self;
}
@end

You can workaround this by writing documentation that says:

/*
This class can’t be instantiated, because I wanted an Abstract Class, but Objective-C was too primitive to allow it.

So, um, please don’t call [super init]. Instead call … ah .. [super secretInit] which does the same thing, but which other people won’t realise exists!
*/

There’s an obvious problem there … the super-secret-init is easy to call anyway, and BOOM your library. It might seem obvious to you that no-one would call that method without understanding it, but this is the way of the world.

Selective Denial: what am I?

The solution is to think about what happens when you instantiate a subclass. The key thing here is that when you call:

Super* s = [[Super alloc] init];

it’s NOT the same as when you call:

Sub* s = [[Sub alloc] init];

…in the first case, the thing that gets sent “init” is an instance of “Super”, whereas in the second case it’s an instance of “Sub”.

That might not sound interesting, but when Sub executes the first (standards-compliant) line of its init method:

The key thing here is that when you call:

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

…then the code in Super.m is *not* being run on an object of type “Super”, but rather an object of type “Sub”.

And so we have a solution:

@implementation DontAllowInit
- (id)init
{
	if( [self class] == [DontAllowInit class])
	{
		NSAssert(false, @"You cannot init this class directly. Instead, use a subclass e.g. MyPreferredSubclass");
		
		return nil;
	}
	else
		return [super init];
}

Does it really matter?

When writing code, you have lots to think about. In my years of experience, two of the most important questions are:

  1. Does it do what it says / work as intended?
  2. Can someone else use (and modify) the code later, when you’re not there … correctly?

Documentation goes a long way to solving both those issues. However … docs take a long time to write, and more importantly:

Other people frequently don’t read the documentation

More importantly:

If you are a great programmer, other programmers SHOULD NOT NEED TO read the code documentation any more than they expected to

“Expected to” is critical here. If your codebase is 1 million lines long, then a programmer would be insane to think they could just “dive in” and start writing / modifying it – the thing is fantastically complex. But if it’s clear and simple, then often they should expect to read the “core” documentation, and be able to work the rest out as they go, from reading your class and method names.

Abstract classes enable you – with very little effort – to use complex chains of OOP subclassing without endangering the programmers who come after you.

9 replies on “ObjectiveC: how to make an abstract class / forbid the “init” method”

i was very disappointed on my first project with iOS that there wasn’t a clear way to use abstract classes :(, and i was looking for a point of view of an expert to learn how to approach the problem. thanks!

What I was trying to understand is that when you get to NSObject (another abstract class) and do the below;

– (id)init
{
if( [self class] == [NSObject class])
{
NSAssert(false, @”You cannot init this class directly. Instead, use a subclass e.g. MyPreferredSubclass”);

return nil;
}
else
return [super init];
}

Where is “super” for a NSObject ?

NSObject is written and owned by Apple, you can’t edit the source yourself.

So there’s no way for you to do what you describe above

Thats a nice feature. Thanks.

But I wouldnt use it – its not part of the language, and things like this tend to cause problems in the long run (confusing maintainers of the code, 3rd party tools, etc).

I find them best saved for features that are frequenty used (hence: widely known by fellow programmers), or essential for a specific project (because of the problem domain – although then I’d look for an alternatve language i possible, to a oid the issue).

Hi,
I think I miss something and It confusing me.
So if I do: Sub* s = [[Sub alloc] init];
It should call the init DontAllow init and then move to the second part.
What confuse me is return [super init];
As far as I understand it should call the init method of the self (which is our child) so it should call the same init method again (infinite loop) but it doesn’t. So actually super doesn’t “linked” to self but either to the class of where it called from???

In init, you must call [super init], this is an absolute requirement in ObjectiveC.

User calls Sub.init, Sub.init calls Super.init (allowed), Super.init calls … well, who knows? Depends on your class. But if Super is a base class, it’ll be calling NSObject.init (which is required).

Comments are closed.