在 iOS 裏面,不管是 Objective-C 仍是 Swift,類(結構體、枚舉)的初始化都有必定的規則要求,只不過在 Objective-C 中會比較寬鬆,若是不按照規則也不會報錯,但會存在隱患,而在 Swift 則須要嚴格按照規則要求代碼才能編譯經過,極大提升了代碼的安全性。html
類(結構體、枚舉)的初始化有兩種初始化器(初始化方法):指定初始化器(Designated Initializers )、便利初始化器(Convenience Initializers)swift
指定初始化器是類(結構體、枚舉)的主初始化器,類(結構體、枚舉)初始化的時候必須調用自身或者父類的指定初始化器。一個類(結構體、枚舉)能夠有多個指定初始化器,做用是表明從不一樣的源進行初始化。一個類(結構體、枚舉)除非有多種不一樣的源進行初始化,不然不建議建立多個指定初始化器。在 iOS 裏,視圖控件類,若是:UIView
、UIViewController
就有兩個指定初始化器,分別表明從代碼初始化、從Nib
初始化安全
便利初始化器是類(結構體、枚舉)的次要初始化器,做用是使類(結構體、枚舉)在初始化時更方便設置相關的屬性(成員變量)。既然便利初始化器是爲了便利,那麼一個類(結構體、枚舉)就能夠有多個便利初始化器,這些便利初始化器裏面最後都須要調用自身的指定初始化器ide
iOS 的初始化最核心兩條的規則:ui
全部的其餘規則都根據這兩條規則而展開,只是 Objective-C 沒有那麼多安全檢查,顯得比較隨意、寬鬆,而 Swift 則有一堆的限制。atom
Objective-C 在初始化時,會自動給每一個屬性(成員變量)賦值爲 0 或者 nil
,沒有強制要求額外爲每一個屬性(成員變量)賦值,方便的同時也缺乏了代碼的安全性。spa
Objective-C 中的指定初始化器會在後面被NS_DESIGNATED_INITIALIZER
修飾,如下爲NSObject
和UIView
的指定初始化器code
// NSObject
@interface NSObject <NSObject>
- (instancetype)init
#if NS_ENFORCE_NSOBJECT_DESIGNATED_INITIALIZER
NS_DESIGNATED_INITIALIZER
#endif
;
@end
// UIView
@interface UIView : UIResponder
- (instancetype)initWithFrame:(CGRect)frame NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER;
@end
複製代碼
在 Objective-C 裏面,全部類都繼承自NSObject
。當自定義一個類的時候,要麼直接繼承自NSObject
,要麼繼承自UIView
或者其餘類。cdn
不管繼承自什麼類,都常常須要新的初始化方法,而這個新的初始化方法其實就是新的指定初始化器。若是存在一個新的指定初始化器,那麼原來的指定初始化器就會自動退化成便利初始化器。爲了遵循必需要調用指定初始化器的規則,就必須重寫舊的定初始化器,在裏面調用新的指定初始化器,這樣就能確保全部屬性(成員變量)被初始化htm
根據這條規則,能夠從NSObject
、UIView
中看出,因爲UIView
擁有新的指定初始化器-initWithFrame:
,致使父類NSObject
的指定初始化器-init
退化成便利初始化器。因此當調用[[UIView alloc] init]
時,-init
裏面必然調用了-initWithFrame:
當存在一個新的指定初始化器的時候,推薦在方法名後面加上NS_DESIGNATED_INITIALIZER
,主動告訴編譯器有一個新的指定初始化器,這樣就可使用 Xcode 自帶的Analysis
功能分析,找出初始化過程當中可能存在的漏洞
@interface MyView : UIView
@property (nonatomic, strong) NSString *name;
// 推薦加上NS_DESIGNATED_INITIALIZER
- (instancetype)initWithFrame:(CGRect)frame name:(NSString *)name NS_DESIGNATED_INITIALIZER;
@end
@implementation MyView
// 初始化時加入參數name,這個方法已經成爲新的指定初始化器
- (instancetype)initWithFrame:(CGRect)frame name:(NSString *)name {
if (self = [super initWithFrame:frame]) {
self.name = name;
}
return self;
}
// 舊的指定初始化器就自動退化成便利初始化器,必須在裏面調用新的指定初始化器
- (instancetype)initWithFrame:(CGRect)frame {
return [self initWithFrame:frame name:@"Daniels"];
}
// 舊的指定初始化器就自動退化成便利初始化器,必須在裏面調用新的指定初始化器
- (instancetype)initWithCoder:(NSCoder *)coder {
// 這裏的實現是僞代碼,只是爲了知足規則
return [self initWithFrame:CGRectNull name:@"Daniels"];
}
@end
複製代碼
若是不想去重寫舊的指定初始化器,但又不想存在漏洞和隱患,那麼可使用NS_UNAVAILABLE
把舊的指定初始化器都廢棄,外界就沒法調用舊的指定初始化器
@interface MyView : UIView
@property (nonatomic, strong) NSString *name;
// 推薦加上NS_DESIGNATED_INITIALIZER
- (instancetype)initWithFrame:(CGRect)frame name:(NSString *)name NS_DESIGNATED_INITIALIZER;
// 廢棄舊的指定初始化器
- (instancetype)init NS_UNAVAILABLE;
// 廢棄舊的指定初始化器
- (instancetype)initWithFrame:(CGRect)frame NS_UNAVAILABLE;
// 廢棄舊的指定初始化器
- (instancetype)initWithCoder:(NSCoder *)coder NS_UNAVAILABLE;
@end
@implementation MyView
// 初始化時加入參數name,這個方法已經成爲新的指定初始化器
- (instancetype)initWithFrame:(CGRect)frame name:(NSString *)name {
if (self = [super initWithFrame:frame]) {
self.name = name;
}
return self;
}
@end
複製代碼
固然,一個新的類也能夠不增長新的初始化方法,在 Objective-C 中,子類會直接繼承父類全部的初始化方法
在 Swift 中,初始化器的規則嚴格且複雜,目的就是爲了使代碼更加安全,若是不符合規則,會直接報錯,經常會讓剛接手 Swift 或者一直對 iOS 的初始化沒有深刻理解的人很頭疼。其實核心規則仍是同樣,只要理解了各個規則的含義和做用,寫起來仍是沒有壓力。
從 iOS 初始化的核心規則展開而來,Swift 多了一些規則:
self
相關的任何東西,例如:調用實例屬性,調用實例方法。這種狀況處理就十分簡單,本身裏面的init
方法就是它的指定初始化器,並且能夠隨意建立多個它的指定初始化器。若是須要建立便利初始化器,則在方法名前面加上convenience
,且在裏面必須調用其餘初始化器,使得最後確定調用指定初始化器
class Person {
var name: String
var age: Int
// 能夠存在多個指定初始化器
init(name: String, age: Int) {
self.name = name;
self.age = age;
}
// 能夠存在多個指定初始化器
init(age: Int) {
self.name = "Daniels";
self.age = age;
}
// 便利初始化器
convenience init(name: String) {
// 必需要調用本身的指定初始化器
self.init(name: name, age: 18)
// 必須在初始化完成後才能調用實例方法
jump()
}
func jump() {
}
}
複製代碼
若是子類沒有新的非可選類型屬性,或者保證全部非可選類型屬性都已經有默認值,則能夠直接繼承父類的指定初始化器和便利初始化器
class Student: Person {
var score: Double = 100
}
複製代碼
若是子類有新的非可選類型屬性,或者沒法保證全部非可選類型屬性都已經有默認值,則須要新建立一個指定初始化器,或者重寫父類的指定初始化器
class Student: Person {
var score: Double
// 新的指定初始化器,若是有新的指定初始化器,就不會繼承父類的全部初始化器,除非重寫
init(name: String, age: Int, score: Double) {
self.score = score
super.init(name: name, age: age)
}
// 重寫父類的指定初始化器,若是不重寫,則子類不存在這個方法
override init(name: String, age: Int) {
score = 100
super.init(name: name, age: age)
}
// 便利初始化器
convenience init(name: String) {
// 必需要調用本身的指定初始化器
self.init(name: name, age: 10, score: 100)
}
}
複製代碼
須要注意的是,若是子類重寫父類全部指定初始化器,則會繼承父類的便利初始化器。緣由也是很簡單,由於父類的便利初始化器,依賴於本身的指定初始化器
在 Swift 中能夠定義一個可失敗的初始化器(Failable Initializers),表示在某些狀況下會建立實例失敗。
只有在表示建立失敗的時候纔有返回值,而且返回值爲nil
。
子類能夠把父類的可失敗的初始化器重寫爲不可失敗的初始化器,但不能把父類的不可失敗的初始化器重寫爲可失敗的初始化器
class Animal {
let name: String
// 可失敗的初始化器,若是把 ! 換成 ?,則爲隱式的可失敗的初始化器
init?(name: String) {
if name.isEmpty {
return nil
}
self.name = name
}
}
class Dog: Animal {
override init(name: String) {
if name.isEmpty {
super.init(name: "旺財")!
} else {
super.init(name: name)!
}
}
}
複製代碼
在 Swift 中,可使用required
修飾初始化器,來指定子類必須實現該初始化器。須要注意的是,若是子類能夠直接繼承父類的指定初始化器和便利初始化器,因此也就能夠不用額外實現required
修飾的初始化器
子類實現該初始化器時,也必須加上required
修飾符,而不是override
class MyView: UIView {
var name: String
init(frame: CGRect, name: String) {
self.name = name;
super.init(frame: frame)
}
// 必須實現此初始化器,但因爲是可失敗的初始化器,因此裏面能夠不作具體實現
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
複製代碼
iOS 的初始化最核心兩條的規則:
展開而來的多條規則:
required
修飾的初始化器