警告:指定初始值设定项的方法覆盖

时间:2015-04-14 23:23:37

标签: ios objective-c

我以编程方式创建了几个表,并且代码已经运行了好几年。两周前我上次运行时没有发出任何警告。我已经更新到iOS 8.3,现在每个UITableViewController都会收到三个警告。

  

超类的指定初始值设定项的方法覆盖   '-initWithStyle:'找不到。

     

超类的指定初始值设定项的方法覆盖   '-initWithCoder:'找不到。

     

超类的指定初始值设定项的方法覆盖   '-initWithNibName:bundle:'找不到。

初始化表的代码与我的所有表类似:

- (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];来覆盖指定的初始化程序,但我想编译器现在有其他想法。

那么如何覆盖指定的初始值设定项?

5 个答案:

答案 0 :(得分:52)

禁止超类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

在这两种情况下,(允许或禁止),无需更改其余的实施。

答案 1 :(得分:12)

您已将initInManagedObjectContext:withScoreKeeper:withWordList:声明为SettingsTableViewController的唯一指定初始值设定项。这意味着所有其他初始值设定项必须调用它。但是你继承了initWithStyle:等等,而那些那些没有调用你指定的初始化程序。这意味着如果您的对象是通过调用initWithStyle:(或更重要的是initWithCoder:)创建的,则无法正确初始化它。 Clang正在警告你。

看起来你通过删除NS_DESIGNATED_INITIALIZER来解决它,这在Cocoa开发中很常见。我没见过很多经常使用那个宏的开发者。但它留下了潜在的错误。更完整的解决方案是覆盖超类的指定初始化器并将它们实现为[self initInManagedObjectContext:withScoreKeeper:withWordList:]的调用,或者使用NSAssert()实现它们以确保它们正确失败而不是默默地执行默认操作(可能就在这里,但可能不是。)

请注意,Swift使这种情况出错。它刚刚回到ObjC作为警告。

答案 2 :(得分:8)

调用[super initWithStyle:UITableViewStyleGrouped]不会覆盖超类中的方法;你只是打电话给它。

我怀疑iOS 8.3现在会在Objective-C中向您显示这些警告(我们已经在Swift中获得了一些警告)。

我只是简单地覆盖这样的方法来摆脱警告:

- (instancetype)initWithStyle:(UITableViewStyle)style
{
    return [super initWithStyle:style];
}

在您的情况下,您似乎不需要做更多其他事情。

<强>更新

尝试将便利初始化程序更改为:

- (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;
}

该方法是一个便利初始化程序,应始终调用当前类中的一个指定初始值设定项。然后,那个人可以调用superview的指定初始化程序。

答案 3 :(得分:3)

万一有人想知道更多关于错误的信息,这就是我发现的。我不是程序员,所以部分可能是错误的。

当我第一次使用Xcode 5和Xcode 6打开我的项目时,我被提示&#34;转换为现代Objective-C语法&#34;。 (最新版本的Xcode将其置于编辑&gt;转换&gt;转换为现代Objective-C语法。)我允许Xcode转换所有内容并理解大部分更改。我理解NS_DESIGNATED_INITIALIZER是如何工作的,但不知道它的工作原理。由于我没有编译器错误,一切都像以前一样工作,我很快忘记了它。在最新版本的Xcode中,它们似乎已经更新了编译器,这就是触发我的警告信息的原因。

在Apple的笔记中:Adopting Modern Objective C

  

使用此宏会引入一些限制:

     

指定初始化程序的实现必须链接到a   指定的超类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;

我违反了第三个限制,因为我没有实现超类的所有指定初始值设定项。

从.h文件中删除NS_DESIGNATED_INITIALIZER宏会使警告消失。

然后出现问题,我是否关心这些类没有指定的初始化器?可能不是。 首先,这些类中没有其他初始化器,因此我不会意外地调用错误的初始化器。其次,我不是训练的程序员,所以当我开始编写应用程序时,我使用了我习惯的程序编程风格。直到最近,我还没有将一个班级分类。所以我不会将这个子类化为子类,并且不会有任何子类需要担心。现在我对Objective C有了更多的了解,结果发现我写的每个类都是iOS类之一的子类,这实际上解释了我为什么会收到错误。

解决Rob的观点。我没有意识到我可以通过调用其超类上的方法来创建表视图对象。例如,此调用有效:

SettingsTableViewController *stvc = [[SettingsTableViewController alloc] initWithStyle: UITableViewStyleGrouped];

即使设置了NS_DESIGNATED_INITIALIZER,它也能正常工作。可能没有发送其他警告,因为编译器已经在抱怨没有调用指定的超级初始化器。

只要在表视图中调用的视图不需要传入的任何对象,一切都很好。如果在表中链接的视图确实需要其中一个对象,那么很明显应用程序崩溃了。

因为除了指定的初始化程序之外我永远不想调用任何东西,在Rob建议使用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 Designated Initializers : Using NS_DESIGNATED_INITIALIZER

This文章详细介绍了它实施的原因。

答案 4 :(得分:3)

您可以使用@steipete分享的此macro。可以解决问题。

#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

任何未指定的初始化程序调用都将引发断言错误。