Swift-構(gòu)造過(guò)程

  • 過(guò)定義構(gòu)造器來(lái)實(shí)現(xiàn)構(gòu)造過(guò)程,它就像用來(lái)創(chuàng)建特定類型新實(shí)例的特殊方法。
  • Swift 的構(gòu)造器沒(méi)有返回值。它們的主要任務(wù)是保證某種類型的新實(shí)例在第一次使用前完成正確的初始化。

1. 存儲(chǔ)屬性的初始賦值

  • 類和結(jié)構(gòu)體在創(chuàng)建實(shí)例時(shí),必須為所有存儲(chǔ)型屬性設(shè)置合適的初始值。

  • 存儲(chǔ)型屬性的值不能處于一個(gè)未知的狀態(tài)。

      注意:當(dāng)你為存儲(chǔ)型屬性分配默認(rèn)值或者在構(gòu)造器中為設(shè)置初始值時(shí),它們的值是被直接設(shè)置的,不會(huì)觸發(fā)任何屬性觀察者。
    

1.1 構(gòu)造器

  • 構(gòu)造器在創(chuàng)建某個(gè)特定類型的新實(shí)例時(shí)被調(diào)用。它的最簡(jiǎn)形式類似于一個(gè)不帶任何形參的實(shí)例方法,以關(guān)鍵字 init 命名:
init() {
    // 在此處執(zhí)行構(gòu)造過(guò)程
}
struct Fahrenheit {
    var temperature: Double
    init() {
        temperature = 32.0
    }
}
var f = Fahrenheit()
print("The default temperature is \(f.temperature)° Fahrenheit")    // 打印“The default temperature is 32.0° Fahrenheit”

這個(gè)結(jié)構(gòu)體定義了一個(gè)不帶形參的構(gòu)造器 init,并在里面將存儲(chǔ)型屬性 temperature 的值初始化為 32.0(華氏溫度下水的冰點(diǎn))。

1.2 默認(rèn)屬性值

  • 可以在屬性聲明時(shí)為其設(shè)置默認(rèn)值。
  • 可以通過(guò)在屬性聲明時(shí)為 temperature 提供默認(rèn)值來(lái)使用更簡(jiǎn)單的方式定義結(jié)構(gòu)體 Fahrenheit :
struct Fahrenheit {
    var temperature = 32.0
}
注意
    1. 如果一個(gè)屬性總是使用相同的初始值,那么為其設(shè)置一個(gè)默認(rèn)值比每次都在構(gòu)造器中賦值要好。
    2. 兩種方法的最終結(jié)果是一樣的,只不過(guò)使用默認(rèn)值讓屬性的初始化和聲明結(jié)合得更緊密。它能讓你的構(gòu)造器更簡(jiǎn)潔、更清晰,且能通過(guò)默認(rèn)值自動(dòng)推導(dǎo)出屬性的類型;同時(shí),它也能讓你充分利用默認(rèn)構(gòu)造器、構(gòu)造器繼承等特性,后續(xù)章節(jié)將講到。

2. 自定義構(gòu)造過(guò)程

  • 可以通過(guò)輸入形參和可選屬性類型來(lái)自定義構(gòu)造過(guò)程。
  • 也可以在構(gòu)造過(guò)程中分配常量屬性

2.1 形參的構(gòu)造過(guò)程

  • 可以在定義中提供構(gòu)造形參,指定其值的類型和名字。構(gòu)造形參的功能和語(yǔ)法跟函數(shù)和方法的形參相同。
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

第一個(gè)構(gòu)造器擁有一個(gè)構(gòu)造形參,其實(shí)參標(biāo)簽為 fromFahrenheit,形參命名為 fahrenheit;
第二個(gè)構(gòu)造器也擁有一個(gè)構(gòu)造形參,其實(shí)參標(biāo)簽為 fromKelvin,形參命名為 kelvin。
這兩個(gè)構(gòu)造器都將單一的實(shí)參轉(zhuǎn)換成攝氏溫度值,并保存在屬性 temperatureInCelsius 中。

2.2 形參命名和實(shí)參標(biāo)簽

  • 構(gòu)造形參可以同時(shí)使用在構(gòu)造器里使用的形參命名和一個(gè)外部調(diào)用構(gòu)造器時(shí)使用的實(shí)參標(biāo)簽。
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)
let veryGreen = Color(0.0, 1.0, 0.0)    // 報(bào)編譯期錯(cuò)誤-需要實(shí)參標(biāo)簽
注意,如果不通過(guò)實(shí)參標(biāo)簽傳值,這個(gè)構(gòu)造器是沒(méi)法調(diào)用的。如果構(gòu)造器定義了某個(gè)實(shí)參標(biāo)簽,就必須使用它,忽略它將導(dǎo)致編譯期錯(cuò)誤。

2.3 不帶實(shí)參標(biāo)簽的構(gòu)造器形參

  • 如果你不希望構(gòu)造器的某個(gè)形參使用實(shí)參標(biāo)簽,可以使用下劃線(_)來(lái)代替顯式的實(shí)參標(biāo)簽來(lái)重寫默認(rèn)行為。
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 為 37.0

構(gòu)造器調(diào)用 Celsius(37.0) 意圖明確,不需要實(shí)參標(biāo)簽。因此適合使用 init(_ celsius: Double) 這樣的構(gòu)造器,從而可以通過(guò)提供未命名的 Double 值來(lái)調(diào)用構(gòu)造器。

2.4 可選屬性類型

  • 如果你自定義的類型有一個(gè)邏輯上允許值為空的存儲(chǔ)型屬性——無(wú)論是因?yàn)樗鼰o(wú)法在初始化時(shí)賦值,還是因?yàn)樗谥竽硞€(gè)時(shí)機(jī)可以賦值為空——都需要將它聲明為 可選類型。
  • 可選類型的屬性將自動(dòng)初始化為 nil,表示這個(gè)屬性是特意在構(gòu)造過(guò)程設(shè)置為空。
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()    // 打印“Do you like cheese?”
cheeseQuestion.response = "Yes, I do like cheese."

調(diào)查問(wèn)題的答案在詢問(wèn)前是無(wú)法確定的,因此我們將屬性 response 聲明為 String? 類型,或者說(shuō)是 “可選類型 String“。
當(dāng) SurveyQuestion 的實(shí)例初始化時(shí),它將自動(dòng)賦值為 nil,表明“暫時(shí)還沒(méi)有字符“。

2.5 構(gòu)造過(guò)程中常量屬性的賦值

  • 可以在構(gòu)造過(guò)程中的任意時(shí)間點(diǎn)給常量屬性賦值,只要在構(gòu)造過(guò)程結(jié)束時(shí)它設(shè)置成確定的值。
  • 一旦常量屬性被賦值,它將永遠(yuǎn)不可更改。
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.)"
注意
    對(duì)于類的實(shí)例來(lái)說(shuō),它的常量屬性只能在定義它的類的構(gòu)造過(guò)程中修改;
    不能在子類中修改。

3. 默認(rèn)構(gòu)造器

  • 如果結(jié)構(gòu)體或類為所有屬性提供了默認(rèn)值,又沒(méi)有提供任何自定義的構(gòu)造器,那么 Swift 會(huì)給這些結(jié)構(gòu)體或類提供一個(gè)默認(rèn)構(gòu)造器。
  • 這個(gè)默認(rèn)構(gòu)造器將簡(jiǎn)單地創(chuàng)建一個(gè)所有屬性值都設(shè)置為它們默認(rèn)值的實(shí)例。
class ShoppingListItem {
    var name: String?
    var quantity = 1
    var purchased = false
}
var item = ShoppingListItem()

上面例子中使用默認(rèn)構(gòu)造器創(chuàng)造了一個(gè) ShoppingListItem 類的實(shí)例(使用 ShoppingListItem() 形式的構(gòu)造器語(yǔ)法),并將其賦值給變量 item。(由于 name 屬性是可選 String 類型,它將接收一個(gè)默認(rèn) nil 的默認(rèn)值,盡管代碼中沒(méi)有寫出這個(gè)值)

3.1 結(jié)構(gòu)體的逐一成員構(gòu)造器

  • 結(jié)構(gòu)體如果沒(méi)有定義任何自定義構(gòu)造器,它們將自動(dòng)獲得一個(gè)逐一成員構(gòu)造器(memberwise initializer)。
  • 不像默認(rèn)構(gòu)造器,即使存儲(chǔ)型屬性沒(méi)有默認(rèn)值,結(jié)構(gòu)體也能會(huì)獲得逐一成員構(gòu)造器。
struct Size {
    var width = 0.0, height = 0.0
}
let twoByTwo = Size(width: 2.0, height: 2.0)

let zeroByTwo = Size(height: 2.0)
print(zeroByTwo.width, zeroByTwo.height)    // 打印 "0.0 2.0"

let zeroByZero = Size()
print(zeroByZero.width, zeroByZero.height)  // 打印 "0.0 0.0"

4. 值類型的構(gòu)造器代理

  • 通過(guò)調(diào)用其它構(gòu)造器來(lái)完成實(shí)例的部分構(gòu)造過(guò)程。這一過(guò)程稱為構(gòu)造器代理。
  • 它能避免多個(gè)構(gòu)造器間的代碼重復(fù)。
  • 值類型(結(jié)構(gòu)體和枚舉類型)不支持繼承,所以構(gòu)造器代理的過(guò)程相對(duì)簡(jiǎn)單,因?yàn)樗鼈冎荒艽斫o自己的其它構(gòu)造器。
  • 類則不同,它可以繼承自其它類。這意味著類有責(zé)任保證其所有繼承的存儲(chǔ)型屬性在構(gòu)造時(shí)也能正確的初始化。
  • 對(duì)于值類型,你可以使用 self.init 在自定義的構(gòu)造器中引用相同類型中的其它構(gòu)造器。并且你只能在構(gòu)造器內(nèi)部調(diào)用 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)
    }
}

let basicRect = Rect()  // basicRect 的 origin 是 (0.0, 0.0),size 是 (0.0, 0.0)
let originRect = Rect(origin: Point(x: 2.0, y: 2.0),
    size: Size(width: 5.0, height: 5.0))    // originRect 的 origin 是 (2.0, 2.0),size 是 (5.0, 5.0)
let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
    size: Size(width: 3.0, height: 3.0))    // centerRect 的 origin 是 (2.5, 2.5),size 是 (3.0, 3.0)
  1. 第一個(gè) Rect 構(gòu)造器 init(),在功能上跟沒(méi)有自定義構(gòu)造器時(shí)自動(dòng)獲得的默認(rèn)構(gòu)造器是一樣的。這個(gè)構(gòu)造器是函數(shù)體是空的,使用一對(duì)大括號(hào) {} 來(lái)表示。調(diào)用這個(gè)構(gòu)造器將返回一個(gè) Rect 實(shí)例,它的 origin 和 size 屬性都使用定義時(shí)的默認(rèn)值 Point(x: 0.0, y: 0.0) 和 Size(width: 0.0, height: 0.0)。
  2. 第二個(gè) Rect 構(gòu)造器 init(origin:size:),在功能上跟結(jié)構(gòu)體在沒(méi)有自定義構(gòu)造器時(shí)獲得的逐一成員構(gòu)造器是一樣的。這個(gè)構(gòu)造器只是簡(jiǎn)單地將 origin 和 size 的實(shí)參值賦給對(duì)應(yīng)的存儲(chǔ)型屬性。
  3. 第三個(gè) Rect 構(gòu)造器 init(center:size:) 稍微復(fù)雜一點(diǎn)。它先通過(guò) center 和 size 的值計(jì)算出 origin 的坐標(biāo),然后再調(diào)用(或者說(shuō)代理給)init(origin:size:) 構(gòu)造器來(lái)將新的 origin 和 size 值賦值到對(duì)應(yīng)的屬性中,構(gòu)造器 init(center:size:) 可以直接將 origin 和 size 的新值賦值到對(duì)應(yīng)的屬性中。然而,構(gòu)造器 init(center:size:) 通過(guò)使用提供了相關(guān)功能的現(xiàn)有構(gòu)造器將會(huì)更加便捷(而且意圖更清晰)。

5. 類的繼承和構(gòu)造過(guò)程

  • 類里面的所有存儲(chǔ)型屬性——包括所有繼承自父類的屬性——都必須在構(gòu)造過(guò)程中設(shè)置初始值。
  • Swift 為類類型提供了兩種構(gòu)造器來(lái)確保實(shí)例中所有存儲(chǔ)型屬性都能獲得初始值:指定構(gòu)造器和便利構(gòu)造器。

5.1 指定構(gòu)造器和便利構(gòu)造器

  • 指定構(gòu)造器是類中最主要的構(gòu)造器。一個(gè)指定構(gòu)造器將初始化類中提供的所有屬性,并調(diào)用合適的父類構(gòu)造器讓構(gòu)造過(guò)程沿著父類鏈繼續(xù)往上進(jìn)行。
  • 每一個(gè)類都必須至少擁有一個(gè)指定構(gòu)造器。在某些情況下,許多類通過(guò)繼承了父類中的指定構(gòu)造器而滿足了這個(gè)條件。
  • 便利構(gòu)造器是類中比較次要的、輔助型的構(gòu)造器。你可以定義便利構(gòu)造器來(lái)調(diào)用同一個(gè)類中的指定構(gòu)造器,并為部分形參提供默認(rèn)值。你也可以定義便利構(gòu)造器來(lái)創(chuàng)建一個(gè)特殊用途或特定輸入值的實(shí)例。

5.2 指定構(gòu)造器和便利構(gòu)造器的語(yǔ)法

  • 類的指定構(gòu)造器的寫法跟值類型簡(jiǎn)單構(gòu)造器一樣:
init(parameters) {
    statements
}
  • 便利構(gòu)造器也采用相同樣式的寫法,但需要在 init 關(guān)鍵字之前放置 convenience 關(guān)鍵字,并使用空格將它們倆分開(kāi):
convenience init(parameters) {
    statements
}

5.3 類類型的構(gòu)造器代理

Swift 構(gòu)造器之間的代理調(diào)用遵循以下三條規(guī)則:

  • 規(guī)則 1:指定構(gòu)造器必須調(diào)用其直接父類的的指定構(gòu)造器。
  • 規(guī)則 2:便利構(gòu)造器必須調(diào)用同類中定義的其它構(gòu)造器。
  • 規(guī)則 3:便利構(gòu)造器最后必須調(diào)用指定構(gòu)造器。

一個(gè)更方便記憶的方法是:

  • 指定構(gòu)造器必須總是向上代理
  • 便利構(gòu)造器必須總是橫向代理

5.4 兩段式構(gòu)造過(guò)程

Swift 中類的構(gòu)造過(guò)程包含兩個(gè)階段。

  • 第一個(gè)階段,類中的每個(gè)存儲(chǔ)型屬性賦一個(gè)初始值。
  • 當(dāng)每個(gè)存儲(chǔ)型屬性的初始值被賦值后,第二階段開(kāi)始,它給每個(gè)類一次機(jī)會(huì),在新實(shí)例準(zhǔn)備使用之前進(jìn)一步自定義它們的存儲(chǔ)型屬性。

Swift 編譯器將執(zhí)行 4 種有效的安全檢查,以確保兩段式構(gòu)造過(guò)程不出錯(cuò)地完成:

  • 安全檢查 1: 指定構(gòu)造器必須保證它所在類的所有屬性都必須先初始化完成,之后才能將其它構(gòu)造任務(wù)向上代理給父類中的構(gòu)造器。
    如上所述,一個(gè)對(duì)象的內(nèi)存只有在其所有存儲(chǔ)型屬性確定之后才能完全初始化。為了滿足這一規(guī)則,指定構(gòu)造器必須保證它所在類的屬性在它往上代理之前先完成初始化。
  • 安全檢查 2: 指定構(gòu)造器必須在為繼承的屬性設(shè)置新值之前向上代理調(diào)用父類構(gòu)造器。如果沒(méi)這么做,指定構(gòu)造器賦予的新值將被父類中的構(gòu)造器所覆蓋。
  • 安全檢查 3: 便利構(gòu)造器必須為任意屬性(包括所有同類中定義的)賦新值之前代理調(diào)用其它構(gòu)造器。如果沒(méi)這么做,便利構(gòu)造器賦予的新值將被該類的指定構(gòu)造器所覆蓋。
  • 安全檢查 4: 構(gòu)造器在第一階段構(gòu)造完成之前,不能調(diào)用任何實(shí)例方法,不能讀取任何實(shí)例屬性的值,不能引用 self 作為一個(gè)值。

類的實(shí)例在第一階段結(jié)束以前并不是完全有效的。只有第一階段完成后,類的實(shí)例才是有效的,才能訪問(wèn)屬性和調(diào)用方法。

階段 1

  • 類的某個(gè)指定構(gòu)造器或便利構(gòu)造器被調(diào)用。
  • 完成類的新實(shí)例內(nèi)存的分配,但此時(shí)內(nèi)存還沒(méi)有被初始化。
  • 指定構(gòu)造器確保其所在類引入的所有存儲(chǔ)型屬性都已賦初值。存儲(chǔ)型屬性所屬的內(nèi)存完成初始化。
  • 指定構(gòu)造器切換到父類的構(gòu)造器,對(duì)其存儲(chǔ)屬性完成相同的任務(wù)。
  • 這個(gè)過(guò)程沿著類的繼承鏈一直往上執(zhí)行,直到到達(dá)繼承鏈的最頂部。
  • 當(dāng)?shù)竭_(dá)了繼承鏈最頂部,而且繼承鏈的最后一個(gè)類已確保所有的存儲(chǔ)型屬性都已經(jīng)賦值,這個(gè)實(shí)例的內(nèi)存被認(rèn)為已經(jīng)完全初始化。此時(shí)階段 1 完成。

階段 2

  • 從繼承鏈頂部往下,繼承鏈中每個(gè)類的指定構(gòu)造器都有機(jī)會(huì)進(jìn)一步自定義實(shí)例。構(gòu)造器此時(shí)可以訪問(wèn) self、修改它的屬性并調(diào)用實(shí)例方法等等。
  • 最終,繼承鏈中任意的便利構(gòu)造器有機(jī)會(huì)自定義實(shí)例和使用 self。

5.5 構(gòu)造器的繼承和重寫

  • Swift 中的子類默認(rèn)情況下不會(huì)繼承父類的構(gòu)造器。

  • Swift 的這種機(jī)制可以防止一個(gè)父類的簡(jiǎn)單構(gòu)造器被一個(gè)更精細(xì)的子類繼承,而在用來(lái)創(chuàng)建子類時(shí)的新實(shí)例時(shí)沒(méi)有完全或錯(cuò)誤被初始化。

      注意:父類的構(gòu)造器僅會(huì)在安全和適當(dāng)?shù)哪承┣闆r下被繼承。、
    
class Vehicle { //  Vehicle 類只為存儲(chǔ)型屬性提供默認(rèn)值,也沒(méi)有提供自定義構(gòu)造器。因此,它會(huì)自動(dòng)獲得一個(gè)默認(rèn)構(gòu)造器
    var numberOfWheels = 0
    var description: String {
        return "\(numberOfWheels) wheel(s)"
    }
}

let vehicle = Vehicle()
print("Vehicle: \(vehicle.description)")    // Vehicle: 0 wheel(s)

class Bicycle: Vehicle {    //  Bicycle 定義了一個(gè)自定義指定構(gòu)造器 init()。這個(gè)指定構(gòu)造器和父類的指定構(gòu)造器相匹配,所以 Bicycle 中這個(gè)版本的構(gòu)造器需要帶上 override 修飾符。
    override init() {
        super.init()    //  這個(gè)方法的作用是調(diào)用 Bicycle 的父類 Vehicle 的默認(rèn)構(gòu)造器,可以確保 Bicycle 在修改屬性之前,它所繼承的屬性 numberOfWheels 能被 Vehicle 類初始化。
        numberOfWheels = 2
    }
}

let bicycle = Bicycle()
print("Bicycle: \(bicycle.description)")    // 打印“Bicycle: 2 wheel(s)”

class Hoverboard: Vehicle {
    var color: String
    init(color: String) {
        self.color = color
        // super.init() 在這里被隱式調(diào)用
    }
    override var description: String {
        return "\(super.description) in a beautiful \(color)"
    }
}

let hoverboard = Hoverboard(color: "silver")
print("Hoverboard: \(hoverboard.description)")
// Hoverboard: 0 wheel(s) in a beautiful silver

注意:子類可以在構(gòu)造過(guò)程修改繼承來(lái)的變量屬性,但是不能修改繼承來(lái)的常量屬性。

5.6 構(gòu)造器的自動(dòng)繼承

  • 規(guī)則 1 : 如果子類沒(méi)有定義任何指定構(gòu)造器,它將自動(dòng)繼承父類所有的指定構(gòu)造器。
  • 規(guī)則 2: 如果子類提供了所有父類指定構(gòu)造器的實(shí)現(xiàn)——無(wú)論是通過(guò)規(guī)則 1 繼承過(guò)來(lái)的,還是提供了自定義實(shí)現(xiàn)——它將自動(dòng)繼承父類所有的便利構(gòu)造器。

5.7 指定構(gòu)造器和便利構(gòu)造器實(shí)踐

class Food {    //  一個(gè)簡(jiǎn)單的用來(lái)封裝食物名字的類
    var name: String
    init(name: String) {
        self.name = name
    }

//  RecipeIngredient 用來(lái)表示食譜中的一項(xiàng)原料
    convenience init() {    //  沒(méi)有參數(shù)的便利構(gòu)造器 init(),為新食物提供了一個(gè)默認(rèn)的占位名字,通過(guò)橫向代理到指定構(gòu)造器 init(name: String) 并給參數(shù) name 賦值為 [Unnamed] 來(lái)實(shí)現(xiàn)
        self.init(name: "[Unnamed]")
    }
}

let namedMeat = Food(name: "Bacon")     //  這個(gè)構(gòu)造器可以使用一個(gè)特定的名字來(lái)創(chuàng)建新的 Food 實(shí)例
// namedMeat 的名字是 "Bacon"
let mysteryMeat = Food()    
// mysteryMeat 的名字是 [Unnamed]

class RecipeIngredient: Food {
    var quantity: Int
    init(name: String, quantity: Int) {
        self.quantity = quantity
        super.init(name: name)
    }
    override convenience init(name: String) {
        self.init(name: name, quantity: 1)
    }
}

let oneMysteryItem = RecipeIngredient()
let oneBacon = RecipeIngredient(name: "Bacon")
let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6)

//  ShoppingListItem,這個(gè)類構(gòu)建了購(gòu)物單中出現(xiàn)的某一種食譜原料。
class ShoppingListItem: RecipeIngredient {
    var purchased = false   //  購(gòu)買狀態(tài)
    var description: String {
        var output = "\(quantity) x \(name)"
        output += purchased ? " ?" : " ?"
        return output
    }
}

var breakfastList = [
    ShoppingListItem(),
    ShoppingListItem(name: "Bacon"),
    ShoppingListItem(name: "Eggs", quantity: 6),
]
breakfastList[0].name = "Orange juice"
breakfastList[0].purchased = true
for item in breakfastList {
    print(item.description)
}
// 1 x orange juice ?
// 1 x bacon ?
// 6 x eggs ?

6. 可失敗構(gòu)造器

  • 有時(shí),定義一個(gè)構(gòu)造器可失敗的類,結(jié)構(gòu)體或者枚舉是很有用的。

  • 這里所指的“失敗” 指的是,如給構(gòu)造器傳入無(wú)效的形參,或缺少某種所需的外部資源,又或是不滿足某種必要的條件等。

  • 可以在一個(gè)類,結(jié)構(gòu)體或是枚舉類型的定義中,添加一個(gè)或多個(gè)可失敗構(gòu)造器。其語(yǔ)法為在 init 關(guān)鍵字后面添加問(wèn)號(hào)(init?)。

  • 可失敗構(gòu)造器會(huì)創(chuàng)建一個(gè)類型為自身類型的可選類型的對(duì)象。你通過(guò) return nil 語(yǔ)句來(lái)表明可失敗構(gòu)造器在何種情況下應(yīng)該 “失敗”。

      注意:
          1. 可失敗構(gòu)造器的參數(shù)名和參數(shù)類型,不能與其它非可失敗構(gòu)造器的參數(shù)名,及其參數(shù)類型相同。
          2. 嚴(yán)格來(lái)說(shuō),構(gòu)造器都不支持返回值。因?yàn)闃?gòu)造器本身的作用,只是為了確保對(duì)象能被正確構(gòu)造。因此你只是用 return nil 表明可失敗構(gòu)造器構(gòu)造失敗,而不要用關(guān)鍵字 return 來(lái)表明構(gòu)造成功。
    
let wholeNumber: Double = 12345.0
let pi = 3.14159

if let valueMaintained = Int(exactly: wholeNumber) {
    print("\(wholeNumber) conversion to Int maintains value of \(valueMaintained)")
}   // 打印“12345.0 conversion to Int maintains value of 12345”

let valueChanged = Int(exactly: pi) // valueChanged 是 Int? 類型,不是 Int 類型

if valueChanged == nil {
    print("\(pi) conversion to Int does not maintain value")
}   // 打印“3.14159 conversion to Int does not maintain value”

實(shí)現(xiàn)針對(duì)數(shù)字類型轉(zhuǎn)換的可失敗構(gòu)造器。確保數(shù)字類型之間的轉(zhuǎn)換能保持精確的值,使用這個(gè) init(exactly:) 構(gòu)造器。如果類型轉(zhuǎn)換不能保持值不變,則這個(gè)構(gòu)造器構(gòu)造失敗。

struct Animal {
    let species: String
    init?(species: String) {
        if species.isEmpty {    //  這個(gè)可失敗構(gòu)造器檢查傳入的species 值是否為一個(gè)空字符串。
            return nil  //  如果為空字符串,則構(gòu)造失敗。
        }
        self.species = species  //  否則,species 屬性被賦值,構(gòu)造成功。
    }
}
  • 可以通過(guò)該可失敗構(gòu)造器來(lái)嘗試構(gòu)建一個(gè) Animal 的實(shí)例,并檢查構(gòu)造過(guò)程是否成功:
let someCreature = Animal(species: "Giraffe")   // someCreature 的類型是 Animal? 而不是 Animal

if let giraffe = someCreature {
    print("An animal was initialized with a species of \(giraffe.species)")
}   // 打印“An animal was initialized with a species of Giraffe”
  • 如果你給該可失敗構(gòu)造器傳入一個(gè)空字符串到形參 species,則會(huì)導(dǎo)致構(gòu)造失?。?/li>
let anonymousCreature = Animal(species: "") // anonymousCreature 的類型是 Animal?, 而不是 Animal

if anonymousCreature == nil {
    print("The anonymous creature could not be initialized")
}   // 打印“The anonymous creature could not be initialized”
注意:
    1. 檢查空字符串的值(如 "",而不是 "Giraffe" )和檢查值為 nil 的可選類型的字符串是兩個(gè)完全不同的概念。
    2. 上例中的空字符串("")其實(shí)是一個(gè)有效的,非可選類型的字符串。
    3. 這里我們之所以讓 Animal 的可失敗構(gòu)造器構(gòu)造失敗,只是因?yàn)閷?duì)于 Animal 這個(gè)類的 species 屬性來(lái)說(shuō),它更適合有一個(gè)具體的值,而不是空字符串。

6.1 枚舉類型的可失敗構(gòu)造器

  • 可以通過(guò)一個(gè)帶一個(gè)或多個(gè)形參的可失敗構(gòu)造器來(lái)獲取枚舉類型中特定的枚舉成員。
  • 如果提供的形參無(wú)法匹配任何枚舉成員,則構(gòu)造失敗。
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
        }
    }
}
  • 可以利用該可失敗構(gòu)造器在三個(gè)枚舉成員中選擇合適的枚舉成員,當(dāng)形參不能和任何枚舉成員相匹配時(shí),則構(gòu)造失敗:
let fahrenheitUnit = TemperatureUnit(symbol: "F")
if fahrenheitUnit != nil {
    print("This is a defined temperature unit, so initialization succeeded.")
}   // 打印“This is a defined temperature unit, so initialization succeeded.”

let unknownUnit = TemperatureUnit(symbol: "X")
if unknownUnit == nil {
    print("This is not a defined temperature unit, so initialization failed.")
}   // 打印“This is not a defined temperature unit, so initialization failed.”

6.2 帶原始值的枚舉類型的可失敗構(gòu)造器

  • 帶原始值的枚舉類型會(huì)自帶一個(gè)可失敗構(gòu)造器 init?(rawValue:)。
  • 該可失敗構(gòu)造器有一個(gè)合適的原始值類型的 rawValue 形參,選擇找到的相匹配的枚舉成員,找不到則構(gòu)造失敗。
enum TemperatureUnit: Character {
    case Kelvin = "K", Celsius = "C", Fahrenheit = "F"
}

let fahrenheitUnit = TemperatureUnit(rawValue: "F")
if fahrenheitUnit != nil {
    print("This is a defined temperature unit, so initialization succeeded.")
}   // 打印“This is a defined temperature unit, so initialization succeeded.”

let unknownUnit = TemperatureUnit(rawValue: "X")
if unknownUnit == nil {
    print("This is not a defined temperature unit, so initialization failed.")
}   // 打印“This is not a defined temperature unit, so initialization failed.”

6.3 構(gòu)造失敗的傳遞

  • 類、結(jié)構(gòu)體、枚舉的可失敗構(gòu)造器可以橫向代理到它們自己其他的可失敗構(gòu)造器。

  • 類似的,子類的可失敗構(gòu)造器也能向上代理到父類的可失敗構(gòu)造器。

  • 無(wú)論是向上代理還是橫向代理,如果你代理到的其他可失敗構(gòu)造器觸發(fā)構(gòu)造失敗,整個(gè)構(gòu)造過(guò)程將立即終止,接下來(lái)的任何構(gòu)造代碼不會(huì)再被執(zhí)行。

      注意:可失敗構(gòu)造器也可以代理到其它的不可失敗構(gòu)造器。通過(guò)這種方式,你可以增加一個(gè)可能的失敗狀態(tài)到現(xiàn)有的構(gòu)造過(guò)程中。
    
class Product { 
    let name: String
    init?(name: String) {
        if name.isEmpty { return nil }
        self.name = name
    }
}

class CartItem: Product {   //  這個(gè)類建立了一個(gè)在線購(gòu)物車中的物品的模型
    let quantity: Int   //  常量存儲(chǔ)型屬性,并確保該屬性的值至少為 1
    init?(name: String, quantity: Int) {
        if quantity < 1 { return nil }  //  CartItem 可失敗構(gòu)造器首先驗(yàn)證接收的 quantity 值是否大于等于 1, 倘若 quantity 值無(wú)效,則立即終止整個(gè)構(gòu)造過(guò)程,返回失敗結(jié)果,且不再執(zhí)行余下代碼。
        self.quantity = quantity
        super.init(name: name)
    }
}

//  傳入一個(gè)非空字符串 name 以及一個(gè)值大于等于 1 的 quantity 來(lái)創(chuàng)建一個(gè) CartItem 實(shí)例
if let twoSocks = CartItem(name: "sock", quantity: 2) {
    print("Item: \(twoSocks.name), quantity: \(twoSocks.quantity)")
}   // 打印“Item: sock, quantity: 2”

//  以一個(gè)值為 0 的 quantity 來(lái)創(chuàng)建一個(gè) CartItem 實(shí)例,那么將導(dǎo)致 CartItem 構(gòu)造器失敗
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”

//  傳入一個(gè)值為空字符串的 name 來(lái)創(chuàng)建一個(gè) CartItem 實(shí)例,那么將導(dǎo)致父類 Product 的構(gòu)造過(guò)程失?。?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”

6.4 重寫一個(gè)可失敗構(gòu)造器

  • 可以在子類中重寫父類的可失敗構(gòu)造器。

  • 或者你也可以用子類的非可失敗構(gòu)造器重寫一個(gè)父類的可失敗構(gòu)造器。

  • 這使你可以定義一個(gè)不會(huì)構(gòu)造失敗的子類,即使父類的構(gòu)造器允許構(gòu)造失敗。

      注意:
          1. 當(dāng)你用子類的非可失敗構(gòu)造器重寫父類的可失敗構(gòu)造器時(shí),向上代理到父類的可失敗構(gòu)造器的唯一方式是對(duì)父類的可失敗構(gòu)造器的返回值進(jìn)行強(qiáng)制解包。
          2. 你可以用非可失敗構(gòu)造器重寫可失敗構(gòu)造器,但反過(guò)來(lái)卻不行。
    
class Document {
    var name: String?
    // 該構(gòu)造器創(chuàng)建了一個(gè) name 屬性的值為 nil 的 document 實(shí)例
    init() {}
    // 該構(gòu)造器創(chuàng)建了一個(gè) name 屬性的值為非空字符串的 document 實(shí)例
    init?(name: String) {
        if name.isEmpty { return nil }
        self.name = name
    }
}

class AutomaticallyNamedDocument: Document {
    override init() {
        super.init()
        self.name = "[Untitled]"
    }
    override init(name: String) {   //  用一個(gè)不可失敗構(gòu)造器 init(name:) 重寫了父類的可失敗構(gòu)造器 init?(name:),子類用一個(gè)不可失敗構(gòu)造器代替了父類的可失敗構(gòu)造器。
        super.init()
        if name.isEmpty {
            self.name = "[Untitled]"
        } else {
            self.name = name
        }
    }
}

class UntitledDocument: Document {
    override init() {   //  name 屬性的值總是 "[Untitled]",它在構(gòu)造過(guò)程中使用了父類的可失敗構(gòu)造器 init?(name:)
        super.init(name: "[Untitled]")!
    }
  1. 如果在調(diào)用父類的可失敗構(gòu)造器 init?(name:) 時(shí)傳入的是空字符串,那么強(qiáng)制解包操作會(huì)引發(fā)運(yùn)行時(shí)錯(cuò)誤。
  2. 不過(guò),因?yàn)檫@里是通過(guò)字符串常量來(lái)調(diào)用它,構(gòu)造器不會(huì)失敗,所以并不會(huì)發(fā)生運(yùn)行時(shí)錯(cuò)誤。

6.5 init! 可失敗構(gòu)造器

  • init 關(guān)鍵字后添加問(wèn)號(hào)的方式(init?)來(lái)定義一個(gè)可失敗構(gòu)造器。
  • init 后面添加感嘆號(hào)的方式來(lái)定義一個(gè)可失敗構(gòu)造器(init!),該可失敗構(gòu)造器將會(huì)構(gòu)建一個(gè)對(duì)應(yīng)類型的隱式解包可選類型的對(duì)象。
  • 可以在 init? 中代理到 init!,反之亦然。
  • 你也可以用 init? 重寫 init!,反之亦然。
  • 你還可以用 init 代理到 init!,不過(guò),一旦 init! 構(gòu)造失敗,則會(huì)觸發(fā)一個(gè)斷言。

7. 必要構(gòu)造器

  • 在類的構(gòu)造器前添加 required 修飾符表明所有該類的子類都必須實(shí)現(xiàn)該構(gòu)造器:
class SomeClass {
    required init() {
        // 構(gòu)造器的實(shí)現(xiàn)代碼
    }
}
  • 子類重寫父類的必要構(gòu)造器時(shí)必須在子類的構(gòu)造器前也添加 required 修飾符,表明該構(gòu)造器要求也應(yīng)用于繼承鏈后面的子類。在重寫父類中必要的指定構(gòu)造器時(shí),不需要添加 override 修飾符
class SomeSubclass: SomeClass {
    required init() {
        // 構(gòu)造器的實(shí)現(xiàn)代碼
    }
}

如果子類繼承的構(gòu)造器能滿足必要構(gòu)造器的要求,則無(wú)須在子類中顯式提供必要構(gòu)造器的實(shí)現(xiàn)。

8. 通過(guò)閉包或函數(shù)設(shè)置屬性的默認(rèn)值

  • 如果某個(gè)存儲(chǔ)型屬性的默認(rèn)值需要一些自定義或設(shè)置,你可以使用閉包或全局函數(shù)為其提供定制的默認(rèn)值。
  • 每當(dāng)某個(gè)屬性所在類型的新實(shí)例被構(gòu)造時(shí),對(duì)應(yīng)的閉包或函數(shù)會(huì)被調(diào)用,而它們的返回值會(huì)當(dāng)做默認(rèn)值賦值給這個(gè)屬性。
  • 這種類型的閉包或函數(shù)通常會(huì)創(chuàng)建一個(gè)跟屬性類型相同的臨時(shí)變量,然后修改它的值以滿足預(yù)期的初始狀態(tài),最后返回這個(gè)臨時(shí)變量,作為屬性的默認(rèn)值。
  • 用閉包為屬性提供默認(rèn)值:
class SomeClass {
    let someProperty: SomeType = {
        // 在這個(gè)閉包中給 someProperty 創(chuàng)建一個(gè)默認(rèn)值
        // someValue 必須和 SomeType 類型相同
        return someValue
    }()
}
  1. 注意閉包結(jié)尾的花括號(hào)后面接了一對(duì)空的小括號(hào)。
  2. 這用來(lái)告訴 Swift 立即執(zhí)行此閉包。
  3. 如果你忽略了這對(duì)括號(hào),相當(dāng)于將閉包本身作為值賦值給了屬性,而不是將閉包的返回值賦值給屬性。
    注意
        1. 如果你使用閉包來(lái)初始化屬性,請(qǐng)記住在閉包執(zhí)行時(shí),實(shí)例的其它部分都還沒(méi)有初始化。
        2. 這意味著你不能在閉包里訪問(wèn)其它屬性,即使這些屬性有默認(rèn)值。
        3. 同樣,你也不能使用隱式的 self 屬性,或者調(diào)用任何實(shí)例方法。
struct Chessboard {
    /*
    boardColors:一個(gè)包含 64 個(gè) Bool 值的數(shù)組
    值為 true 的元素表示一個(gè)黑格
    值為 false 的元素表示一個(gè)白格
    數(shù)組中第一個(gè)元素代表棋盤上左上角的格子
    最后一個(gè)元素代表棋盤上右下角的格子。
    */
    let boardColors: [Bool] = { 
        var temporaryBoard = [Bool]()
        var isBlack = false
        for i in 1...8 {
            for j in 1...8 {
                temporaryBoard.append(isBlack)
                isBlack = !isBlack
            }
            isBlack = !isBlack
        }
        return temporaryBoard
    }()
    func squareIsBlackAt(row: Int, column: Int) -> Bool {
        return boardColors[(row * 8) + column]
    }
}

let board = Chessboard()
print(board.squareIsBlackAt(row: 0, column: 1))
// 打印“true”
print(board.squareIsBlackAt(row: 7, column: 7))
// 打印“false”
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 構(gòu)造過(guò)程是使用類、結(jié)構(gòu)體或枚舉類型的實(shí)例之前的準(zhǔn)備過(guò)程。在新實(shí)例可用前必須執(zhí)行這個(gè)過(guò)程,具體操作包括設(shè)置實(shí)例中每個(gè)...
    CDLOG閱讀 397評(píng)論 0 1
  • 這是16年5月份編輯的一份比較雜亂適合自己觀看的學(xué)習(xí)記錄文檔,今天18年5月份再次想寫文章,發(fā)現(xiàn)簡(jiǎn)書還為我保存起的...
    Jenaral閱讀 3,143評(píng)論 2 9
  • 中文文檔 一、存儲(chǔ)屬性的初始賦值 類和結(jié)構(gòu)體在創(chuàng)建實(shí)例時(shí),必須為所有存儲(chǔ)型屬性設(shè)置合適的初始值。存儲(chǔ)型屬性的值不能...
    伯wen閱讀 229評(píng)論 0 1
  • ?構(gòu)造過(guò)程是使用類、結(jié)構(gòu)體或枚舉類型一個(gè)實(shí)例的準(zhǔn)備過(guò)程。在新實(shí)例可用前必須執(zhí)行這個(gè)過(guò)程,具體操作包括設(shè)置實(shí)例中每個(gè)...
    EndEvent閱讀 735評(píng)論 0 3
  • 本章將會(huì)介紹 存儲(chǔ)屬性的初始賦值自定義構(gòu)造過(guò)程默認(rèn)構(gòu)造器值類型的構(gòu)造器代理類的繼承和構(gòu)造過(guò)程可失敗構(gòu)造器必要構(gòu)造器...
    寒橋閱讀 839評(píng)論 0 0

友情鏈接更多精彩內(nèi)容