Swift 建構過程


建構過程是為了使用某個類別、結構體或枚舉類型的實例而進行的準備過程。這個過程包含了為實例中的每個屬性設定初始值和為其執行必要的準備和初始化任務。

Swift 建構子使用 init() 方法。

與 Objective-C 中的建構器不同,Swift 的建構器無需傳回值,它們的主要任務是保證新實例在第一次使用前完成正確的初始化。

類別實例也可以透過定義析構器(deinitializer)在類別實例釋放之前執行清理記憶體的工作。



儲存型屬性的初始賦值

#類別和結構體在實例建立時,必須為所有儲存型屬性設定適當的初始值。

儲存屬性在建構器中賦值時,它們的值是直接設定的,不會觸發任何屬性觀測器。

儲存屬性在建構器中賦值流程:

  • 建立初始值。

  • 在屬性定義中指定預設屬性值。

  • 初始化實例,並呼叫 init() 方法。


建構器

建構子在建立某特定型別的新實例時呼叫。它的最簡形式類似於一個不帶任何參數的實例方法,以關鍵字init命名。

語法

init()
{
    // 实例化后执行的代码
}

實例

以下結構體定義了一個不帶參數的建構器init,並在裡面將儲存型屬性length 和breadth 的值初始化為6與12:

struct rectangle {
    var length: Double
    var breadth: Double
    init() {
        length = 6
        breadth = 12
    }
}
var area = rectangle()
print("矩形面积为 \(area.length*area.breadth)")

以上程式執行輸出結果為:

矩形面积为 72.0

#預設屬性值

我們可以在建構器中為儲存型屬性設定初始值;同樣,也可以在屬性聲明時為其設定預設值。

使用預設值能讓你的建構器更簡潔、更清晰,並且能透過預設值自動推導出屬性的類型。

以下實例我們在屬性宣告時為其設定預設值:

struct rectangle {
	// 设置默认值
    var length = 6
    var breadth = 12
}
var area = rectangle()
print("矩形的面积为 \(area.length*area.breadth)")

以上程式執行輸出結果為:

矩形面积为 72

建構參數

你可以在定義建構器init() 時提供建構參數,如下所示:

struct Rectangle {
    var length: Double
    var breadth: Double
    var area: Double
    
    init(fromLength length: Double, fromBreadth breadth: Double) {
        self.length = length
        self.breadth = breadth
        area = length * breadth
    }
    
    init(fromLeng leng: Double, fromBread bread: Double) {
        self.length = leng
        self.breadth = bread
        area = leng * bread
    }
}

let ar = Rectangle(fromLength: 6, fromBreadth: 12)
print("面积为: \(ar.area)")

let are = Rectangle(fromLeng: 36, fromBread: 12)
print("面积为: \(are.area)")

以上程式執行輸出結果為:

面积为: 72.0
面积为: 432.0

內部與外部參數名

跟函數和方法參數相同,建構參數也存在一個在建構器內部使用的參數名字和一個在呼叫建構器時使用的外部參數名字。

然而,構造器並不像函數和方法在括號前有一個可辨識的名字。所以在呼叫構造器時,主要透過構造器中的參數名稱和型別來確定需要呼叫的構造器。

如果你在定義建構器時沒有提供參數的外部名字,Swift 會為每個建構器的參數自動產生一個跟內部名字相同的外部名。

struct Color {
    let red, green, blue: Double
    init(red: Double, green: Double, blue: Double) {
        self.red   = red
        self.green = green
        self.blue  = blue
    }
    init(white: Double) {
        red   = white
        green = white
        blue  = white
    }
}

// 创建一个新的Color实例,通过三种颜色的外部参数名来传值,并调用构造器
let magenta = Color(red: 1.0, green: 0.0, blue: 1.0)

print("red 值为: \(magenta.red)")
print("green 值为: \(magenta.green)")
print("blue 值为: \(magenta.blue)")

// 创建一个新的Color实例,通过三种颜色的外部参数名来传值,并调用构造器
let halfGray = Color(white: 0.5)
print("red 值为: \(halfGray.red)")
print("green 值为: \(halfGray.green)")
print("blue 值为: \(halfGray.blue)")

以上程式執行輸出結果為:

red 值为: 1.0
green 值为: 0.0
blue 值为: 1.0
red 值为: 0.5
green 值为: 0.5
blue 值为: 0.5

沒有外部名稱參數

如果你不希望為建構器的某個參數提供外部名字,你可以使用底線_來顯示描述它的外部名稱。

struct Rectangle {
    var length: Double
    
    init(frombreadth breadth: Double) {
        length = breadth * 10
    }
    
    init(frombre bre: Double) {
        length = bre * 30
    }
    //不提供外部名字
    init(_ area: Double) {
        length = area
    }
}

// 调用不提供外部名字
let rectarea = Rectangle(180.0)
print("面积为: \(rectarea.length)")

// 调用不提供外部名字
let rearea = Rectangle(370.0)
print("面积为: \(rearea.length)")

// 调用不提供外部名字
let recarea = Rectangle(110.0)
print("面积为: \(recarea.length)")

以上程式執行輸出結果為:

面积为: 180.0
面积为: 370.0
面积为: 110.0

可選屬性類型

如果你自訂的型別包含一個邏輯上允許取值為空的儲存型屬性,你都需要將它定義為可選型別optional type(可選屬性類型)。

當儲存屬性宣告為可選時,將自動初始化為空 nil。

struct Rectangle {
    var length: Double?
    
    init(frombreadth breadth: Double) {
        length = breadth * 10
    }
    
    init(frombre bre: Double) {
        length = bre * 30
    }
    
    init(_ area: Double) {
        length = area
    }
}

let rectarea = Rectangle(180.0)
print("面积为:\(rectarea.length)")

let rearea = Rectangle(370.0)
print("面积为:\(rearea.length)")

let recarea = Rectangle(110.0)
print("面积为:\(recarea.length)")

以上程式執行輸出結果為:

面积为:Optional(180.0)
面积为:Optional(370.0)
面积为:Optional(110.0)

建構過程中修改常數屬性

#只要在建構過程結束前常數的值能確定,你可以在構造過程中的任意時間點修改常數屬性的值。

對某個類別實例來說,它的常數屬性只能在定義它的類別的建構過程中修改;不能在子類別中修改。

儘管length 屬性現在是常數,我們仍然可以在其類別的建構器中設定它的值:

struct Rectangle {
    let length: Double?
    
    init(frombreadth breadth: Double) {
        length = breadth * 10
    }
    
    init(frombre bre: Double) {
        length = bre * 30
    }
    
    init(_ area: Double) {
        length = area
    }
}

let rectarea = Rectangle(180.0)
print("面积为:\(rectarea.length)")

let rearea = Rectangle(370.0)
print("面积为:\(rearea.length)")

let recarea = Rectangle(110.0)
print("面积为:\(recarea.length)")

以上程式執行輸出結果為:

面积为:Optional(180.0)
面积为:Optional(370.0)
面积为:Optional(110.0)

#預設建構器

預設建構子將簡單的建立一個所有屬性值都設為預設值的實例:

以下實例中,ShoppingListItem類別中的所有屬性都有預設值,而且它是沒有父類的基類,它將自動獲得一個可以為所有屬性設定預設值的預設建構器

class ShoppingListItem {
    var name: String?
    var quantity = 1
    var purchased = false
}
var item = ShoppingListItem()


print("名字为: \(item.name)")
print("数理为: \(item.quantity)")
print("是否付款: \(item.purchased)")

以上程式執行輸出結果為:

名字为: nil
数理为: 1
是否付款: false

結構體的逐一成員建構器

如果結構體對所有儲存型屬性提供了預設值且本身沒有提供客製化的建構器,它們能自動獲得一個逐一成員建構器。

我們在呼叫逐一成員建構器時,透過與成員屬性名稱相同的參數名稱進行傳值來完成對成員屬性的初始賦值。

下面範例中定義了一個結構體 Rectangle,它包含兩個屬性 length 和 breadth。 Swift 可以根據這兩個屬性的初始賦值100.0 、200.0自動推導出它們的型別Double。

struct Rectangle {
    var length = 100.0, breadth = 200.0
}
let area = Rectangle(length: 24.0, breadth: 32.0)

print("矩形的面积: \(area.length)")
print("矩形的面积: \(area.breadth)")

由於這兩個儲存型屬性都有預設值,結構體 Rectangle 自動獲得了一個逐一成員建構器 init(width:height:)。 你可以用它來為 Rectangle 建立新的實例。

以上程式執行輸出結果為:

名字为: nil
矩形的面积: 24.0
矩形的面积: 32.0

值類型的建構器代理程式

建構器可以透過呼叫其它建構器來完成實例的部分建構過程。這個過程稱為構造器代理,它能減少多個構造器間的程式碼重複。

以下實例中,Rect 結構體呼叫了Size 和Point 的建構程序:

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()
    init() {}
    init(origin: Point, size: Size) {
        self.origin = origin
        self.size = size
    }
    init(center: Point, size: Size) {
        let originX = center.x - (size.width / 2)
        let originY = center.y - (size.height / 2)
        self.init(origin: Point(x: originX, y: originY), size: size)
    }
}


// origin和size属性都使用定义时的默认值Point(x: 0.0, y: 0.0)和Size(width: 0.0, height: 0.0):
let basicRect = Rect()
print("Size 结构体初始值: \(basicRect.size.width, basicRect.size.height) ")
print("Rect 结构体初始值: \(basicRect.origin.x, basicRect.origin.y) ")

// 将origin和size的参数值赋给对应的存储型属性
let originRect = Rect(origin: Point(x: 2.0, y: 2.0),
    size: Size(width: 5.0, height: 5.0))

print("Size 结构体初始值: \(originRect.size.width, originRect.size.height) ")
print("Rect 结构体初始值: \(originRect.origin.x, originRect.origin.y) ")


//先通过center和size的值计算出origin的坐标。
//然后再调用(或代理给)init(origin:size:)构造器来将新的origin和size值赋值到对应的属性中
let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
    size: Size(width: 3.0, height: 3.0))

print("Size 结构体初始值: \(centerRect.size.width, centerRect.size.height) ")
print("Rect 结构体初始值: \(centerRect.origin.x, centerRect.origin.y) ")

以上程式執行輸出結果為:

Size 结构体初始值: (0.0, 0.0) 
Rect 结构体初始值: (0.0, 0.0) 
Size 结构体初始值: (5.0, 5.0) 
Rect 结构体初始值: (2.0, 2.0) 
Size 结构体初始值: (3.0, 3.0) 
Rect 结构体初始值: (2.5, 2.5)

建構子代理程式規則

值型別類別型別
#不支援繼承,所以建構子代理程式的過程相對簡單,因為它們只能代理給本身提供的其它構造器。 你可以使用self.init在自訂的建構器中引用其它的屬於相同值類型的建構器。 它可以繼承自其它類別,這表示類別有責任保證其所有繼承的儲存型屬性在建構時也能正確的初始化。

類別的繼承和建構過程

Swift 提供了兩種類型的類別建構器來確保所有類別實例中儲存型屬性都能獲得初始值,它們分別是指定建構器和便利構造器。

指定建構子 便利建構器
類別中最主要的建構子類別中比較次要的、輔助型的建構器
初始化類別中提供的所有屬性,並根據父類別鏈往上呼叫父類別的建構器來實作父類別的初始化。 可以定義便利建構器來呼叫同一個類別中的指定建構器,並為其參數提供預設值。你也可以定義便利建構器來建立一個特殊用途或特定輸入的實例。
每一個類別都必須擁有至少一個指定建構器只在必要的時候為類別提供便利建構器
#
Init(parameters) {
    statements
}
convenience init(parameters) {
      statements
}

指定建構器實例

class mainClass {
    var no1 : Int // 局部存储变量
    init(no1 : Int) {
        self.no1 = no1 // 初始化
    }
}
class subClass : mainClass {
    var no2 : Int // 新的子类存储变量
    init(no1 : Int, no2 : Int) {
        self.no2 = no2 // 初始化
        super.init(no1:no1) // 初始化超类
    }
}

let res = mainClass(no1: 10)
let res2 = subClass(no1: 10, no2: 20)

print("res 为: \(res.no1)")
print("res2 为: \(res2.no1)")
print("res2 为: \(res2.no2)")

以上程式執行輸出結果為:

res 为: 10
res 为: 10
res 为: 20

便利建構器實例

class mainClass {
    var no1 : Int // 局部存储变量
    init(no1 : Int) {
        self.no1 = no1 // 初始化
    }
}

class subClass : mainClass {
    var no2 : Int
    init(no1 : Int, no2 : Int) {
        self.no2 = no2
        super.init(no1:no1)
    }
    // 便利方法只需要一个参数
    override convenience init(no1: Int)  {
        self.init(no1:no1, no2:0)
    }
}
let res = mainClass(no1: 20)
let res2 = subClass(no1: 30, no2: 50)

print("res 为: \(res.no1)")
print("res2 为: \(res2.no1)")
print("res2 为: \(res2.no2)")

以上程式執行輸出結果為:

res 为: 20
res2 为: 30
res2 为: 50

建構器的繼承和重載

Swift 中的子類別不會預設繼承父類別的建構器。

父類別的建構器僅在確定和安全的情況下被繼承。

當你重寫一個父類別指定建構器時,你需要寫override修飾符。

class SuperClass {
    var corners = 4
    var description: String {
        return "\(corners) 边"
    }
}
let rectangle = SuperClass()
print("矩形: \(rectangle.description)")

class SubClass: SuperClass {
    override init() {  //重载构造器
        super.init()
        corners = 5
    }
}

let subClass = SubClass()
print("五角型: \(subClass.description)")

以上程式執行輸出結果為:

矩形: 4 边
五角型: 5 边

指定建構器與便利建構器實例

接下來的範例將在操作中展示指定建構器、便利構造器和自動構造器的繼承。

它定義了包含兩個個類別MainClass、SubClass的類別層次結構,並將示範它們的建構器是如何相互作用的。

class MainClass {
    var name: String
    
    init(name: String) {
        self.name = name
    }
    
    convenience init() {
        self.init(name: "[匿名]")
    }
}
let main = MainClass(name: "php")
print("MainClass 名字为: \(main.name)")

let main2 = MainClass()
print("没有对应名字: \(main2.name)")

class SubClass: MainClass {
    var count: Int
    init(name: String, count: Int) {
        self.count = count
        super.init(name: name)
    }
    
    override convenience init(name: String) {
        self.init(name: name, count: 1)
    }
}

let sub = SubClass(name: "php")
print("MainClass 名字为: \(sub.name)")

let sub2 = SubClass(name: "php", count: 3)
print("count 变量: \(sub2.count)")

以上程式執行輸出結果為:

MainClass 名字为: php
没有对应名字: [匿名]
MainClass 名字为: php
count 变量: 3

類的可失敗建構器

如果一個類,結構體或枚舉類型的對象,在構造自身的過程中有可能失敗,則為其定義一個可失敗構造器。

變數初始化失敗可能的原因有:

  • 傳入無效的參數值。

  • 缺少某種所需的外部資源。

  • 沒有滿足特定條件。

為了妥善處理這種建構過程中可能會失敗的情況。

你可以在一個類,結構體或是枚舉類型的定義中,增加一個或多個可失敗建構器。其語法為在init關鍵字後面加添問號(init?)。

實例

下例中,定義了一個名為Animal的結構體,其中有一個名為species的,String類型的常數屬性。

同時該結構體也定義了一個,帶有一個String類型參數species的,可失敗建構器。這個可失敗建構器,用來檢查傳入的參數是否為一個空字串,如果為空字串,則該可失敗建構器,建構物件失敗,否則成功。

struct Animal {
    let species: String
    init?(species: String) {
        if species.isEmpty { return nil }
        self.species = species
    }
}

//通过该可失败构造器来构建一个Animal的对象,并检查其构建过程是否成功
// someCreature 的类型是 Animal? 而不是 Animal
let someCreature = Animal(species: "长颈鹿")

// 打印 "动物初始化为长颈鹿"
if let giraffe = someCreature {
    print("动物初始化为\(giraffe.species)")
}

以上程式執行輸出結果為:

动物初始化为长颈鹿

枚舉類型的可失敗建構器

你可以透過建構一個帶有一個或多個參數的可失敗建構器來取得枚舉類型中特定的枚舉成員。

實例

下例中,定義了一個名為TemperatureUnit的列舉型別。其中包含了三個可能的枚舉成員(Kelvin,Celsius,和Fahrenheit)和一個被用來找到Character值所對應的枚舉成員的可失敗建構器:

enum TemperatureUnit {
	// 开尔文,摄氏,华氏
    case Kelvin, Celsius, Fahrenheit
    init?(symbol: Character) {
        switch symbol {
        case "K":
            self = .Kelvin
        case "C":
            self = .Celsius
        case "F":
            self = .Fahrenheit
        default:
            return nil
        }
    }
}


let fahrenheitUnit = TemperatureUnit(symbol: "F")
if fahrenheitUnit != nil {
    print("这是一个已定义的温度单位,所以初始化成功。")
}

let unknownUnit = TemperatureUnit(symbol: "X")
if unknownUnit == nil {
    print("这不是一个已定义的温度单位,所以初始化失败。")
}

以上程式執行輸出結果為:

这是一个已定义的温度单位,所以初始化成功。
这不是一个已定义的温度单位,所以初始化失败。

類別的可失敗建構器

值型別(如結構體或列舉型別)的可失敗建構器,對何時何地觸發建構失敗這個行為沒有任何的限制。

但是,類別的可失敗建構器只能在所有的類別屬性被初始化後和所有類別之間的建構器之間的代理呼叫發生完後觸發失敗行為。

實例

下範例中,定義了一個名為 StudRecord 的類,因為 studname 屬性是一個常數,所以一旦 StudRecord 類別建構成功,studname 屬性肯定有一個非nil的值。

class StudRecord {
    let studname: String!
    init?(studname: String) {
        self.studname = studname
        if studname.isEmpty { return nil }
    }
}
if let stname = StudRecord(studname: "失败构造器") {
    print("模块为 \(stname.studname)")
}

以上程式執行輸出結果為:

模块为 失败构造器

覆寫一個可失敗建構器

就如同其它建構器一樣,你也可以用子類別的可失敗建構器覆蓋基底類別的可失敗建構器。

者你也可以用子類別的非可失敗建構器覆寫一個基底類別的可失敗建構器。

你可以用一個非可失敗建構器覆寫一個可失敗建構器,但反過來卻行不通。

一個非可失敗的建構器永遠也不能代理呼叫一個可失敗建構器。

實例

以下實例描述了可失敗與非可失敗建構器:

class Planet {
    var name: String
    
    init(name: String) {
        self.name = name
    }
    
    convenience init() {
        self.init(name: "[No Planets]")
    }
}
let plName = Planet(name: "Mercury")
print("行星的名字是: \(plName.name)")

let noplName = Planet()
print("没有这个名字的行星: \(noplName.name)")

class planets: Planet {
    var count: Int
    
    init(name: String, count: Int) {
        self.count = count
        super.init(name: name)
    }
    
    override convenience init(name: String) {
        self.init(name: name, count: 1)
    }
}

以上程式執行輸出結果為:

行星的名字是: Mercury
没有这个名字的行星: [No Planets]

可失敗建構器init!

通常來說我們透過在init關鍵字後面加上問號的方式(init?)來定義一個可失敗建構器,但你也可以使用透過在init後面加上驚嘆號的方式來定義一個可失敗建構器(init!)。實例如下:

struct StudRecord {
    let stname: String
    
    init!(stname: String) {
        if stname.isEmpty {return nil }
        self.stname = stname
    }
}

let stmark = StudRecord(stname: "php")
if let name = stmark {
    print("指定了学生名")
}

let blankname = StudRecord(stname: "")
if blankname == nil {
    print("学生名为空")
}

以上程式執行輸出結果為:

指定了学生名
学生名为空