深刻了解Swift中的初始化(Initialization)

Swift version:5.0安全

初始化是什麼

初始化簡而言之是一個準備的過程,就比如你想吃地三鮮,這時候你光在腦海裏想,你是吃不到的,你須要買菜、洗菜、切菜、炒菜,而後你才能吃上地三鮮。初始化就至關於買菜、洗菜、切菜、炒菜的過程。回到代碼上面,它主要作了下面兩件事:bash

  • 給每個存儲屬性賦初始值
  • 執行其餘必須的設置

代碼示例:ide

class PotatoPepperEggplant {
    let potato: String
    let pepper: String
    let eggplant: String
    
    init(potato: String, pepper: String, eggplant: String) {
        //給每個存儲屬性賦初始值
        self.potato = potato
        self.pepper = pepper
        self.eggplant = eggplant
        //執行其餘必須的設置
        cook()
    }
    
    func cook() {
        //do something
    }
}

let ppe = PotatoPepperEggplant(potato: "🥔", pepper: "🌶", eggplant: "🍆")
print("now you can eat\(ppe)")
複製代碼

除了上面的方式,咱們還能夠經過設置默認值的方式來給存儲屬性賦值。ui

class PotatoPepperEggplant {
    let potato = "🥔"
    let pepper = "🌶"
    let eggplant = "🍆"
    
    init() {
        cook()
    }
    
    func cook() {}
}

let ppe = PotatoPepperEggplant()
複製代碼

如今,咱們知道了初始化就是執行構造器的過程,下面咱們來看一下默認構造器和建立自定義構造器的幾種方式。this

默認構造器

對於值類型和引用類型,默認構造器是不一樣的。若是 class 給全部的存儲屬性賦了默認值,且沒有實現任何自定義的構造器,那麼 Swift 會提供一個默認的構造器。spa

class

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

而對於 struct ,只要沒有實現任何自定義構造器,無論它有沒有給存儲屬性賦默認值, Swift 都會提供默認構造器。代理

struct

struct Size {
    //var width, height: Double 也會提供默認構造器
    var width = 0.0, height = 0.0
}
let twoByTwo = Size(width: 2.0, height: 2.0)
複製代碼

當你給存儲屬性分配默認值或者經過構造器設置初始值的時候,屬性的值被直接設置,不會觸發屬性觀察code

建立自定義構造器的幾種方式

形參(parameter)構造器

struct Celsius {
    var temperatureInCelsius: Double
    init(fromFahrenheit fahrenheit: Double) {
        temperatureInCelsius = (fahrenheit - 32.0) / 1.8
    }
    init(fromKelvin kelvin: Double) {
        temperatureInCelsius = kelvin - 273.15
    }
}

let boilingPointOfWater = Celsius(fromFahrenheit: 212.0)
// boilingPointOfWater.temperatureInCelsius 是 100.0
let freezingPointOfWater = Celsius(fromKelvin: 273.15)
// freezingPointOfWater.temperatureInCelsius 是 0.0
複製代碼

形參(parameter)和實參(argument)構造器

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
    }
}

let magenta = Color(red: 1.0, green: 0.0, blue: 1.0)
let halfGray = Color(white: 0.5)
複製代碼

若是聲明瞭實參名稱,調用的時候不能省略實參名稱。cdn

let veryGreen = Color(0.0, 1.0, 0.0)
// this reports a compile-time error - argument labels are required
複製代碼

若是你在定義構造器時沒有提供實參標籤,Swift 會爲構造器的每一個形參自動生成一個實參標籤。blog

不帶實參的形參構造器

若是你不但願構造器的某個形參使用實參標籤,可使用下劃線(_)來代替顯式的實參標籤來重寫默認行爲。

struct Celsius {
    var temperatureInCelsius: Double
    init(fromFahrenheit fahrenheit: Double) {
        temperatureInCelsius = (fahrenheit - 32.0) / 1.8
    }
    init(fromKelvin kelvin: Double) {
        temperatureInCelsius = kelvin - 273.15
    }
    init(_ celsius: Double) {
        temperatureInCelsius = celsius
    }
}
let bodyTemperature = Celsius(37.0)
// bodyTemperature.temperatureInCelsius is 37.0
複製代碼

有可選類型屬性的構造器

class SurveyQuestion {
    var text: String
    var response: String?
    init(text: String) {
        self.text = text
    }
    func ask() {
        print(text)
    }
}
let cheeseQuestion = SurveyQuestion(text: "Do you like cheese?")
cheeseQuestion.ask()
// Prints "Do you like cheese?"
cheeseQuestion.response = "Yes, I do like cheese."
複製代碼

有常量屬性的構造器

常量屬性,只能在構造器中被賦值,且一旦賦值就不可修改。子類中也不能修改。

class SurveyQuestion {
    let text: String
    var response: String?
    init(text: String) {
        self.text = text
    }
    func ask() {
        print(text)
    }
}
let beetsQuestion = SurveyQuestion(text: "How about beets?")
beetsQuestion.ask()
// 打印「How about beets?」
beetsQuestion.response = "I also like beets. (But not with cheese.)"
複製代碼

到這裏,咱們瞭解了默認構造器和建立自定義構造器的幾種方式,接下來咱們看一下若是使用構造器代理( Initializer Delegation )來避免多個構造器的代碼重複。

由於值類型是不能繼承的,因此構造器代理又分爲值類型的構造器代理和類的構造器代理,咱們先看一下比較簡單的值類型的構造器代理。

值類型的構造器代理

對於值類型,你可使用 self.init 在自定義的構造器中引用相同類型中的其它構造器。而且你只能在構造器內部調用 self.init。還有就是:若是你爲某個值類型定義了一個自定義的構造器,你將沒法訪問到默認構造器。這主要是爲了不在一個構造器中作了一些重要設置,但有人不當心使用自動生成的構造器而致使錯誤的狀況。

若是你想讓默認構造器、自定義的構造器均可以使用的話,你能夠將自定義的構造器放在Extension中。

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)
    }
}
複製代碼

經過上面的代碼你可使用init()init(origin: Point, size: Size)init(center: Point, size: Size)三種方式來初始化 Rect 的實例。

看完上面的代碼你也許會有疑問:init(origin: Point, size: Size)和默認構造器是同樣的,那爲何咱們還要再寫一遍?那是由於咱們自定義了init(center: Point, size: Size)構造器,因此默認構造器已經失效,咱們只能再本身寫一遍。

若是你不想本身寫一遍默認構造器的話,能夠用下面這種方式實現上面等效的代碼:

extension Rect {
    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)
    }
}
複製代碼

類的構造器代理

爲了確保實例中的全部存儲屬性都能有初始值, Swift 提供了兩種構造器,分別是:指定構造器、便利構造器。

指定構造器( Designated Initializers )

定義一個指定構造器:

init(parameters) {
    statements
}
複製代碼

便利構造器( Convenience Initializers )

定義一個便利構造器:

convenience init(parameters) {
    statements
}
複製代碼

爲了簡化指定構造器和便利構造器之間的調用關係,Swift 構造器之間的代理調用須要遵循類類型的構造器代理規則。

類類型的構造器代理規則

規則有三條,分別是:

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

總結一下就是:指定構造器必須老是向上代理(去父類);便利構造器必須老是橫向代理(在本類)。以下圖所示:

構造器的兩個階段

Swift 中類的構造過程包含兩個階段。第一個階段:給類中的每一個存儲屬性賦初始值。只要每一個存儲屬性初始值被賦值,第二階段開始,它給每一個類一次機會,在新實例準備使用以前進一步自定義它們的存儲屬性。

Swift 經過4步安全檢查來肯定構造器兩個階段的成功執行:

  • 安全檢查1:指定構造器必須在完成本類全部存儲屬性賦值以後,才能向上代理到父類的構造器。
class Animal {
    var head = 1
}

class Dog: Animal {
    var foot: Int
    override init() {
        super.init()
        foot = 4
    }
}
複製代碼

上面的super.init()會報錯,由於此時 Dog 的 foot 尚未被賦值。將 init() 改成下面便可:

override init() {
    foot = 4
    //這句也能夠省略,它默認是隱式調用的。
    super.init()
}
複製代碼
  • 安全檢查2:指定構造器必須在爲繼承的屬性設置新值以前向上代理調用父類構造器。
//這時,你必須顯式的調用super.init(),由於你要修改繼承屬性- head 的值
override init() {
    foot = 4
    super.init()
    head = 2
}
複製代碼
  • 安全檢查3:便利構造器必須先調用其餘構造器,再爲任意屬性(包括全部同類中定義的)賦新值。
convenience init(foot: Int) {
    //先調用其餘構造器,若是此處不調用會編譯出錯
    self.init()
    //再爲任意屬性(包括全部同類中定義的)賦新值
    self.foot = foot
    head = 3
}
複製代碼
  • 安全檢查4:構造器在第一階段構造完成以前,不能調用任何實例方法,不能讀取任何實例屬性的值,不能引用 self 做爲一個值。
class Dog: Animal {
    var foot: Int
    override init() {
        foot = 4
        super.init()
        head = 2
        // 若是上面的未完成,是不能調用run()的,由於self尚未完整的建立
        run()
    }
    
    func run() {
        //do something
    }
}
複製代碼

如今看一下階段一和階段二的完整流程:

階段 1 - 自下而上
* 類的某個指定構造器或便利構造器被調用。
* 完成類的新實例內存的分配,但此時內存尚未被初始化。
* 指定構造器確保其所在類引入的全部存儲型屬性都已賦初值。
  存儲型屬性所屬的內存完成初始化。
* 指定構造器切換到父類的構造器,對其存儲屬性完成相同的任務。
* 這個過程沿着類的繼承鏈一直往上執行,直到到達繼承鏈的最頂部。
* 當到達了繼承鏈最頂部,並且繼承鏈的最後一個類已確保全部的存儲型屬性都已經賦值,
  這個實例的內存被認爲已經徹底初始化。此時階段 1 完成。
階段 2 - 自上而下
* 從繼承鏈頂部往下,繼承鏈中每一個類的指定構造器都有機會進一步自定義實例。
  構造器此時能夠訪問 self、修改它的屬性並調用實例方法等等。
* 最終,繼承鏈中任意的便利構造器有機會自定義實例和使用 self。
複製代碼

第一階段示例圖 - 自下而上:

第二階段示例圖 - 自上而下:

構造器的繼承與重寫

  • 繼承 默認狀況下子類是不會繼承父類的構造器。可是若是知足特定條件,父類構造器是能夠被子類自動繼承。
規則 1
若是子類沒有定義任何指定構造器,它將自動繼承父類全部的指定構造器。
規則 2
若是子類提供了全部父類指定構造器的實現——不管是經過規則 1
繼承過來的,仍是提供了自定義實現——它將自動繼承父類全部的便利構造器。

class Animal {
    let head = 1
    var name = ""
    
    init(name: String) {
        self.name = name
    }
    
    convenience init() {
        self.init(name: "animal")
    }
}

class Dog: Animal {
    let foot  = 4
}
//自動繼承父類全部的指定構造
let d1 = Dog(name: "dog") // d1.name dog
//自動繼承父類全部的便利構造器
let d2 = Dog() // d2.name animal
複製代碼
  • 重寫
class Vehicle {
    var numberOfWheels = 0
    var description: String {
        return "\(numberOfWheels) wheel(s)"
    }
}

class Bicycle: Vehicle {
    override init() {
        super.init()
        numberOfWheels = 2
    }
}
複製代碼

可失敗構造器

在給構造器傳入無效的形參,或缺乏某種所需的外部資源,又或是不知足某種必要的條件等狀況下,咱們建立一個可失敗的構造器是很是有必要的,來看一下可失敗構造器的語法:

struct Animal {
    let species: String
    init?(species: String) {
        if species.isEmpty {
            return nil
        }
        self.species = species
    }
    // 可失敗構造器不能與其餘非可失敗構造器(指定構造器、便利構造器)的參數和類型相同
    //因此下面這個指定構造器是非法的。
    //init(species: String) { }
}
複製代碼

值類型的可失敗構造器能夠橫向代理到自身其餘的可失敗構造器;類的可失敗構造器既可橫向代理自身的可失敗構造器,亦可向上代理到父類的可失敗構造器。

但不管是向上仍是橫向,只要可失敗構造器觸發構造失敗,整個構造過程將即刻中止,不會再執行後面的構造代碼。

必要構造器

咱們能夠經過required關鍵字來實現必要構造器,子類必須實現父類的必要構造器。

class Animal {
    var name: String
    required init(name: String) {
        self.name = name
    }
}

class Dog: Animal {
    var foot: Int
    //在重寫父類必要構造器的時候不須要加override
    required init(name: String) {
        foot = 4
        super.init(name: name)
    }
}

Dog(name: "dog")
複製代碼

有一點須要注意的就是:若是子類繼承的構造器能知足必要構造器的要求,則無須在子類中顯式提供必要構造器的實現。

class Animal {
    var name: String
    required init(name: String) {
        self.name = name
    }
}

class Dog: Animal {
    var foot = 2
}

Dog(name: "dog")
複製代碼

在咱們平常開發中,咱們會常常自定義UITableViewCell的子類來實現咱們定製化的需求,若是咱們沒有實現required init?(coder aDecoder: NSCoder)方法的話,咱們的代碼是編譯報錯的。查看文檔咱們發現該方法爲NSCoding的方法,且該方法爲UIView 必要構造器,因此它的子類必須實現該方法。

class CustomTableViewCell: UITableViewCell {
    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        
    }
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}
複製代碼

練手小栗子

光說不練假把式,經過下面的幾個小栗子來加深一下對構造器的理解。

分析一下下面的例子是否正確並思考錯誤的緣由。

  • 例1
class Animal {
    let head = 1
}

class Dog: Animal {
    override init() {
        head = 3
    }
}
複製代碼
  • 例2
struct Student {
    var name = ""
    var age = 0
    
    init(stuName: String, stuAge: Int) {
        name = stuName
        age = stuAge
    }
}

Student(name: "jack", age: 18)
複製代碼
  • 例3
class Animal {
    let head = 1
}

class Dog: Animal {
    let foot: Int
    
    init(foot: Int) {
        self.foot = foot
        run()
    }
    
    func run() {
        //do something
    }
}
複製代碼
  • 例4
class Animal {
    let head = 1
}

class Dog: Animal {
    var foot: Int
    
    init(foot: Int) {
        super.init()
        self.foot = foot
        run()
    }
    
    func run() {
        //do something
    }
}
複製代碼

總結

經過上面的介紹,咱們瞭解了默認構造器、自定義構造器類型、構造器代理、構造器的兩個階段、可失敗構造器和必要構造器是什麼。如今咱們總結一下須要重點理解的構造器代理和構造器的兩個階段:

  • 構造器代理
一、值類型只能橫向代理
二、類能夠橫向代理和向上代理
複製代碼
  • 構造器的兩個階段
一、先確保全部的存儲屬性都被賦予初始值
二、在實例準備使用以前,能夠自定義存儲屬性的值

經過4步安全檢查來確保兩個階段成功:
* 安全檢查1:指定構造器必須在完成本類全部存儲屬性賦值以後,才能向上代理到父類的構造器。
* 安全檢查2:指定構造器必須在爲繼承的屬性設置新值以前向上代理調用父類構造器。
* 安全檢查3:便利構造器必須先調用其餘構造器,再爲任意屬性(包括全部同類中定義的)賦新值。
* 安全檢查4:構造器在第一階段構造完成以前,不能調用任何實例方法,不能讀取任何實例屬性的值,不能引用 `self` 做爲一個值。
複製代碼

除了上面的兩個重點,咱們還須要注意一下幾個小點:

* Swift 中的構造器與 Objective-C 中的不一樣,它並不返回值。
* 若是你爲某個值類型定義了一個自定義的構造器,你將沒法訪問到默認構造器
 (若是是結構體,還將沒法訪問逐一成員構造器)。
  但能夠將自定義構造器寫在Extension中,來避免這個問題。
* 子類能夠在構造過程修改繼承來的變量屬性,可是不能修改繼承來的常量屬性。
複製代碼

關於構造器,本身總結的思惟導圖:

好了,本文到此就結束了。但願經過這篇文章能讓你們對 Swift 的構造過程能有一個更清晰的認識。Enjoy Every Day!🙂

相關文章
相關標籤/搜索