how to forbid the basic init method in a NSObject
Asked Answered
O

7

17

I want to force user to use my own init method (for example -(id)initWithString:(NSString*)foo;) and not the basic [[myObject alloc]init];.

how can I do that?

Obstreperous answered 18/1, 2012 at 9:25 Comment(4)
whats stopping you from writing ur own constructor?Ywis
This is a great question. As the below answers point out you can't really do that - The way I see it, since all classes are supposed to subclass NSObject which defines the -init method, and since subclassing is always supposed to support the interface they inherit - Apple is forcing you to make -init a valid initializer, or bend this basic OOP law (by throwing an exception or such).Drawback
FYI: I've needed this technique a few times when writing complex Libraries for other developers, where it's not safe to assume that they read the docs carefully - or they accidentally auto-completed the wrong class. But I agree it's ALMOST ALWAYS the wrong approach when writing plain apps...Eelworm
A lot has changed since you asked the question. I deleted my original (slightly embarrassing) answer and wrote a new one on how to do this properly using LLVM compiler directives. Please take a look.Fallow
E
9

The accepted answer is incorrect - you CAN do this, and it's very easy, you just have to be a bit explicit. Here's an example:

You have a class named "DontAllowInit" which you want to prevent people init'ing:

@implementation DontAllowInit

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

        self = nil; // as per @uranusjr's answer, should assign to self before returning
    }
    else
        self = [super init];

    return nil;
}

Explanation:

  1. When you call [super init], the class that was alloc'd was the SUBCLASS.
  2. "self" is the instance - i.e. the thing that was init'd
  3. "[self class]" is the class that was instantiated - which will be SUBCLASS when the SUBCLASS is calling [super init], or will be the SUPERCLASS when the SUPERCLASS is being called with plain [[SuperClass alloc] init]
  4. So, when the superclass receives an "init" call, it just needs to check whether the alloc'd class is the same as its own class

Works perfectly. NB: I don't recommend this technique for "normal apps" because usually you INSTEAD want to use a Protocol.

HOWEVER ... when writing Libraries ... this technique is VERY valuable: you frequently want to "save (other developers) from themselves", and its easy to NSAssert and tell them "Oops! you tried to alloc/init the wrong class! Try class X instead...".

Eelworm answered 11/11, 2012 at 18:19 Comment(1)
This answer is outdated. It's not wrong, but it's incomplete and still dangerous, because it can't be statically checked. See my newer answer below for a solution that gives you compile-time errors.Fallow
F
20

All other answers here are outdated. There is a way to do this properly now!

While it is easy to just crash at runtime when somebody calls your method, compile-time checking would be far preferable.

Fortunately, this has been possible in Objective-C for a while.

Using LLVM, you can declare any method as unavailable in a class like so

- (void)aMethod __attribute__((unavailable("This method is not available")));

This will make the compiler complain when trying to call aMethod. Great!

Since - (id)init is just an ordinary method, you can prohibit calling of the default (or any other) initializer in this way.

Note, though, that this will not insure against the method being called using the dynamic aspects of the language, for instance via [object performSelector:@selector(aMethod)] etc. In the case of init, you won't even get a warning, because the init method is defined in other classes, and the compiler doesn't know enough to give you an undeclared selector warning.

So, to ensure against this, make sure that the init method crashes when being called (see Adam's answer).

If you want to disallow - (id)init in a framework, make sure to also disallow + (id)new, as this will just forward to init.

Javi Soto has written a small macro to forbid using the designated initializer faster and easier and to give nicer messages. You can find it here.

Fallow answered 11/11, 2014 at 7:24 Comment(7)
Beware, GCC users - this will not work, and getting GNUStep to work with clang under non-darwin boxes is a pain.Koa
Looking at some recent open source projects, I saw use of an NS ... init disallowed ... macro - I assumed that's an official Apple one, introduced in iOS 9 maybe?Eelworm
You also need to put this in a header AFICT. Seems to be ignored in a .m file class extension or in the @implmentation. Not sure about private headers.Rugose
Seems in Xcode 8 at least, it is ignored in a private header as well. :(Rugose
If that is indeed the case, please file a bug report with Apple. If you also copy it to openradar, why not share a link here in the comments so people can duplicate?Fallow
This newer answer looks a lot cleaner; are there advantages to your solution?Premeditate
@Premeditate That other answer is essentially the same with a little nicer syntax that wasn't available when I wrote mine, and missing the additional info about new and runtime checking. I would use that newer syntax now and to be really sure still use the checks outlined here, especially when making frameworks for others to use.Fallow
A
18

tl; dr

Swift:

private init() {}

Since all Swift classes include an internal init by default, you can change it to private to keep other classes from calling it.

Objective C:

Put this in your class's .h file.

- (instancetype)init NS_UNAVAILABLE;

This relies on an OS define that prevents the method named from being called.

Aroid answered 4/2, 2016 at 7:3 Comment(0)
E
9

The accepted answer is incorrect - you CAN do this, and it's very easy, you just have to be a bit explicit. Here's an example:

You have a class named "DontAllowInit" which you want to prevent people init'ing:

@implementation DontAllowInit

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

        self = nil; // as per @uranusjr's answer, should assign to self before returning
    }
    else
        self = [super init];

    return nil;
}

Explanation:

  1. When you call [super init], the class that was alloc'd was the SUBCLASS.
  2. "self" is the instance - i.e. the thing that was init'd
  3. "[self class]" is the class that was instantiated - which will be SUBCLASS when the SUBCLASS is calling [super init], or will be the SUPERCLASS when the SUPERCLASS is being called with plain [[SuperClass alloc] init]
  4. So, when the superclass receives an "init" call, it just needs to check whether the alloc'd class is the same as its own class

Works perfectly. NB: I don't recommend this technique for "normal apps" because usually you INSTEAD want to use a Protocol.

HOWEVER ... when writing Libraries ... this technique is VERY valuable: you frequently want to "save (other developers) from themselves", and its easy to NSAssert and tell them "Oops! you tried to alloc/init the wrong class! Try class X instead...".

Eelworm answered 11/11, 2012 at 18:19 Comment(1)
This answer is outdated. It's not wrong, but it's incomplete and still dangerous, because it can't be statically checked. See my newer answer below for a solution that gives you compile-time errors.Fallow
H
3
-(id) init
{
    @throw [NSException exceptionWithName: @"MyExceptionName" 
                                   reason: @"-init is not allowed, use -initWithString: instead"
                                 userInfo: nil];
}

-(id) initWithString: (NSString*) foo
{
    self = [super init];  // OK because it calls NSObject's init, not yours
    // etc

Throwing the exception is justified if you document that -init is not allowed and therefore using it is a programmer error. However, a better answer would be to make -init invoke -initWtihString: with some suitable default value i.e.

-(id) init
{
    return [self initWithString: @""];
}
Hump answered 18/1, 2012 at 11:29 Comment(3)
And when the initializer of the superclass calls its bare -init method everything will crash? :)Discreditable
@Eiko: No because we only call the -init of the superclass through [super init] from our designated initialiser. Any other initialisers of the super class that are considered legal in the subclass must be overridden to call our designated initialiser instead.Hump
Throwing an exception in the init seems to break unit testing with OCUnit. I'm getting this exception when building my tests. "Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'objc_getClassList returned more classes than it should have.'". I'm thinking of having the init return nil since that's what failed inits do anyways.Feer
W
0

Short answer: you can't.

Longer answer: the best practice is to set your most detailed initializer as the designated initializer, as described here. 'init' will then call that initializer with sane, default values.

Another option is to 'assert(0)' or crash in another way inside the 'init', but this isn't a good solution.

Wenona answered 18/1, 2012 at 9:51 Comment(5)
Actually, you can. Throw an exception in -init.Hump
@Hump Thus breaking your superclass's interface? If class B inherits A, then instances of B are also expected to be valid instances of A - and thus conform to A's interface.Drawback
@Danra: In theory, you are correct. It's much better to override -init to call your designated initialiser. In practice, it doesn't matter much because the -initWhatever: method is only called once immediately after allocation.Hump
See my answer below: you CAN do this and it is SAFE if you do it carefully.Eelworm
The answer I posted shows how you can safely do this in one line using an Apple provided define statement. Works with any method as well.Aroid
S
0

I actually voted up Adam's answer, but would like to add some things to it.

First, it is strongly encouraged (as seem in auto-generated init methods in NSObject subclasses) that you check self against nil in inits. Also, I don't think class objects are guaranteed to be "equal" as in ==. I do this more like

- (id)init
{
    NSAssert(NO, @"You are doing it wrong.");
    self = [super init];
    if ([self isKindOfClass:[InitNotAllowedClass class]])
        self = nil;
    return self;
}

Note that I use isKindOfClass: instead because IMHO if this class disallows init, it should disallow its descendants to have it as well. If one of its subclass want it back (which doesn't make sense for me), it should override it explicitly by calling my designated initializer.

But more importantly, whether you take the above approach or not, you should always have appropriate documentation. You should always clearly state which method is your designated initializer, try as best as you can to remind others not to use inappropriate initializers in documentation, and put some faith in other users/developers, instead of trying to "save everybody else's asses" with clever codes.

Spoliation answered 26/11, 2012 at 15:47 Comment(1)
Good point about the need to assign to self before returning - I've updated my answer above.Eelworm
A
0

For Swift 5, using @available(*, unavailable, message: "This method is not available") is a way but in this approach you can't use the instance method inside the class. I suggest using private keyword for inaccesible initializers.

For a class that inherits from NSObject:

  private override init() {
        super.init()
    }
Affairs answered 6/8, 2024 at 11:49 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.