Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use NS_DESIGNATED_INITIALIZER and override init?

Okay, this is admittedly a bit of a best practice question, but I want to get it right, so hopefully someone can enlighten me:

Scenario is pretty standard, but has one twist: I have a class in a framework I write that inherits directly from NSObject. It has a designated initializer with quite a few arguments, most of which are nonnull. Since the thing is part of a framework, I explicitly use the NS_DESIGNATED_INITIALIZER macro (which I don't always do in smaller, personal apps). The problem is that this leads to XCode warning me to also override init, i.e. the superclass's designated initializer. But additionally it demands that I call my designated initalizer from it, which I can't do, because I simply lack meaningful defaults for its arguments. I don't really want to throw an exception in the "small" init, I'd much rather like to return nil.

To get rid of the warning, I added init as a second designated initalizer in my class's extension like so:

@interface MyClassName ()
// some other stuff not relevant`

-(nullable instancetype)init NS_DESIGNATED_INITIALIZER;

@end

Now I can safely just return nil; in the overriden init method like I wanted. That means my documentation (I'm using appledoc) and by extension XCode's code completion won't tell someone using my framework that init is actually also a designated initializer (so they won't accidentally use it), but it is still there (in unit tests, for example, this could come in handy).

My question is: Are there any dangers to this besides from someone actually using it in production, happily sending messages to nil afterwards without realizing? Would this be one of the few cases where throwing an exception in init would be preferable?

like image 626
Gero Avatar asked Apr 12 '16 13:04

Gero


1 Answers

Rather than just returning nil from init (and maybe adding a comment saying you shouldn't call it) – you should mark it as unavailable.

Not only will this dismiss the warning about you not overriding NSObject's designated initialiser – it will also generate a compile-time error if anyone tries to call init instead of your designated initialiser.

To do this, you can either use the NS_UNAVAILABLE macro, or use the unavailable __attribute__ as shown by this answer. The advantage of using the __attribute__ is you can specify a message that the compiler will present to the user.

For example:

@interface Foo : NSObject

-(instancetype) init __attribute__((unavailable("You cannot create a foo instance through init - please use initWithBar:")));

-(instancetype) initWithBar:(Bar*)bar NS_DESIGNATED_INITIALIZER;

@end

...


Foo* fooA = [[Foo alloc] init]; // ERROR: 'init' is unavailable: You cannot create a foo instance through init - please use initWithBar:

Foo* fooB = [[Foo alloc] initWithBar:[[Bar alloc] init]]; // No error
like image 165
Hamish Avatar answered Oct 02 '22 13:10

Hamish