NS_DESIGNATED_INITIALIZER

警告
  • 1: Method override for the designated initializer of the superclass '-init' not found
  • 2: Convenience initializer missing a 'self' call to another initializer
  • 3: Convenience initializer should not invoke an initializer on 'super
原因

以上3个警告是由于使用NS_DESIGNATED_INITIALIZER不规范引起的。使用场景如下:

@interface Warning : NSObject
- (instancetype)initWithName:(NSString *)name NS_DESIGNATED_INITIALIZER;
@end

@implementation Warning
- (instancetype)initWithName:(NSString *)name
{
  self = [super init];
  if (self) {
    _name = name;
  }
  return self;
}

- (instancetype)initWithNickName:(NSString *)name
{
  self = [super init];
  if (self) {
    _name = name;
  }
  return self;
}
@end

我们先看一下NS_DESIGNATED_INITIALIZER的来历。Objective-C对象的初始化是通过[[SomeClass alloc] initXXX]来进行的,一个类可以有多个初始化方法,通过init作为前缀来标识。通常我们希望调用者使用指定的初始化方法来创建对象,因为在指定的初始化方法中会做一些初始设置,比如给属性设置初始值等。iOS 8以前,开发者只能通过注释的方式来标明哪个方法是指定的初始化方法,使用起来很不方便。在iOS 8开始,Clang提供了一个标志__attribute__((objc_designated_initializer))来标明指定初始化方法,也就是NS_DESIGNATED_INITIALIZER这个宏,声明在NSObjCRuntime.h中:

#ifndef NS_DESIGNATED_INITIALIZER
#if __has_attribute(objc_designated_initializer)
#define NS_DESIGNATED_INITIALIZER __attribute__((objc_designated_initializer))
#else
#define NS_DESIGNATED_INITIALIZER
#endif
#endif

使用NS_DESIGNATED_INITIALIZER需要遵循三个规范:

  • A designated initializer must call (via super) a designated initializer of the superclass. Where NSObject is the superclass this is just [super init].
  • Any convenience initializer must call another initializer in the class - which eventually leads to a designated initializer.
  • A class with designated initializers must implement all of the designated initializers of the superclass.

这三个规范主要是为了确保使用指定初始化方法能够得到完整初始化过的对象,尤其是在子类继承父类的场景中。对应的警告正是上面列出的三个警告内容,修改后的代码如下:

@interface Warning : NSObject
- (instancetype)initWithName:(NSString *)name NS_DESIGNATED_INITIALIZER;
@end

@implementation Warning
//Implement super class (NSObject) 's designated initializer
- (instancetype)init 
{
  return [self initWithName:@"No One"];
}

- (instancetype)initWithName:(NSString *)name
{
  self = [super init]; // Call Super Class's designated initializer
  if (self) {
    _name = name;
  }
  return self;
}

- (instancetype)initWithNickName:(NSString *)name
{
  self = [self initWithName:name]; // Call other initializer via self
  if (self) {
    _name = name;
  }
  return self;
}
@end

简单总结起来其实就是两条:

  • 无论通过哪个初始化方法,都要调用到该类的指定初始化方法。
  • 指定初始化方法一定要调用到父类的指定初始化方法。

Swift中其实也有同样的规则,官方指南中有一张图描述了上述规则:

Designated Initializers and Convenience Initializers

NS_UNAVAILABLE

有时父类的指定初始化很多,子类重写起来比较麻烦,这时可以通过NS_UNAVAILABLE来标明禁用父类的某些指定初始化方法即可,调用者调用NS_UNAVAILABLE标记的方法时,在编译阶段会报错提示。

参考文章1
参考文章2
参考文章3

推荐阅读更多精彩内容