Designated Initializer
一个类,可能有很多初始化函数,但是有主次之分,最主要的初始函数应该对类内应当需要初始化的变量进行初始化。这个最主要的初始函数即Designated Initializer(指定初始化器),可以理解为是类的默认初始函数。比如,UIView的Designated Initializer是initWithFrame:而不是init:
原则1.类的正确初始化过程应当依次调用子类到父类的Designated Initializer。即使是用父类的Designated Initializer初始化一个子类对象,也需要遵从这个过程
原则2.如果子类指定了新的Designated Initializer,那么该函数必须调用父类的Designated Initializer。并且需要重写父类的Designated Initializer,将其指向子类新的Designated Initializer
NSObject的Designated Initializer为init;
UIView的Designated Initializer为initWithFrame:;
TestView继承于UIView,它有一个属性Name,所以它的Designated Initializer为initWithFrame:andName:
假如不遵循第2点,initWithFrame:andName:的实现如下
- (id)initWithFrame:(CGRect)frame andName:(NSString *)name { if (self = [super init]) { self.name = name; } return self; }
测试1:使用子类的Designated Initializer初始化子类对象
TestView *testView = [[TestView alloc] initWithFrame:CGRectZero andName:@""];
调用顺序如下:
1)TestView的initWithFrame:andName:
2)UIView的init
3)UIView的initWithFrame
4)NSObject的init
此时没问题,3个类的Designated Initializer都被调用了
测试2:使用父类的Designated Initializer初始化子类对象
TestView *testView = [[TestView alloc] initWithFrame:CGRectZero];
调用顺序如下:
1)UIView的initWithFrame
2)NSObject的init
此时有问题,TestView的Designated Initializer没有被调用
而测试1之所以没问题,是因为UIView遵循了原则1和原则2。
好,现在我们将TestView修改为遵循原则1和原则2。
- (id)initWithFrame:(CGRect)frame andName:(NSString *)name { if (self = [super initWithFrame:frame]) { self.name = name; } return self; } - (id)initWithFrame:(CGRect)frame { return [self initWithFrame:frame andName:@""]; }
测试1:使用子类的Designated Initializer初始化子类对象
TestView *testView = [[TestView alloc] initWithFrame:CGRectZero andName:@""];
调用顺序如下:
1)TestView的initWithFrame:andName:
2)UIView的initWithFrame
3)NSObject的init
此时没问题,3个类的Designated Initializer都被调用了
测试2:使用父类的Designated Initializer初始化子类对象
TestView *testView = [[TestView alloc] initWithFrame:CGRectZero];
调用顺序如下:
1)TestView的initWithFrame
2)TestView的initWithFrame:andName:
3)UIView的initWithFrame
4)NSObject的init
此时没问题,3个类的Designated Initializer都被调用了
原则3.你可以不自定义Designated Initializer,但需要通过重写父类的Designated Initializer,来调用父类的Designated Initialzier
- (id)initWithFrame:(CGRect)frame { if (self = [super initWithFrame:frame]) { self.name = name; //注意这里的name不是通过初始化函数传递进来的 } return self; }
原则4.如果有多个Secondary Initializers(次要初始化器),它们之间可以任意调用,但最后必须指向该类的Designated Initializer。而且在Secondary Initializer内不能直接调用父类的初始化器。注意重写父类的Designated Initializer的也算是Secondary Initializer
//Super Override - (id)initWithFrame:(CGRect)frame { return [self initWithFrame:frame andName:@""]; } //Designated Initializer - (id)initWithFrame:(CGRect)frame andName:(NSString *)name { if (self = [super initWithFrame:frame]) { self.name=name; } return self; } //Instance Secondary Initializer - (id)initWithName:(NSString *)name { return [self initWithFrame:CGRectZero andName:name]; } //Instance Secondary Initializer - (id)initWithName2:(NSString *)name { return [self initWithName:name]; } //Class secondary initializer + (id)testViewWithName:(NSString *)name { TestView *testView=[[TestView alloc] initWithFrame:CGRectZero andName:name]; return testView; }
可以看到方法initWithName2调用的顺序是initWithName2–>initWithName–>initWithFrame:andName:,最后指向了Designated Initializer。
同时需要注意的是,Secondary initializers不仅可以是实例方法,也可以是静态方法,如testViewWithName:
还有一个问题是为什么Secondary Initializer内不能直接调用父类的初始化器?
我们要明确,调用父类的Designated Initializer的那个方法就是子类的Designated Initializer,Designated Initializer有且只有1个,所以即使有多个初始化函数,也要保证只能有一个是Designated Initializer
P.S.
使用 NS_DESIGNATED_INITIALIZER 来显式指定 Designated Initializer,参照《Adopting Modern Objective-C》
- (instancetype)init NS_DESIGNATED_INITIALIZER;
参考链接