?構造過程是使用類、結構體或枚舉類型一個實例的準備過程。在新實例可用前必須執行這個過程,具體操作包括設置實例中每個存儲型屬性的初始值和執行其他必須的設置或初始化工作。
?通過定義構造器來實現構造過程,構造器即是創建特定類型實例的特殊方法。與OC構造器不同,swift的構造器無需返回值,它們的主要任務是保證新實例在第一次使用前完成正確的初始化。
一、存儲屬性的初始賦值
?類和結構體在創建實例時,必須為所有存儲類型屬性設置合適的初始值。存儲類型屬性的值不能處于未知狀態。
注意:當為存儲類型屬性設置默認值或在構造器中為其賦值時,它們的值是被直接設置的,不會觸發任何屬性觀察者。
- 構造器。構造器在創建某個特定類型的新實例時調用,最簡形式類似于一個不帶參數的實例方法,以關鍵字
init
命名:
class Cat {
var name:String;
var color:String;
// 如果缺少如下構造過程,代碼是會報錯的,因為name和color沒有初始值
init() { // 在此執行構造過程
print("開始構造");
name = "cat"; // 即設置默認值
color = "black"; // 即設置默認值
}
}
// 默認就是調用init()構造器
var myCat = Cat();
print("\(myCat.name) - \(myCat.color)");
輸出結果:
開始構造
cat - black
- 默認屬性值。上述代碼中,是在構造器中為存儲屬性設置初始值,但也可以在屬性聲明時為直接設置默認值:
class Cat {
// 聲明時,直接設置默認值
var name:String = "cat";
var color:String = "red";
}
二、自定義構造過程
?可以通過傳入參數和可選屬性類型來自定義構造過程,也可以在構造過程中修改常量屬性。
- 構造參數。自定義構造過程時,可以在定義中提供構造參數,指定所需值的類型和名字。而構造參數的功能與語法跟函數、方法的參數相同:
// 自定義構造過程
class Cat {
var name:String?;
var color:String?;
// 自定義構造器1
// 構造參數: 外部參數名fromName,內部參數名name
init(fromName name:String) {
print("初始化名字");
self.name = name;
}
// 自定義構造器2
init(fromColor color:String) {
print("初始化顏色");
self.color = color;
}
}
// 實例化
let myCat1 = Cat(fromName:"笨笨");
print(myCat1.name!);
let mycat2 = Cat(fromColor:"白色");
print(mycat2.color!);
輸出結果:
初始化名字
笨笨
初始化顏色
白色
- 構造器內部參數和外部參數。與函數和方法參數相同,但不同的地方放在于,如果構造器中沒有提供外部參數名,swift會自動為每個構造器自動生成一個與內部參數名相同的外部參數名:
struct CustomColor {
// 顏色的三原色,都是Double類型
let red, green, blue: Double;
// 構造器
init(red:Double, green:Double, blue:Double) {
self.red = red;
self.green = green;
self.blue = blue;
}
}
// 必須要有外部名稱,這里系統會報錯
//let testColor = CustomColor(1.0, 0.0, 1.0);
// 注意: 沒有外部參數,系統會自動生成與內部參數名一樣的
let magenta = CustomColor(red: 1.0, green: 0.0, blue: 1.0);
- 不帶外部參數名的構造器。如果不想提供,某個外部參數名,那么可以使用下劃線
_
來顯示描述外部參數名:
struct CustomColor {
// 顏色的三原色,都是Double類型
let red, green, blue: Double;
// 使用下劃線`_`來顯示描述外部參數名
init(_ red:Double,_ green:Double,_ blue:Double) {
self.red = red;
self.green = green;
self.blue = blue;
}
}
// 不帶外部參數名的構造器,調用時候跟簡潔
let magenta = CustomColor(1.0, 0.0, 1.0);
- 構造器中修改常量屬性。可以在構造過程的任意時間點修改常量屬性值,但常量屬性被賦值以后,之后都是不能再修改。另外,對于類的實例來說它的常量屬性只能在定義它的類的構造器中修改,而不能在子類中修改:
// 基類: 汽車類
class Car {
// 速度
var currentSpeed = 0.0;
// 車型別名
let alias:String;
// 車型顏色
let color:String;
// 構造器
init(_ alias:String, _ color:String) {
self.alias = alias;
self.color = color;
}
}
var myLamborghini = Car("Lamborghini","白色");
print("\(myLamborghini.alias) - \(myLamborghini.color)");
// 蘭博基尼Lamborghini
// 該類繼承Car
class Lamborghini:Car {
// 以下構造器是錯誤的,不能在子類中修改!!!
init(_ alias:String) {
self.alias = alias;
}
}
三、默認構造器
?如果結構體和類所有屬性都有默認值,而且也沒有自定義構造器,那么swift會自動給結構體和類創建一個默認的構造器。
- 類的默認構造器。默認構造器將簡單的創建一個所有屬性值都設置為默認值的實例:
class Cat {
var name:String?;
// 沒有自定義構造器,系統將自動生成一個為所有屬性設置默認值的默認構造器
// 而屬性name,是可選的,系統將自動設置默認為`nil`
}
let myCat = Cat();
- 結構體的成員逐一構造器。如果結構體對所有存儲屬性提供了默認值并且自身沒有提供定制的構造器,系統將自動添加一個成員逐一構造器。成員逐一構造器是初始化結構體新實例成員屬性的快捷方法,通過與成員屬性名相同的參數名進行傳值即可完成對成員屬性的初始化賦值:
struct Size {
// 注意: 是提供了`默認值` 并且 `沒有提供定制的構造器`,系統將自動添加一個成員逐一構造器
var width = 0.0, height = 0.0;
}
// 參數名與成員屬性名相同
let test = Size(width:30, height: 30);
四、值類型的構造器代理
?構造器代理即是構造器通過調用其他構造器來完成實例的部分構造過程,這樣能減少多個構造器間的代碼重復。
?構造器代理的實現規則與形式在值類型和類類型不同。值類型(結構體和枚舉類型)是不支持繼承的,它們只能代理給本身提供的其他構造器。類則不同,它可以繼承自其他類(參考繼承),這意味該類有必要保證其所有繼承的存儲類型屬性在構造時也能正確初始化。
?值類型,可以使用self.init
在自定義構造器中引用其他的屬于相同值類型的構造器,但只能在構造器內部調用self.init
。
?如果在值類型中自定義了構造器,那么將無法訪問默認構造器(如果是結構體,即無法訪問成員逐一構造器,參考本章節中三的內容)。
?備注:如果你有iOS基礎,下面其實就是設置視圖大小以及位置相關的幾個結構體。
// 矩形大小結構體
struct Size {
// 可以訪問成員逐一構造器
var width = 0.0, height = 0.0;
}
// 矩形中心點位置
struct Point {
// 可以訪問成員逐一構造器
var x = 0.0, y = 0.0;
}
// 矩形的大小以及位置
struct Rect {
// 位置
var origin = Point();
// 大小
var size = Size();
// 構造器1
init(origin:Point, size:Size) {
self.origin = origin;
self.size = size;
}
// 構造器2
init(center:Point, size:Size) {
let originX = center.x - (size.width / 2);
let originY = center.y - (size.height / 2);
let tempOrigin = Point(x:originX, y: originY);
// 使用`self.init`調用其他的構造器
self.init(origin:tempOrigin, size: size);
}
}
/**
矩形大小(100,100),位置(50,50)
其矩形的中心點為位置是(50+100/2, 50+100/2)
*/
// 位置: 調用成員逐一構造器,默認構造器
let origin = Point(x:50, y:50);
// 大小: 調用成員逐一構造器,默認構造器
let size = Size(width:100, height: 100);
// 寫法一
let rect1 = Rect(origin:origin, size:size);
// 寫法二
// 中心點位置
let center = Point(x: 50+100/2, y: 50+100/2);
let rect2 = Rect(center: center, size: size);
五、類的繼承和構造過程
?類里面的所有存儲型屬性,包括繼承自父類的屬性,都必須在構造過程中設置初始值。
?swift提供了兩種類型的類構造器來確保所有類實例中的存儲型屬性能獲得初始值,分別為指定構造器和便利構造器。
?指定構造器是類中最主要的構造器,一個指定構造器將初始化類中提供的所有屬性,并根據父類鏈往上調用父類的構造器來實現父類的初始化。另外每個類都必須擁有至少一個指定構造器,大多數情況下,類都通過繼承了父類中的指定構造器而滿足了這個條件。
?便利構造器是類中比較次要的、輔助型的構造器。可以定義便利構造器來調用同一類中的指定構造器,并為其參數提供默認值,也可以定義便利構造器來創建一個特殊用途或特定輸入的實例。
- 指定構造器語法,其與值類型構造器一樣:
init(parameters) {
statements
}
- 便利構造器語法,不同點在于要加上一個
convenience
關鍵字:
convenience init(parameters) {
statements
}
-
類的構造器代理規則:
1、指定構造器必須調用其直接父類的指定構造器;
2、便利構造器必須調用同一類中定義的其他構造器;
3、便利構造器必須最終以調用一個指定構造器結束;
【指定構造器必須總是向上代理;便利構造器總是橫向代理(即類中某個指定構造器)】
類的構造器代理規則
?兩段式構造過程。在swift中類的構造過程包括兩個階段,第一個階段,每個存儲類型屬性通過引入它們的類構造器設置初始值。當每個存儲屬性值確定后,第二階段開始,它給每個類一個機會在新實例準備使用之前,再進一步定制它的存儲類型屬性。
?兩段式構造器過程的使用欄構造過程更為安全,同時在整個類層次中也更為靈活。兩段式構造過程可以防止屬性值在初始化之前被訪問,也可以防止屬性被另外一個構造器意外賦予不同的值。
swift編譯器中對于兩段式構造過程進行了4中有效的安全檢查:
1、指定構造器必須保證它所在類引入的所有屬性都必須初始化完成,之后才能調用父類的構造器完成其他構造任務。例如,一個對象的內存只有在所有存儲類型屬性確定之后才能完全初始化,為了滿足這個規則,指定構造器必須保證所在類引入的屬性調用父類構造器之前先完成初始化;
2、指定構造器必須調用父類構造器,然后為繼承的屬性設置新值。如果沒有這樣做,那么指定構造器賦予的新值將會被父類中的構造器所覆蓋;
3、便利構造器必須向調用本類中的其他指定構造器,然后再為任意屬性賦新的值。如果這樣的操作,那么遍歷構造器賦予的新值將被本類中其他指定構造器所覆蓋;
4、構造器在第一階段構造完成之前,不能調用任何實例方法,也不能讀取任何實例屬性的值,且self
的值不能被引用;-
兩段式構造過程的安全檢查,階段一:
1、某個指定構造器或便利構造器被調用;
2、完成新實例內存的分配,但此時內存還沒有被初始化;
3、指定構造器確保其所在類引入的所有存儲屬性都已經被賦初始值,此時存儲類型屬性所屬的內存完成初始化;
4、指定構造器將調用父類的構造器,完成父類屬性的初始化;
5、調用父類構造器的過程將會沿著構造器鏈一直往上執行,直到到達構造器鏈的最頂部;
6、當到達了構造器最頂部,且確保所有實例包含的存儲類型屬性都已經賦值,那么實例的存儲才算是完全初始化。此時階段一完成;
安全檢查的構造流程,階段一 -
兩段式構造過程的安全檢查,階段二:
1、從頂部構造器鏈一直往下,每個構造器鏈中類的指定構造器都有機會進一步定制實例,即存儲屬性都已經被初始化過,現在可以進行修改操作。構造器此時可以訪問self
、修改屬性并調用實例方法等,因為此時實例已經完全被初始化了;
2、最終,任意構造器鏈中的便利構造器有機會定制實例和使用self
;
安全檢查的構造流程,階段二
備注: 兩段式構造過程以及安全檢查只做了解即可,了解系統的實例化過程。
- 構造器的繼承和重寫。與OC的子類不同,swift的子類不會默認繼承父類的構造器,這種機制可以防止一個父類的簡單構造器被更專業的子類繼承,并錯誤的用來創建子類的實例。當重寫指定構造器時,需要加上
override
修飾符,甚至是重寫便利構造器也需要添加該修飾符:
// 類: 人
class Person {
var name:String?
var description:String {
return "name:\(name!)";
}
// 指定構造器
init () {
// 初始化操作
name = "";
}
}
// 類: 學生,繼承Person
class Student:Person {
// 學號
var sid:Int?;
// 重寫屬性
override var description: String {
return "格式:" + super.description + " sid:\(sid!)"
}
// 重寫指定構造器,需要`override`修飾
override init() {
// 先調用父類的指定構造器,確保`name`屬性已經被初始化
super.init();
// 再修改屬性
name = "StudentName";
sid = 100_000_000_001;
}
}
// 實例化學生類
var zhansan = Student();
print(zhansan.description);
注意: 子類可以在初始化時修修改繼承來的變量屬性,但不能修改該繼承過來的常量屬性。
- 自動構造器的繼承。當滿足特定條件,父類構造器是可以被自動繼承的:
1、如果子類沒有定義任何構造器,該子類將自動繼承所有父類的指定構造器;
2、如果子類提供了所有父類指定構造器的實現,不管是通過上述規則1繼承過來的,還是通過自定義實現的,該類將自動繼承所有父類的便利構造器;
六、可失敗構造器
?當一個類、結構體或枚舉類型的對象,在構造自身的過程中有可能失敗,那么為其定義一個可失敗構造器,是非常有用的。所謂的"失敗"是指,如給構造器傳入無效的參數值,或缺少某種所需要的外部資源,又或者不滿足某種必要的條件等。你可以添加一個或多個可失敗構造器,其語法是init?
,另外通過return nil
語句,來表名可失敗構造器在某種情況下是"失敗"。
可失敗構造器的參數名和類型,不能與其非失敗構造器的參數名以及類型相同。
嚴格來說,構造器都不支持返回值。因為構造器本身的作用,只是為了確保對象自身被正確的構造器,所以即使在表明可失敗構造器,失敗的這種情況下,用到了return nil
。
class Foot {
var name:String
// 可失敗構造器
init?(name:String) {
if name.isEmpty {
// 表示"失敗"
return nil;
}
self.name = name;
}
}
// 實例化Foot對象,并檢查構造是否成功
// foot1類型是Foot?,而不是Foot
let foot1 = Foot(name:"bread");
if let temp = foot1 {
print("foot1構造成功:\(foot1!.name)");
}
// 傳入一個空值,構造失敗
let foot2 = Foot(name:"");
if let temp = foot2 {
print("foot2構造成功:\(foot2!.name)");
} else {
print("foot2構造失敗");
}
輸出結果:
foot1構造成功:bread
foot2構造失敗
- 枚舉類型的可失敗構造器。通過構造一個帶一個或多個參數的可失敗構造器來獲取枚舉類型中特定的枚舉成員,當不滿足枚舉成員期望條件時,構造失敗:
// 加減乘除枚舉
enum ArithmeticSign {
// 對應加減乘除
case Add, Subtract, Multiply, Divide;
// 可失敗構造器
init?(sign:String) {
switch sign {
case "+":
self = .Add;
case "-":
self = .Subtract;
case "*":
self = .Multiply;
case "/":
self = .Divide;
default: // 否則即是"失敗"
return nil;
}
}
}
// 枚舉1
let sign1 = ArithmeticSign(sign: "+");
if sign1 != nil {
print(sign1!);
}
// 枚舉2
let sign2 = ArithmeticSign(sign: "~");
if sign2 == nil {
print("sign2構造失敗");
}
輸出結果:
Add
sign2構造失敗
- 帶原始值的枚舉類型可失敗構造器。帶原始值的枚舉類型會自帶一個可失敗構造器
init?(rawValue:)
,該可失敗構造器有一個名字為rawValue
的默認參數,其類型和枚舉類型的原始值類型一致,如果該參數的值能夠和枚舉類型成員所帶的原始值匹配,則該構造器構造一個帶此原始值的枚舉成員,否則構造失敗:
// 加減乘除枚舉
enum ArithmeticSign:Character {
// 對應加減乘除
case Add="+", Subtract = "-", Multiply = "*", Divide = "/";
}
// 枚舉1
let sign1 = ArithmeticSign(rawValue: "+");
if sign1 != nil {
print(sign1!);
}
// 枚舉2
let sign2 = ArithmeticSign(rawValue: "~");
if sign2 == nil {
print("sign2構造失敗");
}
- 構造失敗的傳遞。可失敗構造器允許在同一類、結構體、枚舉中橫向調用其他的可失敗構造器,類似的子類的可失敗構造器也能向上調用父類的可失敗構造器。無論向上還是橫向調用,如果調用的可失敗構造器,在構建過程中觸發了構造失敗的行為,整個構造過程都將被立即終止,接下來的任何構造代碼都不會被執行。而可失敗構造器也可以調用其他的非失敗構造器,通過此方法,可以為已有的構造過程加入構造失敗的條件:
// 食物類
class Foot {
// 注意是常量
let name:String;
// 可失敗構造器
init?(name:String) {
self.name = name;
if name.isEmpty {
// 表示"失敗"
return nil;
}
}
}
// 面包類
class Bread:Foot {
let quantity:Int;
// 可失敗構造器
init?(name:String, quantity:Int) {
// 賦值
self.quantity = quantity;
// 調用父類的構造方法
super.init(name: name);
if quantity < 1 {
// "失敗"
return nil;
}
}
}
// 實例1
if let bread1 = Bread(name:"麥香面包", quantity: 1) {
print("\(bread1.name):\(bread1.quantity)");
}
// 實例2
if let bread2 = Bread(name:"麥香面包", quantity: 0) {
print("\(bread2.name):\(bread2.quantity)");
} else { // Bread觸發構建失敗行為
print("bread2構建失敗");
}
// 實例3
if let bread3 = Bread(name:"", quantity: 1) {
print("\(bread3.name):\(bread3.quantity)");
} else { // Foot觸發構建失敗行為,但整個Bread同樣還是失敗的
print("bread3構建失敗");
}
輸出結果:
麥香面包:1
bread2構建失敗
bread3構建失敗
- 重寫一個可失敗構造器。可以用子類的非可失敗構造器重寫一個積累的可失敗構造器。好處是,即使基類的構造器為可失敗的,但當子類的構造器在構造過程不可能失敗時,我們也可以把它修改過來。但是當子類的非可失敗構造器重寫了一個父類的可失敗構造器時,子類的構造器將不能再向上調用父類的可失敗構造器,另外一個非失敗的構造器永遠不能代理調用一個可失敗構造器。最后,可以用非可失敗構造器重寫一個可失敗構造器,但反過來卻行不通的:
class Foot {
// 注意是變量
var name:String?;
// 指定構造器,構建name為nil
init () {}
// 可失敗構造器,構建name為非空字符串
init?(name:String) {
self.name = name;
if name.isEmpty {
// 表示"失敗"
return nil;
}
}
}
// 面包類
class Bread:Foot {
// 重寫指定構造器
override init() {
// 調用父類的指定構造器
super.init();
// 再賦值
name = "[untitled]";
}
// 重寫一個可失敗構造器
override init(name: String) {
// 調用父類的指定構造器,即先做初始化相關操作
super.init();
if name.isEmpty {
self.name = "[untitled]";
} else {
self.name = name;
}
}
}
let bread1 = Bread(name:"");
print(bread1.name!);
let bread2 = Bread(name:"奶油面包");
print(bread2.name!);
七、必要構造器
?在類的構造器前添加required
修飾符表名所有該類的子類都必須實現該構造器:
// 基類,書寫格式
class SomeClass {
required init() {
// 在這里添加該必要構造器的實現代碼
}
}
// 子類,書寫格式
class SomeSubCalss: SomeClass {
// 子類重寫父類的必要構造器時,必須添加`required`,為了保證繼承鏈上子類的構造器也是必要的構造器。另外這里不需要添加`override`修飾符
required init() {
// 在這里添加子類必要構造器的實現代碼
}
}
注意: 如果子類繼承的構造器能滿足必要構造器的需求,則不需要再子類中提供必要構造器的實現。
八、通過閉包和函數設置屬性的默認值
?如果某個存儲類型屬性的默認值需要特定的定制操作,那么可以使用閉包或全部函數來為該屬性提供定制的默認值。每當某個屬性所屬的新類型創建時,對應的閉包或函數就會被調用,而它們的返回值會當做默認值賦值給這個屬性。
? 閉包或函數一般會創建與屬性類型相同的臨時變量,然后修改它的值以滿足預期初始狀態,最后將這個臨時變量的值作為屬性的默認值返回即可:
// 格式
class SomeClass {
let someProperty:SomeType = {
// 在這里閉包中給someProperty創建一個默認值
// someValue必須和SomeType類型相同
return someValue;
}(); // 這里后面跟著括號,表示需要立即執行此閉包操作
}
// 貓類
class Cat {
// 貓的顏色,通過閉包產生隨機顏色
var color:UIColor = {
let randomRed = Float(arc4random_uniform(255)) / 255.0;
let randomGreed = Float(arc4random_uniform(255)) / 255.0;
let randomBlue = Float(arc4random_uniform(255)) / 255.0;
let tempColor = UIColor(colorLiteralRed: randomRed, green:randomGreed, blue:randomBlue, alpha:1.0);
return tempColor;
}();
}
// 實例化
let myCat = Cat();
// 顏色是隨機顏色
print(myCat.color);
注意: 如果你使用閉包來初始化屬性的值,但在閉包執行時,實例的其他部分都還沒有初始化,即意味此時還不能夠在閉包里訪問其他的屬性,就算這個屬性有默認值也不允許,另外也不能使用隱式的
self
屬性,或調用其他的實例方法。