Designated Initializer翻譯過來就是`指定初始化函數(shù)。先看一則關于UIViewController的小故事。
如何創(chuàng)建UIViewController
如何創(chuàng)建一個UIViewController呢?不懂?老老實實翻API去...
圖中的Designated Initializer標志說明,系統(tǒng)推薦你使用initWithNibName:bundle:
方法創(chuàng)建UIViewController及其子類,不要用其他什么亂七八糟的方法創(chuàng)建對象。因此通常你會這么寫:
//MyViewController.h
//MyViewController.m is clean
@interface MyViewController : UIViewController
@end
//client
NSBundle *bundle = ...;
MyViewController *vc = [MyViewController alloc] initWithNibName:@"vcNib" bundle:bundle];
更高逼格
如果想逼格高一點,自己給MyViewController創(chuàng)建一個新的指定初始化函數(shù)可以嗎?可以!比如在初始化時,把作者名字也傳進去。那你得這么寫:
//MyViewController.h
@interface MyViewController : UIViewController
@property (nonatomic, strong) NSString *author;
@end
- (instancetype)initWithNibName:(NSString *)nibName bundle:(NSBundle *)nibBundle author:(NSString *)author;
//MyViewController.m
@implementation MyViewController
- (instancetype)initWithNibName:(NSString *)nibName bundle:(NSBundle *)nibBundle author:(NSString *)author {
if((self=[super initWithNibName:nibName bundle:nibBundle])) { //調(diào)用父類的Designated Initializer
_author = author;
}
return self;
}
//自動繼承父類的- (instancetype)initWithNibName: bundle:方法
@end
此時- (instancetype)initWithNibName:bundle:author:
變成了:MyViewController的指定初始化函數(shù)。
- (instancetype)initWithNibName:bundle:
則變成了MyViewController的便利初始化函數(shù)(Convenience Initializer/Secondary Initializer)。
也就是說,從此以后,我們應該使用- (instancetype)initWithNibName:bundle:author:
來創(chuàng)建MyViewController對象。
手癢怎么辦
雖然- (instancetype)initWithNibName:bundle:
已經(jīng)被降級為便利初始化函數(shù)。但如果你哪天手癢,仍然使用它來創(chuàng)建MyViewController,就會造成author屬性沒有值了!如果你在指定初始化函數(shù)中做了額外的操作,也沒有機會得到調(diào)用了(比如初始化了一些私有變量)!
為了避免這樣的問題,我們應該把指定初始化函數(shù)看做是初始化的統(tǒng)一出口。我們需要重寫所有便利初始化函數(shù),讓便利初始化函數(shù)在內(nèi)部轉調(diào)指定初始化函數(shù)。
//MyViewController.m
@implementation MyViewController
//指定初始化函數(shù)
- (instancetype)initWithNibName:(NSString *)nibName bundle:(NSBundle *)nibBundle author:(NSString *)author {
if((self=[super initWithNibName:nibName bundle:nibBundle])) { //調(diào)用父類的Designated Initializer
_author = author;
_address = @"Kings Road"; //額外操作,初始化私有變量
}
return self;
}
//重寫從父類繼承過來的便利初始化函數(shù)
- (instancetype)initWithNibName:(NSString *)nibName bundle:(NSBundle *)nibBundle {
if((self=[self initWithNibName:nibName bundle:nibBundle author:@"anonymous"])) { //調(diào)用MyViewController的Designated Initializer
_address = @"Kings Road"; //額外操作,初始化私有變量
}
return self;
}
@end
搞掂!此時即使你調(diào)用便利初始化函數(shù),在內(nèi)部也會轉調(diào)指定初始化函數(shù),確保初始化過程的完整性。以后我們只需保證指定初始化函數(shù)是正確的就行了。
自己創(chuàng)建一個便利初始化函數(shù)
//MyViewController.m
@implementation MyViewController
//指定初始化函數(shù)
...
//自己創(chuàng)建的便利初始化函數(shù)
//這次我們不要bundle了..真的好嗎..只是舉個栗子
- (instancetype)initWithNibName:(NSString *)nibName {
if((self=[self initWithNibName:nibName bundle:nil author:nil])) { //調(diào)用MyViewController的Designated Initializer
}
return self;
}
@end
其實做法和之前的方式是完全一樣的: 只要確保便利初始化函數(shù)在內(nèi)部轉調(diào)指定初始化函數(shù)就可以了。
只能有一個Designated Initializer嗎?
這是API中關于
initWithNibName:bundle:
方法的討論。高亮部分說到: 如果使用storyboard創(chuàng)建VC,則系統(tǒng)自動調(diào)用initWithCoder:
創(chuàng)建VC,而不是使用initWithNibName:bundle:
創(chuàng)建VC。
...這臉打得好疼。之前還說推薦使用initWithNibName:bundle:
,為毛系統(tǒng)自己卻用initWithCoder:
創(chuàng)建VC。
公布答案: 這兩個方法其實都是UIViewController的指定初始化函數(shù),但隸屬于不同的初始化數(shù)據(jù)源。initWithNibName:bundle:
使用nibName的名字和bundle來創(chuàng)建VC。而initWithCoder:
使用一個已被序列化的VC對象作為參數(shù),并將VC對象反序列化。
科普: 序列化是指把一個對象實例轉為二進制數(shù)據(jù),然后保存硬盤文件中。反序列化則把一個二進制數(shù)據(jù)讀取出來,重新解析為一個對象。這和平常直接用文本值初始化對象是不一樣的。
在objc中,如果希望對象支持序列化和反序列化,則必須實現(xiàn)NSCoding接口,并實現(xiàn)其中的兩個方法。
結論: 在同一個類中,我們可以擁有多個Designated Initializer, 每一個Designated Initializer負責使用一種類型的數(shù)據(jù)源進行初始化。
總結
- Designated Initializer表示指定初始化函數(shù)(唯一的初始化出口)。
- 如果需要創(chuàng)建新的指定初始化函數(shù),則新的指定初始化函數(shù)在內(nèi)部調(diào)用父類的指定初始化函數(shù)。而舊的指定初始化函數(shù)降級為便利初始化函數(shù),你必須重寫便利初始化函數(shù),并在里面轉調(diào)新的指定初始化函數(shù)。
- 如果需要自己創(chuàng)建便利初始化函數(shù),則在里面必須轉調(diào)指定初始化函數(shù)。(和第二點一樣)
- 一個類可以擁有多個指定初始化函數(shù)。你需要根據(jù)初始化數(shù)據(jù)源選擇其中一個指定初始化函數(shù)。(不建議為一種初始化數(shù)據(jù)源創(chuàng)建多個指定初始化函數(shù))。