简体   繁体   中英

Warning: Method override for designated initializer

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 iOS 8.3 and I 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;

I thought that I was overriding a designated initializer by invoking self = [super initWithStyle:UITableViewStyleGrouped];, but I guess the compiler now has other ideas.

So how do I override the designated initializer?

To forbid superclass NS_DESIGNATED_INITIALIZER

You can describe them as unavailable and implement them with exceptions.

For your example, in SettingsTableViewController.h :

@interface SettingsTableViewController : UITableViewController
- (instancetype)initWithStyle:(UITableViewStyle)style NS_UNAVAILABLE;
- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil NS_UNAVAILABLE;
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder NS_UNAVAILABLE;

- (instancetype)initInManagedObjectContext:(NSManagedObjectContext *)context 
                 withScoreKeeper:(ScoreKeeper *)scorer 
                    withWordList:(WordList *)wordlist NS_DESIGNATED_INITIALIZER;

//...your class interface here

@end

In SettingsTableViewController.m :

@interface SettingsTableViewController ()
- (instancetype)initWithStyle:(UITableViewStyle)style NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER;
@end

@implementation SettingsTableViewController
- (instancetype)initWithStyle:(UITableViewStyle)style { @throw nil; }
- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil { @throw nil; }
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { @throw nil; }

//...your class implementation here

@end

You can also add NSAssert(NO, nil); before @throw nil; to know the file and line number of the exception in the logs.

To allow superclass NS_DESIGNATED_INITIALIZER

You have to implement them explicitly. But nice thing to do would be to remove the public superfluous NS_DESIGNATED_INITIALIZER macro for your potential subclasses, and only reintroduce-it privately.

For your example, in SettingsTableViewController.h:

@interface SettingsTableViewController : UITableViewController
- (instancetype)initWithStyle:(UITableViewStyle)style;
- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil;
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder;

- (instancetype)initInManagedObjectContext:(NSManagedObjectContext *)context 
                 withScoreKeeper:(ScoreKeeper *)scorer 
                    withWordList:(WordList *)wordlist NS_DESIGNATED_INITIALIZER;

//...your class interface here

@end

In SettingsTableViewController.m :

@interface SettingsTableViewController ()
- (instancetype)initWithStyle:(UITableViewStyle)style NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER;
@end

@implementation SettingsTableViewController
- (instancetype)initWithStyle:(UITableViewStyle)style { return [super initWithStyle:style]; }
- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil { return [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; }
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder { return [super initWithCoder:aDecoder]; }

//...your class implementation here

@end

In both cases, (allowing or forbidding), no need to change the rest of your implementation.

You've declared initInManagedObjectContext:withScoreKeeper:withWordList: to be the one-and-only designated initializer for SettingsTableViewController . That means that all other initializers must call it. But you inherited initWithStyle: , etc., and those those don't call your designated initializer. That means that if your object is created by calling initWithStyle: (or more importantly, initWithCoder: ), it won't be properly initialized. Clang is warning you about that.

It appears you solved it by removing NS_DESIGNATED_INITIALIZER , and that's pretty common among Cocoa devs. I haven't seen many devs who routinely use that macro. But it leaves the potential bug. The more complete solution would be to override the superclass's designated initializers and implement them as calls to [self initInManagedObjectContext:withScoreKeeper:withWordList:] , or implement them with NSAssert() to ensure that they correctly fail rather than just silently do the default thing (which might be right here, but might not be).

Note that Swift made this situation an error. It's just coming back into ObjC as a warning.

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.

Just in case anyone wants to know more about the error, 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.

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

To address Rob's point. 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, following Rob's suggestion of using NSAssert(), I can 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;
}

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

Useful links: iOS Designated Initializers : Using NS_DESIGNATED_INITIALIZER

This article explains more about why it is implemented.

You can use this macro shared by @steipete . Can solve the problem.

#define PSPDF_NOT_DESIGNATED_INITIALIZER() PSPDF_NOT_DESIGNATED_INITIALIZER_CUSTOM(init)
#define PSPDF_NOT_DESIGNATED_INITIALIZER_CUSTOM(initName) \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wobjc-designated-initializers\"") \
- (instancetype)initName \
{ do { \
NSAssert2(NO, @"%@ is not the designated initializer for instances of %@.", NSStringFromSelector(_cmd), NSStringFromClass([self class])); \
return nil; \
} while (0); } \
_Pragma("clang diagnostic pop")

Usage:

// IBLAirHomeActionsBarItem.h
@interface IBLAirHomeActionsBarItem : IBLBaseView

@property (nonatomic, copy, readonly) NSString *title;
@property (nonatomic, strong, readonly) UIImage *image;

- (instancetype)initWithTitle:(NSString *)title image:(UIImage *)image NS_DESIGNATED_INITIALIZER;

@end


// IBLAirHomeActionsBarItem.m
@implementation IBLAirHomeActionsBarItem

PSPDF_NOT_DESIGNATED_INITIALIZER_CUSTOM(initWithFrame:(CGRect)frame)
PSPDF_NOT_DESIGNATED_INITIALIZER_CUSTOM(initWithCoder:(NSCoder *)aDecoder)

- (instancetype)initWithTitle:(NSString *)title image:(UIImage *)image {
  self = [super initWithFrame:CGRectZero];
  if (self) {
  }
  return self;
}

@end

Any not designated initializer get called will throw an assertion error.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM