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;

 

参考链接

正确编写Designated Initializer的几个原则

posted @ 2015-10-11 23:33  Norcy  阅读(461)  评论(0编辑  收藏