Using NS_DESIGNATED_INITIALIZER with Xcode6

One day I was working on one of my side projects. I came to know about NS_DESIGNATED_INITIALIZER Apple introduced in Xcode6. When you have choice of multiple initializers and still you want to init a model with specific initializer, you can use this keyword NS_DESIGNATED_INITIALIZER which will emit the warning if that specific initializer is not used to init the model.

For example, I have following model to store person information,



@interface JKPersonInfoViewController : UIViewController

@property (nonatomic, copy) NSString* firstName;
@property (nonatomic, copy) NSString* middleName;
@property (nonatomic, copy) NSString* lastName;
@property (nonatomic, copy) NSString* country;

- (instancetype)initWithDictionary:(NSDictionary*)personInfo NS_DESIGNATED_INITIALIZER;

@end

Please note the use of keyword copy. This is used to avoid problems arising due to state transitions. As you all know NSString has an immutable variant NSMutableString. When you assign NSString to NSMutableString and change the NSMutableString, the change will get transferred to NSString which is undesired side effect

Now every time you want to initialize JKPersonInfo, you will have to use designated initializer - (instancetype)initWithDictionary:(NSDictionary*)personInfo;

However, there is still a possibility that external user might still use init instead of using designated initializer. The trick here is to use NS_UNAVAILABLE which will prevent user from using default initializer.

- (instancetype)init NS_UNAVAILABLE;

Now, if you try to use default initializer, Xcode will throw a nice compiler error.

compiler_error

However, if you try to use designated initializer in the UIViewController subclass, it will throw following warnings.

designated_initializer_warnings

There are two approaches you can use to fix these warnings.

  • Use macro NS_UNAVAILABLE for rest of the superclass designated initializer

This will make all but your designated initializer unavailable to use. This will keep Xcode from forcing you to implement those methods.



- (instancetype)initWithDictionary:(NSDictionary*)personInfo NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_UNAVAILABLE;
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil NS_UNAVAILABLE;

  • Implement superclass designated initializers in the UIViewController subclass as follows. You will not be using these designated superclass initializers, but unless you mark them with NS_UNAVAILABLE you will have to implement them in subclass


- (instancetype)initWithDictionary:(NSDictionary *)personInfo {
    self = [super initWithNibName:nil bundle:nil];
    if (!self) { return nil; }
    return self;
}

- (instancetype)init {
    return [self initWithDictionary:@{}];
}

- (instancetype)initWithStyle:(UITableViewStyle)style {
    return [self initWithDictionary:@{}];
}

- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    return [self initWithDictionary:@{}];
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    return [self initWithDictionary:@{}];
}

Hope this helps someone who is struggling with these warnings. I will be more than happy to hear from you what you think about this approach. Meanwhile if you have any suggestions/critic/comments about it, do let me know.