iOS開發之Swift篇(12)—— 構造和析構

版本

Xcode 11.3.1
Swift 5.1.3

構造過程

構造過程是使用類、結構體或枚舉類型的實例之前的準備過程。這個過程包含了爲實例中的每個屬性設置初始值和爲其執行必要的準備和初始化任務。使用關鍵字 init
對應的, 類的實例也可以通過實現析構器來執行它釋放之前自定義的清理工作, 稱爲析構過程。使用關鍵字 deinit

構造器

構造器有很多種類型(稱呼): 指定構造器(Designated)、便利構造器(Convenience)、必要構造器(required)、默認構造器、結構體的逐一成員構造器、可失敗構造器等等. 雖然名稱不同, 職能不一, 但到底還是一個構造過程. 我們挑選一些經常遇到的構造器來進行討論.

指定構造器

語法:

init(parameters) {
    statements
}

指定構造器是類中最主要的構造器。
每一個類都必須至少擁有一個指定構造器; 一個指定構造器將初始化類中提供的所有屬性。
先來看個例子:

class People {
    var age: Int
    init() {
        age = 1
    }
}

let people = People()
print("age = \(people.age)")
// age = 1

這個簡單的例子中, People只有一個存儲屬性age, 和一個指定構造器init().
在這個指定構造器 init() 中, 初始化了age這個屬性使之獲得一個初始值.
假如我們新增一個屬性 height, 而沒有在指定構造器中初始化, 那麼還將會報錯:

class People {
    var age: Int
    var height: Double
    init() {
        age = 1
        // 沒有初始化height 報錯
    }
}

上面的 init() 小括號中是不帶參數的, 其實我們也可以給指定構造器設置形參:

class People {
    var age: Int
    init(newAge: Int) {
        age = newAge
    }
    // 可以有另一個構造器
    init(aAge: Int) {
        age = aAge
    }
}

let people = People(newAge: 2)
let people1 = People(aAge: 3)
print("age = \(people.age)")
// age = 2
print("age1 = \(people1.age)")
// age1 = 3

跟函數和方法形參相同, 也可以使用外部參數名(參數標籤), 或者前面加"_"以省略參數名

便利構造器

語法:

convenience init(parameters) {
      statements
}

便利構造器是類中比較次要的、輔助型的構造器。
顧名思義, 便利構造器主要是用來提供便利的. 比方說某種情況下通過使用便利構造器來快捷調用某個指定構造器,能夠節省更多開發時間並讓類的構造過程更清晰明瞭。
便利構造器必須調用同類中的其他構造器, 而且便利構造器的最終節點必須是指定構造器. 這一點我們後面還會再介紹.
舉例, 上面People例子中我們添加幾個參數:

class People {
    
    var name: String
    var age: Int
    var height: Double
    var weight: Double

    // 指定構造器
    init(name: String, age: Int, height: Double, weight: Double) {
        self.name = name
        self.age = age
        self.height = height
        self.weight = weight
    }
    
    // 便利構造器
    convenience init() {
        self.init(name: "無名", age: 0, height: 0, weight:0)
    }
    
    // 另一個便利構造器
    convenience init(year: Int) {
        self.init(name: "小明", age: year-2000, height: Double((year-2000))*10.0, weight: Double((year-2000))*5.0)
    }
}

let people = People()
print("name = \(people.name), age = \(people.age)")
// name = 無名, age = 0

let people1 = People(year: 2020)
print("name = \(people1.name), age = \(people1.age), height = \(people1.height), weight = \(people1.weight)")
// name = 小明, age = 20, height = 200.0, weight = 100.0

兩個便利構造器中都調用了指定構造器. 實例化people沒有輸入參數, 調用的是第一個便利構造器, 而people1調用的是第二個構造器, 他們之間根據傳入的參數類型和數量來匹配.
people1中只傳入年份就能得到人的年齡身高體重等, 達到便利構造目的. 這個例子給的不太恰當, 人的身高體重不是每年均等增長的. 主要是想說明便利構造和指定構造之間的調用關係.

默認構造器

如果結構體或類爲所有屬性提供了默認值,又沒有提供任何自定義的構造器,那麼 Swift 會給這些結構體或類提供一個默認構造器。
例如:

class People {
    var age = 1
    var height = 10.0
    var weight = 20.0
}

let people = People()
print("age = \(people.age), height = \(people.height), weight = \(people.weight)")
// age = 1, height = 10.0, weight = 20.0

People類中的所有屬性都有默認值,且它是沒有父類的基類,它將自動獲得一個將爲所有屬性設置默認值的並創建實例的默認構造器。使用 People() 形式的構造器語法,並將其賦值給常量people。

結構體的逐一成員構造器

結構體如果沒有定義任何自定義構造器,它們將自動獲得一個逐一成員構造器(memberwise initializer)。不像默認構造器,即使存儲型屬性沒有默認值,結構體也能會獲得逐一成員構造器。

struct Coord {
    var x = 0
    var y = 0
    var z = 0
}

let coord = Coord(x: 1, y: 2, z: 3)
print(coord.x, coord.y, coord.z)
// 1 2 3

let coord1 = Coord(x: 1)
print(coord1.x, coord1.y, coord1.z)
// 1 0 0

let coord2 = Coord(y: 2)
print(coord2.x, coord2.y, coord2.z)
// 0 2 0

let coord3 = Coord(z: 3)
print(coord3.x, coord3.y, coord3.z)
// 0 0 3

let coord4 = Coord(x: 1, y: 2)
print(coord4.x, coord4.y, coord4.z)
// 1 2 0

或者不賦任何初始值, 但創建實例的時候必須窮舉:

struct Coord {
    var x: Int
    var y: Int
    var z: Int
}

let coord = Coord(x: 10, y: 20, z: 30)
print(coord.x, coord.y, coord.z)
// 10 20 30

注意, 只適用於結構體struct中, 而不適用於類class中

可失敗的構造器

如果一個類、結構體或枚舉類型的對象,在構造自身的過程中有可能失敗,則爲其定義一個可失敗構造器。語法爲在 init 關鍵字後面添加問號(init?)。
可失敗的原因可能有:

  • 傳入無效的參數值。
  • 缺少某種所需的外部資源。
  • 沒有滿足特定條件。

注意
可失敗構造器的參數名和參數類型,不能與其它非可失敗構造器的參數名,及其參數類型相同。

值類型與類類型的可失敗構造器有以下區別:

  • 值類型(結構體或枚舉)的可失敗構造器,對何時何地觸發構造失敗這個行爲沒有任何的限制
  • 類的可失敗構造器只能在所有的類屬性被初始化後和所有類之間的構造器之間的代理調用發生完後觸發失敗行爲。

1.值類型可失敗構造器
你可以通過一個帶一個或多個形參的可失敗構造器來獲取枚舉類型中特定的枚舉成員。如果提供的形參無法匹配任何枚舉成員,則構造失敗。

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("這不是一個已定義的溫度單位,所以初始化失敗。")
}
// 打印 這不是一個已定義的溫度單位,所以初始化失敗。

2. 類類型可失敗構造器
無論是向上代理還是橫向代理,如果你代理到的其他可失敗構造器觸發構造失敗,整個構造過程將立即終止,接下來的任何構造代碼不會再被執行。

注意
可失敗構造器也可以代理到其它的不可失敗構造器。通過這種方式,你可以增加一個可能的失敗狀態到現有的構造過程中。

下面這個例子,定義了一個名爲 CartItem 的 Product 類的子類。這個類建立了一個在線購物車中的物品的模型,它有一個名爲 quantity 的常量存儲型屬性,並確保該屬性的值至少爲 1:

class Product {
    let name: String
    init?(name: String) {
        if name.isEmpty { return nil }
        self.name = name
    }
}

class CartItem: Product {
    let quantity: Int
    init?(name: String, quantity: Int) {
        if quantity < 1 { return nil }
        self.quantity = quantity
        super.init(name: name)
    }
}


// 傳入一個非空字符串 name 以及一個值大於等於 1 的 quantity, 構造成功
if let twoSocks = CartItem(name: "sock", quantity: 2) {
    print("Item: \(twoSocks.name), quantity: \(twoSocks.quantity)")
}
// 打印“Item: sock, quantity: 2”


// 傳入一個值爲 0 的 quantity ,構造失敗
if let zeroShirts = CartItem(name: "shirt", quantity: 0) {
    print("Item: \(zeroShirts.name), quantity: \(zeroShirts.quantity)")
} else {
    print("Unable to initialize zero shirts")
}
// 打印“Unable to initialize zero shirts”


// 傳入一個值爲空字符串的 name,那麼將導致父類 Product 的構造過程失敗
if let oneUnnamed = CartItem(name: "", quantity: 1) {
    print("Item: \(oneUnnamed.name), quantity: \(oneUnnamed.quantity)")
} else {
    print("Unable to initialize one unnamed product")
}
// 打印“Unable to initialize one unnamed product”

構造器代理

構造器可以通過調用其它構造器來完成實例的部分構造過程。這一過程稱爲構造器代理,它能減少多個構造器間的代碼重複。
構造器代理的實現規則和形式在值類型和類類型中有所不同:

  • 值類型(結構體和枚舉類型)不支持繼承,所以構造器代理的過程相對簡單,因爲它們只能代理給自己的其它構造器。
  • 類類型可以繼承自其它類,這意味着類有責任保證其所有繼承的存儲型屬性在構造時也能正確的初始化。類類型構造器代理允許子類調用父類的指定構造器。
值類型的構造器代理

你可以使用 self.init 在自定義的構造器中引用相同類型中的其它構造器。並且你只能在構造器內部調用 self.init。
舉例:

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) ")
print("Rect 結構體初始值: \(basicRect.origin) ")

// 將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) ")
print("Rect 結構體初始值: \(originRect.origin) ")

// 先通過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) ")
print("Rect 結構體初始值: \(centerRect.origin) ")

/**
 Size 結構體初始值: Size(width: 0.0, height: 0.0)
 Rect 結構體初始值: Point(x: 0.0, y: 0.0)
 Size 結構體初始值: Size(width: 5.0, height: 5.0)
 Rect 結構體初始值: Point(x: 2.0, y: 2.0)
 Size 結構體初始值: Size(width: 3.0, height: 3.0)
 Rect 結構體初始值: Point(x: 2.5, y: 2.5) 
 */
類類型的構造器代理

爲了簡化指定構造器和便利構造器之間的調用關係,Swift 構造器之間的代理調用遵循以下三條規則:

  1. 指定構造器必須調用其直接父類的的指定構造器。
  2. 便利構造器必須調用同類中定義的其它構造器。
  3. 便利構造器最後必須調用指定構造器。

也可以總結爲以下兩點:

  • 指定構造器必須總是向上代理
  • 便利構造器必須總是橫向代理

一圖以蔽之:
類類型構造器代理.png

析構過程

析構器只適用於類類型,當一個類的實例被釋放之前,析構器會被立即調用。析構器用關鍵字 deinit 來標示,類似於構造器要用 init 來標示。

語法:

deinit {
    // 執行析構過程
}

析構過程原理

Swift 會自動釋放不再需要的實例以釋放資源。
Swift 通過自動引用計數(ARC)處理實例的內存管理。
通常當你的實例被釋放時不需要手動地去清理。但是,當使用自己的資源時,你可能需要進行一些額外的清理。
例如,如果創建了一個自定義的類來打開一個文件,並寫入一些數據,你可能需要在類實例被釋放之前關閉該文件。

實例

var counter = 0;  // 引用計數器
class BaseClass {
    init() {
        counter += 1;
    }
    deinit {
        counter -= 1;
    }
}

var show: BaseClass? = BaseClass()
print(counter)
show = nil
print(counter)

/**
 1
 0
 */
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章