iOS Designated Initializers:Using Ns_Designated_Initializer

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...

  1. 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.
  2. 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.
  3. 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:

  1. Remove NS_DESIGNATED_INITIALIZER from -initWithCoder:
  2. Add NS_DESIGNATED_INITIALIZER to -init
  3. 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 Initializer
  • init 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



Leave a reply



Submit