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?
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?
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:
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...".
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.
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.
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:
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...".
-(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: @""];
}
-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 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.
-init
. –
Hump 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 init
s. 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.
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()
}
© 2022 - 2025 — McMap. All rights reserved.