Предупреждение: переопределение метода для назначенного инициализатора
Я программно создаю несколько таблиц, и код работал отлично в течение многих лет. Две недели назад он не генерировал никаких предупреждений, когда я его последний раз запускал. Ive с момента обновления до iOS 8.3, и теперь я получаю три предупреждения для каждого UITableViewController.
Переопределение метода для назначенного инициализатора суперкласса '-initWithStyle:' не найден.
Переопределение метода для назначенного инициализатора суперкласса '-initWithCoder:' не найден.
Переопределение метода для назначенного инициализатора суперкласса '-initWithNibName: bundle:' not found.
Код для инициализации таблицы аналогичен для всех моих таблиц:
- (instancetype)initInManagedObjectContext:(NSManagedObjectContext *)context
withScoreKeeper:(ScoreKeeper *)scorer
withWordList:(WordList *)wordlist {
self = [super initWithStyle:UITableViewStyleGrouped];
if (self) {
_mObjContext = context;
_scoreKeeper = scorer;
_wordList = wordlist;
}
return self;
}
а .h выглядит так:
@interface SettingsTableViewController : UITableViewController {
UIPopoverController *popover;
}
- (instancetype)initInManagedObjectContext:(NSManagedObjectContext *)context
withScoreKeeper:(ScoreKeeper *)scorer
withWordList:(WordList *)wordlist NS_DESIGNATED_INITIALIZER;
Я думал, что переопределяю назначенный инициализатор, вызывая self = [super initWithStyle: UITableViewStyleGrouped];, но я думаю, что у компилятора теперь есть другие идеи.
Итак, как мне переопределить назначенный инициализатор?
Ответы
Ответ 1
Вызов [super initWithStyle:UITableViewStyleGrouped]
не отменяет метод в суперклассе; вы просто называете это.
Я подозреваю, что iOS 8.3 теперь показывает вам эти предупреждения в Objective-C (мы некоторое время получали эти предупреждения в Swift).
Я просто просто переопределяю эти методы, чтобы избавиться от предупреждений:
- (instancetype)initWithStyle:(UITableViewStyle)style
{
return [super initWithStyle:style];
}
Не похоже, что вам нужно что-то сделать в вашем случае.
Update:
Попробуйте изменить инициализатор удобства на это:
- (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;
}
Этот метод является инициализатором удобства и всегда должен вызывать один из назначенных инициализаторов в текущем классе. Затем можно вызвать инициализатор, назначенный супервизором.
Ответ 2
Запретить суперкласс NS_DESIGNATED_INITIALIZER
Вы можете описать их как недоступные и реализовать их с исключениями.
В вашем примере в 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
В 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
Вы также можете добавить NSAssert(NO, nil);
до @throw nil;
, чтобы узнать номер файла и номера исключения в журналах.
Чтобы разрешить суперкласс NS_DESIGNATED_INITIALIZER
Вы должны выполнить их явно. Но приятно было бы удалить публичный избыточный макрос NS_DESIGNATED_INITIALIZER
для ваших потенциальных подклассов и только повторно ввести его в частном порядке.
В вашем примере в 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
В 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
В обоих случаях (разрешать или запрещать) нет необходимости изменять оставшуюся часть вашей реализации.
Ответ 3
Вы объявили initInManagedObjectContext:withScoreKeeper:withWordList:
единственным назначаемым инициализатором для SettingsTableViewController
. Это означает, что все остальные инициализаторы должны это назвать. Но вы унаследовали initWithStyle:
и т.д., И те, кто не называет ваш назначенный инициализатор. Это означает, что если ваш объект создан, вызывая initWithStyle:
(или, что более важно, initWithCoder:
), он не будет правильно инициализирован. Кланг предупреждает вас об этом.
Кажется, вы решили его, удалив NS_DESIGNATED_INITIALIZER
, и это довольно распространено среди разработчиков Cocoa. Я не видел много разработчиков, которые обычно используют этот макрос. Но это оставляет потенциальную ошибку. Более полным решением было бы переопределить инициализаторы, назначенные суперклассами, и реализовать их как вызовы [self initInManagedObjectContext:withScoreKeeper:withWordList:]
или реализовать их с помощью NSAssert()
, чтобы гарантировать, что они корректно выходят из строя, а не просто молчат по умолчанию (что может быть здесь, но может и не быть).
Обратите внимание, что Swift сделал эту ситуацию ошибкой. Он просто возвращается в ObjC как предупреждение.
Ответ 4
На всякий случай кто-то хочет узнать больше об ошибке, вот что я узнал. Я не программист, поэтому части могут ошибиться.
Когда я впервые открыл свой проект с помощью Xcode 5 и Xcode 6, мне было предложено "Преобразовать в современный Objective-C Синтаксис". (Последняя версия Xcode помещается в раздел "Преобразование > Преобразование в современный Objective-C). Я разрешил Xcode преобразовывать все и понимал большинство изменений. Я понял, как работает NS_DESIGNATED_INITIALIZER, а не почему он работает. Поскольку у меня не было ошибок компилятора, и все работало по-прежнему, я быстро забыл об этом. В последней версии Xcode они, похоже, обновили компилятор, и это то, что вызвало мое предупреждающее сообщение.
В заметках о яблоках: Принятие современной цели C
Использование этого макроса вводит несколько ограничений:
Реализация назначенного инициализатора должна быть связана с метод суперкласса init (с [super init...]), который обозначается инициализатор для суперкласса.
Реализация инициализатора удобства (инициализатор не помеченный как назначенный инициализатор в классе, который имеет как минимум один инициализатор, помеченный как назначенный инициализатор), должен делегировать другой инициализатор (с [self init...]).
Если класс предоставляет один или несколько назначенных инициализаторов, он должен реализовать все назначенные инициализаторы своего суперкласса.
Вот что я думаю. Во-первых, я получил три предупреждающих сообщения, потому что UITableViewController имеет три назначенных инициализатора.
> - (instancetype)initWithStyle:(UITableViewStyle)style NS_DESIGNATED_INITIALIZER;
> - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil NS_DESIGNATED_INITIALIZER;
> - (instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER;
Я нарушил третье ограничение, потому что я не реализовал все назначенные инициализаторы суперкласса.
Удаление макроса NS_DESIGNATED_INITIALIZER из файлов .h заставил предупреждения уйти.
Возникает вопрос: неужели я не забочусь о том, что у этих классов нет назначенных инициализаторов? Возможно нет.
Во-первых, в этих классах нет других инициализаторов, поэтому я случайно не ошибаюсь. Во-вторых, я не программист по образованию, поэтому, когда я начал писать приложения, я использовал стиль процедурного программирования, к которому я привык. До недавнего времени я никогда не подклассифицировал класс. Поэтому я не буду подклассифицировать этот, и не будет никаких подклассов, о которых стоит беспокоиться. Теперь, когда я знаю немного больше о Objective C, выясняется, что каждый класс, который я написал, был подклассом одного из классов iOSs, и это фактически немного объясняет, почему я получаю ошибки.
Чтобы обратиться к точке Robs. Я не понимал, что могу создать объект представления таблицы, вызвав метод на своем суперклассе. Например, этот вызов работает:
SettingsTableViewController *stvc = [[SettingsTableViewController alloc] initWithStyle: UITableViewStyleGrouped];
Он работает, даже если у меня установлен NS_DESIGNATED_INITIALIZER. Предположительно, никакие другие предупреждения не отправляются, потому что компилятор уже жалуется на то, что не вызывается назначенный инициализатор супер.
И пока представления, вызываемые в представлении таблицы, не нуждаются ни в одном из объектов, которые были переданы, все в порядке. Если представление, связанное с таблицей, нуждается в одном из объектов, то, очевидно, приложение аварийно завершает работу.
Поскольку я никогда не хочу вызывать ничего, кроме назначенного инициализатора, после того, как Робс предложил использовать NSAssert(), я могу убедиться, что я не вызываю назначенные инициализаторы моего суперкласса и не оставляю все предупреждения с этим кодом:
- (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;
}
Я получаю эту ошибку в журнале, когда я пытаюсь напрямую вызвать initWithStyle.
*** Ошибка утверждения в - [SettingsTableViewController initWithStyle:], /Users/jscarry/Words/Words/Settings/SettingsTableViewController.m:37
Полезные ссылки:
Назначенные инициализаторы iOS: использование NS_DESIGNATED_INITIALIZER
В этой статье более подробно объясняется, почему она реализована.
Ответ 5
Вы можете использовать этот макрос, совместно используемый @steipete. Может решить проблему.
#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")
Использование:
// 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
Любой вызываемый вызываемый инициализатор вызовет ошибку утверждения.