Подтвердить что ты не робот

Предупреждение: переопределение метода для назначенного инициализатора

Я программно создаю несколько таблиц, и код работал отлично в течение многих лет. Две недели назад он не генерировал никаких предупреждений, когда я его последний раз запускал. 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];, но я думаю, что у компилятора теперь есть другие идеи.

Итак, как мне переопределить назначенный инициализатор?

4b9b3361

Ответ 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

Любой вызываемый вызываемый инициализатор вызовет ошибку утверждения.