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 knowNSString
has an immutable variantNSMutableString
. When you assignNSString
toNSMutableString
and change theNSMutableString
, the change will get transferred toNSString
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.
However, if you try to use designated initializer in the UIViewController
subclass, it will throw following 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 withNS_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.