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.