I programmatically create several tables and the code has worked fine for years. It did not generate any warnings two weeks ago when I last ran it. I’ve since updated to Xcode 8.3 and 10.10.3 and now get three warnings for each UITableViewController.
Method override for the designated initializer of the superclass '-initWithStyle:' not found.
Method override for the designated initializer of the superclass '-initWithCoder:' not found.
Method override for the designated initializer of the superclass '-initWithNibName:bundle:' not found.
The code to initialize the table is similar for all of my tables:
- (instancetype)initInManagedObjectContext:(NSManagedObjectContext *)context
withScoreKeeper:(ScoreKeeper *)scorer
withWordList:(WordList *)wordlist {
self = [super initWithStyle:UITableViewStyleGrouped];
if (self) {
_mObjContext = context;
_scoreKeeper = scorer;
_wordList = wordlist;
}
return self;
}
and the .h looks like this:
@interface SettingsTableViewController : UITableViewController {
UIPopoverController *popover;
}
- (instancetype)initInManagedObjectContext:(NSManagedObjectContext *)context
withScoreKeeper:(ScoreKeeper *)scorer
withWordList:(WordList *)wordlist NS_DESIGNATED_INITIALIZER;
Here’s what I found out. I am not a programmer, so parts could be mistaken.
When I opened my project for the first time with Xcode 5 and Xcode 6, I was prompted to “Convert to Modern Objective-C Syntax”. (The latest version of Xcode puts it under Edit > Convert > Convert to Modern Objective-C Syntax.) I allowed Xcode to convert everything and understood most of the changes. I understood how NS_DESIGNATED_INITIALIZER works but not why it works. Since I had no compiler errors and everything worked as before, I promptly forgot about it. In the latest version of Xcode, they appear to have updated the compiler and that’s what triggered my warning message.
From Apple’s notes: Adopting Modern Objective C
Using this macro introduces a few restrictions:
The implementation of a designated initializer must chain to a superclass init method (with [super init…]) that is a designated initializer for the superclass.
The implementation of a convenience initializer (an initializer not marked as a designated initializer within a class that has at least one initializer marked as a designated initializer) must delegate to another initializer (with [self init…]).
If a class provides one or more designated initializers, it must implement all of the designated initializers of its superclass.
Here’s what I think happened. First, I got three warning messages because UITableViewController has three designated initializers.
- (instancetype)initWithStyle:(UITableViewStyle)style NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER;
I violated the third restriction because I did not implement all of the designated initializers of the superclass.
Removing the NS_DESIGNATED_INITIALIZER macro from the .h files made the warnings go away.
The question then arises, Do I care that these classes have no designated initializers? Probably not.
First, there are no other initializers in these classes, so I won’t accidentally call the wrong one. Second, I’m not a programmer by training, so when I started writing apps, I used the procedural programming style that I was used to. Until recently, I had never subclassed a class. So I won’t be subclassing this one and there won’t be any subclasses to worry about. Now that I know a bit more about Objective C, it turns out that every class I wrote was a subclass of one of iOS’s classes and that actually explains a bit about why I was getting the errors.
I did not realize that I could create the table view object by calling a method on its superclass. For example, this call works:
SettingsTableViewController *stvc = [[SettingsTableViewController alloc] initWithStyle: UITableViewStyleGrouped];
It works even when I have NS_DESIGNATED_INITIALIZER set. Presumably no other warnings are sent because the compiler is already complaining about not calling a designated initializer of super.
And as long as the views called in the table view do not need any of the objects that were passed in, everything is fine. If a view that is linked to in the table does need one of the objects, then obviously the app crashes.
Since I never want to call anything but the designated initializer, it was suggested that I use NSAssert() to make sure that I don’t call the designated initializers of my superclass, and make all the warnings go away with this code:
- (instancetype)initWithStyle:(UITableViewStyle)style {
NSAssert(NO, @"%@", @"Tried to implement with initWithStyle");
self = [self initInManagedObjectContext:nil withScoreKeeper:nil withWordList:nil];
return self;
}
- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
NSAssert(NO, @"%@", @"Tried to implement with initWithNibName");
self = [self initInManagedObjectContext:nil withScoreKeeper:nil withWordList:nil];
return self;
}
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
NSAssert(NO, @"%@", @"Tried to implement with initWithCoder");
self = [self initInManagedObjectContext:nil withScoreKeeper:nil withWordList:nil];
return self;
}
Now I get this error in the log when I try to call initWithStyle directly.
*** Assertion failure in -[SettingsTableViewController initWithStyle:], /Users/jscarry/Words/Words/Settings/SettingsTableViewController.m:37
This stackoverflow question has some useful info.
This article explains more about why it is implemented.
Update: I found this article (UITableViewController Designated Initializer Woes) where they have a more complete explanation of the issue and a more complete fix.