原文鏈接:http://www.saitjr.com/ios/ios-ns_unavailable-ns_designated_initializer.html
小知識點但是分析的很到位,對于此知識點不太清楚的也可以了解下。
當面對多個初始化方法時,外部調用者往往會手足無措,不知道哪一個才是正確的初始化方法,對此,蘋果提供了兩個關鍵字: NS_UNAVAILABLE 與 NS_DESIGNATED_INITIALIZER 來幫助我們約束定義方式,使得接口描述更加清晰。
對于多個 init 方法,蘋果給出了一個調用順序,而我們也應該遵守這種調用順序,以確保無論外部調用者從哪個入口進入,都能夠正確的初始化:
可以看到真正在進行初始化參數的,是 initWithTitle:date: ,如果調用者通過 init 或者 initWithTitle: 進入,都應該確保變量 title 和 date 能正確賦值,所以 init 與 initWithTitle: 都通過調用 initWithTitle:date: 來初始化。
最后 initWithTitle:date: 在通過父類的 init 初始化,并初始化兩個變量。
對于這種能初始化全部必需變量的方法,一般可作為 designed initializer 。所以,可以明確的告訴外部調用者,無論調用哪種初始化方法,最終,都會調用 designed initializer:
- (instancetype)initWithTitle:(NSString *)title date:(NSDate *)date NS_DESIGNATED_INITIALIZER;
一個子類如果有自己的 designed initializer,則必須要實現父類的 designed initializer。比如一個繼承自 NSObject 的 Person 類,就必須要重寫 init 方法,并在 init 方法中,調用自己的 designed initializer,而不是調用 super 的初始化方法。如果未實現,可以看到編譯警告:
Method override for the designed initializer of the superclass ‘- init’ not found.
所以,對于 Person 來說,如果 initWithName: 被標記了 NS_DESIGNED_INITIALIZER ,那么實現應該為:
- (instancetype)init {
// 在外部調用不需要 name 變量時,應該給出默認值
return [self initWithName:@"John doe"];
}
- (instancetype)initWithName:(NSString *)name {
self = [super init];
if (self) {
self.name = name;
}
return self;
}
除此之外,子類的 designed initializer 方法,在調用 super 時,也應該調用 super 的 designed initializer。也就是說,如果 CustomView 是 UIView 的子類,那么應該寫作:
// 實現 UIView 的 designed initializer
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
return [self initWithVideoID:@0];
}
// 實現 UIView 的 designed initializer
- (instancetype)initWithFrame:(CGRect)frame {
return [self initWithVideoID:@0];
}
// 實現自己的 designed initializer
- (instancetype)initWithVideoID:(NSNumber *)videoID {
// 這里在調用 super 的初始化方法時,就不能調用 init,因為 init 不是 UIView 的 designed initializer
self = [super initWithFrame:CGRectZero];
if (self) {
self.videoID = videoID;
[self setupUI];
}
return self;
}
NS_UNAVAILABLE
在定義初始化方法時,除了能夠用 NS_DESIGNATED_INITIALIZER 標記以外,還可以使用更為強勢的 NS_UNAVAILABLE 。和 NS_DESIGNATED_INITIALIZER 用于明確初始化方法方式不同, NS_UNAVAILABLE 的作用是,直接禁用其他初始化方法,簡單粗暴。
假設,對于 User 類,如果沒有 userID 就代表著用戶無效,那么我們也沒必要給 init 方法一個默認的 userID = 0 ,或者 userID = nil 。此時,需要告訴調用者,就只能通過 userID 來初始化,那么可以寫作:
+ (instancetype)new NS_UNAVAILABLE;
- (instancetype)init NS_UNAVAILABLE; ///< 直接標記 init 方法不可用
- (instancetype)initWithUserID:(NSNumber *)userID;
方法一旦標記 NS_UNAVAILABLE ,那么在 IDE 自動補全時,就不會索引到該方法,并且如果強制調用該方法,編譯器會報錯(但并不代表著方法不能被調用,runtime 依然可以做到)。
除了可以直接使用 NS_UNAVAILABLE 標記不可用以外,還有一些其他的方式:
// 作用與 NS_UNAVAILABLE 類似
- (id) init __unavailable;
- (id) init __attribute__((unavailable));
- (id) init UNAVAILABLE_ATTRIBUTE;
// 在調用時給出提示
- (id) init __attribute__((unavailable("Must use initWithFoo: instead.")));
甚至是在調用時拋出異常等,比如 userID 不能小于 0:
- (instancetype)initWithUserID:(NSNumber *)userID {
self = [super init];
if (self) {
if (userID.integerValue <= 0) {
// raise: 原因
// format: 具體描述
[NSException raise:@"error parameter" format:@"user id can not = %@", userID];
}
self.userID = userID;
}
return self;
}
小結
NS_DESIGNATED_INITIALIZER 與 NS_UNAVAILABLE 都能清晰的告知調用者應該如何調用方法。
如果是可以給出默認值初始化方法,那么使用 NS_DESIGNATED_INITIALIZER 就可以。
如果是必須要用某參數來初始化的,可以使用 NS_UNAVAILABLE 。
如果需要在內部驗證參數是否合法,如果不合法就一定不能成功的,也可以在實現的時候,驗證并拋出異常。
具體選擇使用哪一種方式,可以根據具體的情況來看。