iOS Designated Initializers : Using NS_DESIGNATED_INITIALIZER
The use of NS_DESIGNATED_INITIALIZER
is nicely explained in http://useyourloaf.com/blog/2014/08/19/xcode-6-objective-c-modernization.html:
The designated initializer guarantees the object is fully initialised
by sending an initialization message to the superclass. The
implementation detail becomes important to a user of the class when
they subclass it. The rules for designated initializers in detail:
- A designated initializer must call (via super) a designated
initializer of the superclass. Where NSObject is the superclass this
is just [super init].- Any convenience initializer must call another
initializer in the class - which eventually leads to a designated
initializer.- A class with designated initializers must implement all
of the designated initializers of the superclass.
As an example, if your interface is
@interface MyClass : NSObject
@property(copy, nonatomic) NSString *name;
-(instancetype)initWithName:(NSString *)name NS_DESIGNATED_INITIALIZER;
-(instancetype)init;
@end
then the compiler checks if the (convenience) initializer init
calls
the (designated) initializer initWithName:
, so this would cause a warning:
-(instancetype)init
{
self = [super init];
return self;
}
and this would be OK:
-(instancetype)init
{
self = [self initWithName:@""];
return self;
}
In Swift the rules about designated and convenience initializers are even more strict,
and if you mix Objective-C and Swift code, marking the designated Objective-C initializers helps the compiler to enforce the rules.
For example, this Swift subclass would cause an compiler error:
class SwClass: MyClass {
var foo : String
init(foo : String) {
self.foo = foo
super.init()
}
}
and this would be OK:
class SwClass: MyClass {
var foo : String
init(foo : String) {
self.foo = foo
super.init(name: "")
}
}
How to use NS_DESIGNATED_INITIALIZER and override init?
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
Is it possible to override a designated initializer with a convenience initializer?
This is possible but there are several conditions that must be met in order to get no compiler warnings. ALL designated initialisers for the sub-class must be overridden, also use the NS_DESIGNATED_INITIALIZER macro to mark what init.. methods are to be treated as designated initialisers.
The following is for a sub-class of UITableViewController -
.h
- (instancetype)initWithStyle:(UITableViewStyle)style;// this is no longer a designated initialiser
- (instancetype)init NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER;
.m
- (instancetype)initWithStyle:(UITableViewStyle)style {
//.. no longer treated as designated initialiser
self = [self init];
return self;
}
- (instancetype)init {
if (self = [super initWithStyle:UITableViewStylePlain]) {
//.. this is now treated as designated initialiser
}
return self;
}
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
//.. must implement
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
if (self = [super initWithCoder:aDecoder]) {
//.. must implement
}
return self;
}
Additional info is found in Adopting Modern Objective-C under Object Initialization.
Designated Initializer, clarify please.
In Objective-C, the designated initializer of a class is the method that's responsible for properly initializing the class to put it in an appropriate state for use. There is nothing in the code that marks it as the designated initializer, so there is usually a comment in the header file for that. It is up to the developer who is using or extending that class to figure out which method is the designated initializer (usually init
or prefixed with init
) and to write code accordingly. This can lead to mis-use of a class if it is not properly documented and its source code is not available and is one of the shortcomings that Swift attempts to solve. So to address your questions...
- A designated initializer is not determined by the compiler. A subclass should also have a designated initializer that calls the super's designated initializer at some point.
- Each class should clearly state (via comments or documentation) which initializer is intended to be used as the designated initializer. It is up to the developer who is using the class to make sure that the designated initializer is invoked. It is up to the developer of the class itself to ensure that the super's designated initializer is invoked as intended. However, with properly written classes, any of the init methods should invoke the designated initializer. However, it is not guaranteed by the compiler.
- Other init methods need to be coded appropriately to invoke the designated initializer via
[self init...]
or[super init...]
. Again, it is up to you to figure out how a class is intended to be used and to use it or extend it appropriately.
The designated initializer is the method that does the "heavy lifting" to prepare a new instance of a class for use. Other initializers are known as convenience initializers and are typically used to provide shorter signatures with implied defaults since you can't specify default parameter values for Objective-C method signatures.
In most cases, if a class is properly written, you shouldn't really have to worry too much since all convenience initializers should end up invoking the designated one.
A lot of thought was put into this during the development of Swift and even if you don't intend to learn it, you should read up on Swift Initializers as it will give you good insight into the proper chain of initialization that you could then use to guide you in creating initializers in Objective-C.
UPDATE:
Since Xcode 6, designated initializers can be marked as such, typically via the NS_DESIGNATED_INITIALIZER
macro. This helps enforce properly written classes and is something that was brought back to Objective-C from Swift. Check out iOS Designated Initializers : Using NS_DESIGNATED_INITIALIZER.
Warning: Method override for designated initializer
Calling [super initWithStyle:UITableViewStyleGrouped]
is not overriding the method in the superclass; you're just calling it.
I suspect that iOS 8.3 now shows you these warnings in Objective-C (we have been getting these warnings for a while in Swift).
I would just simply override those methods like this to get rid of the warnings:
- (instancetype)initWithStyle:(UITableViewStyle)style
{
return [super initWithStyle:style];
}
It doesn't look like you need to do anything more in your case.
Update:
Try changing your convenience initializer to this:
- (instancetype)initInManagedObjectContext:(NSManagedObjectContext *)context
withScoreKeeper:(ScoreKeeper *)scorer
withWordList:(WordList *)wordlist {
self = [self initWithStyle:UITableViewStyleGrouped]; // <-- self instead of super
if (self) {
_mObjContext = context;
_scoreKeeper = scorer;
_wordList = wordlist;
}
return self;
}
That method is a convenience initializer and should always call one of the designated initializers in the current class. That one can then call the superview's designated initializer.
How to correct these warnings for Convenience initializer missing a 'self' call to another initializer
You've got two options:
- Remove
NS_DESIGNATED_INITIALIZER
from-initWithCoder:
- Add
NS_DESIGNATED_INITIALIZER
to-init
- Change
-init
to call[self initWithCoder:]
The first two make the most sense; 2 would be rather difficult with no coder.
When would a class ever have more than one designated initializer?
You would do this when you want to have a different initialization for different objects of the same class. One example is class clusters, like NSNumber. It has quite a few initializers for the different types of numbers they can hold. To provide the most accurate representation, the class should hold its value in the same format it received it in, instead of casting. This means the initializers can't simply call a common initializer and return. They need to do some custom work. This makes them a designated initializer.
Another example would be a document class which needs to do some initialization only for new files and some other initialization only for documents being opened. Both of these initializers will call their super implementation, which in turn calls the plain init
method to do common initialization. However, since they do more than simply calling another initializer with a default value, they are considered designated initializers.
Must you override init when you create a new designated initializer?
you can now mark your designated initializer using NS_DESIGNATED_INITIALIZER but I am wondering if that means it forces you to separately, within the same class, first override init with a call to your designated initializer?
Calling any initializer in self will bypass the warning. When I try to call a super.init method I do get the following warning.
Convenience initializer missing a 'self' call to another initializer.
what might the implications be of NOT overriding init explicitly with the call to the designated initializer?
This would mean your object might be missing some important information that it requires to function, or that the app assumes the object always has, and if it doesn't have it, it could cause errors or a crash in your app.
Here is a good explanation of how it works:
http://timekl.com/blog/2014/12/09/objective-cs-designated-secret/
Designated initializer should only invoke a designated initializer on 'super'
As you can read in NSMutableDictionary documentation, there are two designated initializer for this class:
initWithCapacity:
Designated Initializerinit
Designated Initializer
Here you are calling from initWithCapacity in your class to super.init. That's the reason the compiler warns you.
This code maybe is better:
// This is the new method added.
- (instancetype)init
{
return [self initWithCapacity:0];
}
- (instancetype)initWithCapacity:(NSUInteger)capacity
{
self = [super initWithCapacity:capacity];
if (self != nil)
{
// Allocate here.
}
return self;
}
Related Topics
Selector to Get Indexpath Uicollectionview Swift 3.0
Programmatically Select All Text in Uitextfield
Nstimer Not Fired When Uiscrollview Event Occurs
iOS Calendar Access Permission Dialog, Force It to Appear
iOS Different Font Sizes Within Single Size Class for Different Devices
How to Create Otp Verification Screen and Detect Delete Backward on Multiple Uitextfield Is Swift
Check If User Is Logged into Icloud? Swift/Ios
Why Can't I Force Landscape Orientation When Use Uinavigationcontroller
How to Xcodebuild a Static Library with Bitcode Enabled
Add Swipe to Delete Uitableviewcell
Enterprise App Deployment Doesn't Install on iOS 8.1.3
How to Dismiss a Storyboard Popover
Uisearchbar: Clear Background Color or Set Background Image
Cropping Image with Swift and Put It on Center Position
Ios7 Status Bar Hide/Show on Select Controllers
Determine If iOS Device Is 32- or 64-Bit
iOS 7.1 Uitextview Still Not Scrolling to Cursor/Caret After New Line