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?

CodeBender
  • 35,668
  • 12
  • 125
  • 132
Anthony
  • 2,801
  • 3
  • 30
  • 49
  • whats stopping you from writing ur own constructor? – Shanti K Jan 18 '12 at 09:30
  • 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). – Danra Feb 13 '12 at 15:16
  • 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... – Adam Nov 11 '12 at 18:21
  • 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. – fzwo Nov 11 '14 at 07:27

6 Answers6

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.

Community
  • 1
  • 1
fzwo
  • 9,842
  • 3
  • 37
  • 57
  • 2
    Beware, GCC users - this **will not work**, and getting GNUStep to work with clang under non-darwin boxes is a pain. – Qix - MONICA WAS MISTREATED Jan 20 '15 at 09:59
  • 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? – Adam Jan 11 '16 at 22:27
  • 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. – uchuugaka Jan 04 '17 at 08:39
  • Seems in Xcode 8 at least, it is ignored in a private header as well. :( – uchuugaka Jan 04 '17 at 08:41
  • 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? – fzwo Jan 04 '17 at 13:41
  • [This newer answer](https://stackoverflow.com/a/35194550/539599) looks a lot cleaner; are there advantages to your solution? – Raphael Jul 14 '17 at 07:57
  • 1
    @Raphael 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. – fzwo Jul 14 '17 at 12:38
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.

CodeBender
  • 35,668
  • 12
  • 125
  • 132
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...".

Adam
  • 32,900
  • 16
  • 126
  • 153
  • 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](http://stackoverflow.com/a/26859656/534888) below for a solution that gives you compile-time errors. – fzwo Jan 11 '16 at 11:54
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: @""];
}
JeremyP
  • 84,577
  • 15
  • 123
  • 161
  • 1
    And when the initializer of the superclass calls its bare -init method everything will crash? :) – Eiko Jan 18 '12 at 12:18
  • 1
    @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. – JeremyP Jan 18 '12 at 14:31
  • 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. – cesarislaw Mar 07 '12 at 19:32
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.

uranusjr
  • 1,380
  • 12
  • 36
  • Good point about the need to assign to self before returning - I've updated my answer above. – Adam Jan 05 '13 at 20:14
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.

Vladimir Gritsenko
  • 1,669
  • 11
  • 25
  • 1
    Actually, you can. Throw an exception in `-init`. – JeremyP Jan 18 '12 at 11:22
  • @JeremyP 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. – Danra Feb 13 '12 at 15:20
  • @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. – JeremyP Feb 20 '12 at 14:24
  • See my answer below: you CAN do this and it is SAFE if you do it carefully. – Adam Nov 11 '12 at 18:20
  • 1
    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. – CodeBender Feb 05 '16 at 15:31