Swift:構(gòu)造過程

中文文檔

一、存儲(chǔ)屬性的初始賦值

  • 類和結(jié)構(gòu)體在創(chuàng)建實(shí)例時(shí),必須為所有存儲(chǔ)型屬性設(shè)置合適的初始值。存儲(chǔ)型屬性的值不能處于一個(gè)未知的狀態(tài)。

  • 你可以在構(gòu)造器中為存儲(chǔ)型屬性賦初值,也可以在定義屬性時(shí)為其設(shè)置默認(rèn)值。

1、構(gòu)造器
  • 構(gòu)造器在創(chuàng)建某個(gè)特定類型的新實(shí)例時(shí)被調(diào)用。它的最簡形式類似于一個(gè)不帶任何參數(shù)的實(shí)例方法,以關(guān)鍵字 init 命名:
init() {
    // 在此處執(zhí)行構(gòu)造過程
}
  • 下面例子中定義了一個(gè)用來保存華氏溫度的結(jié)構(gòu)體 Fahrenheit,它擁有一個(gè) Double 類型的存儲(chǔ)型屬性 temperature
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"
2、默認(rèn)屬性值
  • 如前所述,你可以在構(gòu)造器中為存儲(chǔ)型屬性設(shè)置初始值。同樣,你也可以在屬性聲明時(shí)為其設(shè)置默認(rèn)值。

  • 你可以使用更簡單的方式在定義結(jié)構(gòu)體 Fahrenheit 時(shí)為屬性 temperature設(shè)置默認(rèn)值:

struct Fahrenheit {
    var temperature = 32.0
}
  • 定義一個(gè)結(jié)構(gòu)體時(shí), 可以不給屬性賦初始值, 也不需要自定義構(gòu)造方法, 結(jié)構(gòu)體會(huì)自動(dòng)生成一個(gè)給所有屬性賦值的構(gòu)造方法
struct Vehicle {
    let speed: Int
    var currentSpeed: Double
}
let vehicle = Vehicle(speed: 0, currentSpeed: 0)
  • 自動(dòng)生成的如下方法, 這個(gè)方法不需要我們寫, 系統(tǒng)會(huì)自動(dòng)生成
init(speed: Int, currentSpeed: Double) {
    self.speed = speed
    self.currentSpeed = currentSpeed
}
  • 但是一個(gè)類, 卻不會(huì)自動(dòng)生成這種方法, 當(dāng)我們沒有給屬性默認(rèn)值時(shí), 就必須寫一個(gè)構(gòu)造方法, 給沒有默認(rèn)值的非可選屬性指定一個(gè)默認(rèn)值

  • 下面定義的Vehicle, 會(huì)在編譯時(shí)報(bào)錯(cuò), 因?yàn)閷傩詻]有初始值, 并且類不會(huì)自動(dòng)生成一個(gè)給所有屬性賦初始值的構(gòu)造方法

class Vehicle {
    let speed: Int
    var currentSpeed: Double
}

二、自定義構(gòu)造過程

你可以通過輸入?yún)?shù)和可選類型的屬性來自定義構(gòu)造過程,也可以在構(gòu)造過程中給常量屬性賦初值。

1、構(gòu)造參數(shù)
  • 自定義構(gòu)造過程時(shí),可以在定義中提供構(gòu)造參數(shù),指定參數(shù)值的類型和名字。構(gòu)造參數(shù)的功能和語法跟函數(shù)和方法的參數(shù)相同。

  • 下面例子中定義了一個(gè)包含攝氏度溫度的結(jié)構(gòu)體 Celsius。它定義了兩個(gè)不同的構(gòu)造器:init(fromFahrenheit:)init(fromKelvin:),二者分別通過接受不同溫標(biāo)下的溫度值來創(chuàng)建新的實(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ù),其外部名字為 fromFahrenheit,內(nèi)部名字為 fahrenheit;第二個(gè)構(gòu)造器也擁有一個(gè)構(gòu)造參數(shù),其外部名字為 fromKelvin,內(nèi)部名字為 kelvin。這兩個(gè)構(gòu)造器都將唯一的參數(shù)值轉(zhuǎn)換成攝氏溫度值,并保存在屬性 temperatureInCelsius中。
2、參數(shù)名和參數(shù)標(biāo)簽
  • 跟函數(shù)和方法參數(shù)相同,構(gòu)造參數(shù)也擁有一個(gè)在構(gòu)造器內(nèi)部使用的參數(shù)名和一個(gè)在調(diào)用構(gòu)造器時(shí)使用的參數(shù)標(biāo)簽。
然而,構(gòu)造器并不像函數(shù)和方法那樣在括號(hào)前有一個(gè)可辨別的名字。
因此在調(diào)用構(gòu)造器時(shí),主要通過構(gòu)造器中的參數(shù)名和類型來確定應(yīng)該被調(diào)用的構(gòu)造器。
正因?yàn)閰?shù)如此重要,如果你在定義構(gòu)造器時(shí)沒有提供參數(shù)標(biāo)簽,
Swift 會(huì)為構(gòu)造器的每個(gè)參數(shù)自動(dòng)生成一個(gè)參數(shù)標(biāo)簽。
  • 以下例子中定義了一個(gè)結(jié)構(gòu)體 Color,它包含了三個(gè)常量:redgreenblue。

  • Color 提供了一個(gè)構(gòu)造器,其中包含三個(gè) Double 類型的構(gòu)造參數(shù)。Color 也提供了第二個(gè)構(gòu)造器,它只包含名為 whiteDouble 類型的參數(shù),它被用于給上述三個(gè)構(gòu)造參數(shù)賦予同樣的值。

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
    }
}
  • 兩種構(gòu)造器都能通過提供的初始參數(shù)值來創(chuàng)建一個(gè)新的 Color 實(shí)例:
let magenta = Color(red: 1.0, green: 0.0, blue: 1.0)
let halfGray = Color(white: 0.5)
  • 注意,如果不通過參數(shù)標(biāo)簽傳值,你是沒法調(diào)用這個(gè)構(gòu)造器的。只要構(gòu)造器定義了某個(gè)參數(shù)標(biāo)簽,你就必須使用它,忽略它將導(dǎo)致編譯錯(cuò)誤:
let veryGreen = Color(0.0, 1.0, 0.0)
// 報(bào)編譯時(shí)錯(cuò)誤,需要外部名稱
3、不帶參數(shù)標(biāo)簽的構(gòu)造器參數(shù)
  • 如果你不希望為構(gòu)造器的某個(gè)參數(shù)提供參數(shù)標(biāo)簽,你可以使用下劃線(_)來顯式描述它的外部名,以此重寫上面所說的默認(rèn)行為。
struct Celsius {
    var temperatureInCelsius: Double
    init(_ celsius: Double){
        temperatureInCelsius = celsius
    }
}

let bodyTemperature = Celsius(37.0)
// bodyTemperature.temperatureInCelsius 為 37.0
4、可選屬性類型
  • 如果你定制的類型包含一個(gè)邏輯上允許取值為空的存儲(chǔ)型屬性——無論是因?yàn)樗鼰o法在初始化時(shí)賦值,還是因?yàn)樗谥竽硞€(gè)時(shí)間點(diǎn)可以賦值為空——你都需要將它定義為 可選類型。
  • 可選類型的屬性將自動(dòng)初始化為 nil,表示這個(gè)屬性是有意在初始化時(shí)設(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)查問題的答案在回答前是無法確定的,因此我們將屬性 response 聲明為 String? 類型,或者說是 可選字符串類型。當(dāng) SurveyQuestion 實(shí)例化時(shí),它將自動(dòng)賦值為 nil,表明此字符串暫時(shí)還沒有值。
5、構(gòu)造過程中常量屬性的賦值
  • 你可以在構(gòu)造過程中的任意時(shí)間點(diǎn)給常量屬性指定一個(gè)值,只要在構(gòu)造過程結(jié)束時(shí)是一個(gè)確定的值。一旦常量屬性被賦值,它將永遠(yuǎn)不可更改。

注意
對(duì)于類的實(shí)例來說,它的常量屬性只能在定義它的類的構(gòu)造過程中修改;不能在子類中修改。

  • 你可以修改上面的 SurveyQuestion 示例,用常量屬性替代變量屬性 text,表示問題內(nèi)容 textSurveyQuestion 的實(shí)例被創(chuàng)建之后不會(huì)再被修改。
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.)"

注意
如果類的常量屬性, 在定義時(shí)指定了初始值, 就不能在構(gòu)造方法中再次修改常量的值
只有在定義時(shí), 沒有指定常量屬性的值, 才可以在構(gòu)造方法中指定值

三、默認(rèn)構(gòu)造器

  • 如果結(jié)構(gòu)體或類的所有屬性都有默認(rèn)值,同時(shí)沒有自定義的構(gòu)造器,那么 Swift 會(huì)給這些結(jié)構(gòu)體或類提供一個(gè)默認(rèn)構(gòu)造器

  • 這個(gè)默認(rèn)構(gòu)造器將簡單地創(chuàng)建一個(gè)所有屬性值都設(shè)置為默認(rèn)值的實(shí)例。

  • 下面例子中創(chuàng)建了一個(gè)類 ShoppingListItem,它封裝了購物清單中的某一物品的屬性:名字、數(shù)量和購買狀態(tài):

class ShoppingListItem {
    var name: String?
    var quantity = 1
    var purchased = false
}
var item = ShoppingListItem()
1、結(jié)構(gòu)體的逐一成員構(gòu)造器
  • 除了上面提到的默認(rèn)構(gòu)造器,如果結(jié)構(gòu)體沒有提供自定義的構(gòu)造器,它們將自動(dòng)獲得一個(gè)逐一成員構(gòu)造器,即使結(jié)構(gòu)體的存儲(chǔ)型屬性沒有默認(rèn)值。

  • 逐一成員構(gòu)造器是用來初始化結(jié)構(gòu)體新實(shí)例里成員屬性的快捷方法。

  • 我們?cè)谡{(diào)用逐一成員構(gòu)造器時(shí),通過與成員屬性名相同的參數(shù)名進(jìn)行傳值來完成對(duì)成員屬性的初始賦值。

struct Size {
    var width = 0.0, height = 0.0
}
let twoByTwo = Size(width: 2.0, height: 2.0)

四、值類型的構(gòu)造器代理

  • 構(gòu)造器可以通過調(diào)用其它構(gòu)造器來完成實(shí)例的部分構(gòu)造過程。這一過程稱為構(gòu)造器代理,它能避免多個(gè)構(gòu)造器間的代碼重復(fù)。

  • 構(gòu)造器代理的實(shí)現(xiàn)規(guī)則和形式在值類型和類類型中有所不同。

值類型不支持繼承,所以構(gòu)造器代理的過程相對(duì)簡單,因?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

  • 請(qǐng)注意,如果你為某個(gè)值類型定義了一個(gè)自定義的構(gòu)造器,你將無法訪問到默認(rèn)構(gòu)造器(如果是結(jié)構(gòu)體,還將無法訪問逐一成員構(gòu)造器)。

  • 這種限制可以防止你為值類型增加了一個(gè)額外的且十分復(fù)雜的構(gòu)造器之后,仍然有人錯(cuò)誤的使用自動(dòng)生成的構(gòu)造器

注意
假如你希望默認(rèn)構(gòu)造器、逐一成員構(gòu)造器以及你自己的自定義構(gòu)造器都能用來創(chuàng)建實(shí)例,可以將自定義的構(gòu)造器寫到擴(kuò)展(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)
    }
}

五、類的繼承和構(gòu)造過程

  • 類里面的所有存儲(chǔ)型屬性——包括所有繼承自父類的屬性——都必須在構(gòu)造過程中設(shè)置初始值。 Swift 為類型提供了兩種構(gòu)造器來確保實(shí)例中所有存儲(chǔ)型屬性都能獲得初始值,它們分別是指定構(gòu)造器便利構(gòu)造器
1、指定構(gòu)造器和便利構(gòu)造器
  • 指定構(gòu)造器是類中最主要的構(gòu)造器。一個(gè)指定構(gòu)造器將初始化類中提供的所有屬性,并根據(jù)父類鏈往上調(diào)用父類合適的構(gòu)造器來實(shí)現(xiàn)父類的初始化。

  • 類傾向于擁有少量指定構(gòu)造器,普遍的是一個(gè)類擁有一個(gè)指定構(gòu)造器。指定構(gòu)造器在初始化的地方通過“管道”將初始化過程持續(xù)到父類鏈。

  • 每一個(gè)類都必須至少擁有一個(gè)指定構(gòu)造器。在某些情況下,許多類通過繼承了父類中的指定構(gòu)造器而滿足了這個(gè)條件

  • 便利構(gòu)造器是類中比較次要的、輔助型的構(gòu)造器。你可以定義便利構(gòu)造器來調(diào)用同一個(gè)類中的指定構(gòu)造器,并為其參數(shù)提供默認(rèn)值。你也可以定義便利構(gòu)造器來創(chuàng)建一個(gè)特殊用途或特定輸入值的實(shí)例。

  • 你應(yīng)當(dāng)只在必要的時(shí)候?yàn)轭愄峁┍憷麡?gòu)造器,比方說某種情況下通過使用便利構(gòu)造器來快捷調(diào)用某個(gè)指定構(gòu)造器,能夠節(jié)省更多開發(fā)時(shí)間并讓類的構(gòu)造過程更清晰明了。

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

指定構(gòu)造器便利構(gòu)造器最大的區(qū)別:
指定構(gòu)造器只能調(diào)用父類的指定構(gòu)造器, 沒有父類, 不需要調(diào)用任何構(gòu)造器
遍歷構(gòu)造器只能調(diào)用自己的其他構(gòu)造器, 可以是自己的指定構(gòu)造器, 也可以是自己的遍歷構(gòu)造器, 遍歷構(gòu)造器最終必須調(diào)用自己的指定構(gòu)造器

3、類的構(gòu)造器代理規(guī)則
  • 為了簡化指定構(gòu)造器和便利構(gòu)造器之間的調(diào)用關(guān)系,Swift 采用以下三條規(guī)則來限制構(gòu)造器之間的代理調(diào)用:
規(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)造器必須總是橫向代理
4、兩段式構(gòu)造過程
  • Swift 中類的構(gòu)造過程包含兩個(gè)階段。
第一個(gè)階段,類中的每個(gè)存儲(chǔ)型屬性賦一個(gè)初始值。
當(dāng)每個(gè)存儲(chǔ)型屬性的初始值被賦值后,第二階段開始,
它給每個(gè)類一次機(jī)會(huì),在新實(shí)例準(zhǔn)備使用之前進(jìn)一步定制它們的存儲(chǔ)型屬性。
  • Swift 編譯器將執(zhí)行 4 種有效的安全檢查,以確保兩段式構(gòu)造過程不出錯(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)造器,
如果沒這么做,指定構(gòu)造器賦予的新值將被父類中的構(gòu)造器所覆蓋。

安全檢查 3
便利構(gòu)造器必須為任意屬性(包括同類中定義的)賦新值之前代理調(diào)用同一類中的其它構(gòu)造器,
如果沒這么做,便利構(gòu)造器賦予的新值將被同一類中其它指定構(gòu)造器所覆蓋。

安全檢查 4
構(gòu)造器在第一階段構(gòu)造完成之前,不能調(diào)用任何實(shí)例方法,
不能讀取任何實(shí)例屬性的值,不能引用 self 作為一個(gè)值。
  • 類實(shí)例在第一階段結(jié)束以前并不是完全有效的。只有第一階段完成后,該實(shí)例才會(huì)成為有效實(shí)例,才能訪問屬性和調(diào)用方法。

  • 以下是兩段式構(gòu)造過程中基于上述安全檢查的構(gòu)造流程展示:

階段 1:
1、 某個(gè)指定構(gòu)造器或便利構(gòu)造器被調(diào)用。
2、 完成新實(shí)例內(nèi)存的分配,但此時(shí)內(nèi)存還沒有被初始化。
3、 指定構(gòu)造器確保其所在類引入的所有存儲(chǔ)型屬性都已賦初值。存儲(chǔ)型屬性所屬的內(nèi)存完成初始化。
4、 指定構(gòu)造器將調(diào)用父類的構(gòu)造器,完成父類屬性的初始化。
5、 這個(gè)調(diào)用父類構(gòu)造器的過程沿著構(gòu)造器鏈一直往上執(zhí)行,直到到達(dá)構(gòu)造器鏈的最頂部。
6、 當(dāng)?shù)竭_(dá)了構(gòu)造器鏈最頂部,且已確保所有實(shí)例包含的存儲(chǔ)型屬性都已經(jīng)賦值,

這個(gè)實(shí)例的內(nèi)存被認(rèn)為已經(jīng)完全初始化。此時(shí)階段 1 完成。
階段 2:
1、從頂部構(gòu)造器鏈一直往下,每個(gè)構(gòu)造器鏈中類的指定構(gòu)造器都有機(jī)會(huì)進(jìn)一步定制實(shí)例。
構(gòu)造器此時(shí)可以訪問 self、修改它的屬性并調(diào)用實(shí)例方法等等。
2、最終,任意構(gòu)造器鏈中的便利構(gòu)造器可以有機(jī)會(huì)定制實(shí)例和使用 self。
5、構(gòu)造器的繼承和重寫
  • 在下面的例子中定義了一個(gè)叫 Vehicle 的基類。
  • 基類中聲明了一個(gè)存儲(chǔ)型屬性 numberOfWheels,它是默認(rèn)值為 0Int類型的存儲(chǔ)型屬性。
  • numberOfWheels 屬性用于創(chuàng)建名為 descrpiptionString 類型的計(jì)算型屬性:
class Vehicle {
    var numberOfWheels = 0
    var description: String {
        return "\(numberOfWheels) wheel(s)"
    }
}
  • Vehicle 類只為存儲(chǔ)型屬性提供默認(rèn)值,也沒有提供自定義構(gòu)造器。因此,它會(huì)自動(dòng)獲得一個(gè)默認(rèn)構(gòu)造器, 自動(dòng)獲得的默認(rèn)構(gòu)造器總是類中的指定構(gòu)造器
let vehicle = Vehicle()
print("Vehicle: \(vehicle.description)")
// Vehicle: 0 wheel(s)
  • 下面例子中定義了一個(gè) Vehicle 的子類 Bicycle
class Bicycle: Vehicle {
    override init() {
        super.init()
        numberOfWheels = 2
    }
}
  • 子類 Bicycle 定義了一個(gè)自定義指定構(gòu)造器 init()。這個(gè)指定構(gòu)造器和父類的指定構(gòu)造器相匹配,所以 Bicycle 中的指定構(gòu)造器需要帶上 override 修飾符。
  • 如果你創(chuàng)建一個(gè) Bicycle 實(shí)例,你可以調(diào)用繼承的 description 計(jì)算型屬性去查看屬性 numberOfWheels 是否有改變:
let bicycle = Bicycle()
print("Bicycle: \(bicycle.description)")
// 打印 "Bicycle: 2 wheel(s)"

注意
子類可以在初始化時(shí)修改繼承來的變量屬性,但是不能修改繼承來的常量屬性。

6、構(gòu)造器的自動(dòng)繼承
  • 子類在默認(rèn)情況下不會(huì)繼承父類的構(gòu)造器。但是如果滿足特定條件,父類構(gòu)造器是可以被自動(dòng)繼承的。

  • 假設(shè)你為子類中引入的所有新屬性都提供了默認(rèn)值,以下 2 個(gè)規(guī)則適用:

規(guī)則 1:
如果子類沒有定義任何指定構(gòu)造器,它將自動(dòng)繼承父類所有的指定構(gòu)造器。

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

注意
對(duì)于規(guī)則 2,子類可以將父類的指定構(gòu)造器實(shí)現(xiàn)為便利構(gòu)造器。

7、指定構(gòu)造器和便利構(gòu)造器實(shí)踐
  • 接下來的例子將在實(shí)踐中展示指定構(gòu)造器、便利構(gòu)造器以及構(gòu)造器的自動(dòng)繼承。

  • 這個(gè)例子定義了包含三個(gè)類 FoodRecipeIngredient 以及 ShoppingListItem 的類層次結(jié)構(gòu),并將演示它們的構(gòu)造器是如何相互作用的。

  • 類層次中的基類是 Food,它是一個(gè)簡單的用來封裝食物名字的類。Food類引入了一個(gè)叫做 nameString 類型的屬性,并且提供了兩個(gè)構(gòu)造器來創(chuàng)建 Food 實(shí)例:

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

    convenience init() {
        self.init(name: "[Unnamed]")
    }
}
  • 類類型沒有默認(rèn)的逐一成員構(gòu)造器,所以 Food 類提供了一個(gè)接受單一參數(shù) name 的指定構(gòu)造器。這個(gè)構(gòu)造器可以使用一個(gè)特定的名字來創(chuàng)建新的 Food 實(shí)例:
let namedMeat = Food(name: "Bacon")
// namedMeat 的名字是 "Bacon"
  • Food 類同樣提供了一個(gè)沒有參數(shù)的便利構(gòu)造器 init()。這個(gè) init() 構(gòu)造器為新食物提供了一個(gè)默認(rèn)的占位名字,通過橫向代理到指定構(gòu)造器 init(name: String) 并給參數(shù) name 賦值為[Unnamed] 來實(shí)現(xiàn):
let mysteryMeat = Food()
// mysteryMeat 的名字是 [Unnamed]
  • 類層級(jí)中的第二個(gè)類是 Food 的子類 RecipeIngredient。RecipeIngredient 類用來表示食譜中的一項(xiàng)原料。它引入了 Int 類型的屬性 quantity(以及從 Food 繼承過來的 name 屬性),并且定義了兩個(gè)構(gòu)造器來創(chuàng)建 RecipeIngredient 實(shí)例:
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)
    }
}

RecipeIngredient 類擁有一個(gè)指定構(gòu)造器 init(name: String, quantity: Int),它可以用來填充 RecipeIngredient 實(shí)例的所有屬性值。這個(gè)構(gòu)造器一開始先將傳入的 quantity 參數(shù)賦值給 quantity 屬性,這個(gè)屬性也是唯一在 RecipeIngredient 中新引入的屬性。隨后,構(gòu)造器向上代理到父類 Foodinit(name: String)。這個(gè)過程滿足兩段式構(gòu)造過程中的安全檢查 1。

RecipeIngredient 也定義了一個(gè)便利構(gòu)造器 init(name: String),它只通過 name 來創(chuàng)建 RecipeIngredient的實(shí)例。這個(gè)便利構(gòu)造器假設(shè)任意 RecipeIngredient 實(shí)例的 quantity1,所以不需要顯式指明數(shù)量即可創(chuàng)建出實(shí)例。這個(gè)便利構(gòu)造器的定義可以更加方便和快捷地創(chuàng)建實(shí)例,并且避免了創(chuàng)建多個(gè) quantity1RecipeIngredient 實(shí)例時(shí)的代碼重復(fù)。這個(gè)便利構(gòu)造器只是簡單地橫向代理到類中的指定構(gòu)造器,并為 quantity參數(shù)傳遞 1

注意,RecipeIngredient 的便利構(gòu)造器 init(name: String) 使用了跟 Food 中指定構(gòu)造器 init(name: String) 相同的參數(shù)。由于這個(gè)便利構(gòu)造器重寫了父類的指定構(gòu)造器 init(name: String),因此必須在前面使用 override 修飾符

  • 盡管 RecipeIngredient 將父類的指定構(gòu)造器重寫為了便利構(gòu)造器,但是它依然提供了父類的所有指定構(gòu)造器的實(shí)現(xiàn)。因此,RecipeIngredient 會(huì)自動(dòng)繼承父類的所有便利構(gòu)造器。

  • 在這個(gè)例子中,RecipeIngredient 的父類是 Food,它有一個(gè)便利構(gòu)造器 init()。這個(gè)便利構(gòu)造器會(huì)被 RecipeIngredient 繼承。這個(gè)繼承版本的 init() 在功能上跟 Food 提供的版本是一樣的,只是它會(huì)代理到 RecipeIngredient 版本的 init(name: String) 而不是 Food 提供的版本。

  • 所有的這三種構(gòu)造器都可以用來創(chuàng)建新的 RecipeIngredient 實(shí)例:

let oneMysteryItem = RecipeIngredient()
let oneBacon = RecipeIngredient(name: "Bacon")
let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6)
  • 類層級(jí)中第三個(gè)也是最后一個(gè)類是 RecipeIngredient 的子類,叫做 ShoppingListItem。這個(gè)類構(gòu)建了購物單中出現(xiàn)的某一種食譜原料。
class ShoppingListItem: RecipeIngredient {
    var purchased = false
    var description: String {
        var output = "\(quantity) x \(name)"
        output += purchased ? " ?" : " ?"
        return output
    }
}
  • 由于它為自己引入的所有屬性都提供了默認(rèn)值,并且自己沒有定義任何構(gòu)造器,ShoppingListItem 將自動(dòng)繼承所有父類中的指定構(gòu)造器和便利構(gòu)造器。

  • 你可以使用三個(gè)繼承來的構(gòu)造器來創(chuàng)建 ShoppingListItem 的新實(shí)例:

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 ?

六、可失敗構(gòu)造器

  • 如果一個(gè)類、結(jié)構(gòu)體或枚舉類型的對(duì)象,在構(gòu)造過程中有可能失敗,則為其定義一個(gè)可失敗構(gòu)造器是很有用的。

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

注意
可失敗構(gòu)造器的參數(shù)名和參數(shù)類型,不能與其它非可失敗構(gòu)造器的參數(shù)名,及其參數(shù)類型相同。

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

注意
嚴(yán)格來說,構(gòu)造器都不支持返回值。因?yàn)闃?gòu)造器本身的作用,只是為了確保對(duì)象能被正確構(gòu)造。因此你只是用 return nil 表明可失敗構(gòu)造器構(gòu)造失敗,而不要用關(guān)鍵字 return 來表明構(gòu)造成功。

  • 下例中,定義了一個(gè)名為 Animal 的結(jié)構(gòu)體,其中有一個(gè)名為 speciesString 類型的常量屬性。同時(shí)該結(jié)構(gòu)體還定義了一個(gè)接受一個(gè)名為 speciesString 類型參數(shù)的可失敗構(gòu)造器。這個(gè)可失敗構(gòu)造器檢查傳入的參數(shù)是否為一個(gè)空字符串。如果為空字符串,則構(gòu)造失敗。否則,species屬性被賦值,構(gòu)造成功。
struct Animal {
    let species: String
    init?(species: String) {
        if species.isEmpty {
            return nil
        }
        self.species = species
    }
}
  • 你可以通過該可失敗構(gòu)造器來嘗試構(gòu)建一個(gè) Animal 的實(shí)例,并檢查構(gòu)造過程是否成功:
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è)空字符串作為其參數(shù),則會(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、枚舉類型的可失敗構(gòu)造器
  • 你可以通過一個(gè)帶一個(gè)或多個(gè)參數(shù)的可失敗構(gòu)造器來獲取枚舉類型中特定的枚舉成員。如果提供的參數(shù)無法匹配任何枚舉成員,則構(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è)枚舉成員中獲取一個(gè)相匹配的枚舉成員,當(dāng)參數(shù)的值不能與任何枚舉成員相匹配時(shí),則構(gòu)造失?。?/li>
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."
2、帶原始值的枚舉類型的可失敗構(gòu)造器
  • 帶原始值的枚舉類型會(huì)自帶一個(gè)可失敗構(gòu)造器 init?(rawValue:),該可失敗構(gòu)造器有一個(gè)名為 rawValue 的參數(shù),其類型和枚舉類型的原始值類型一致,如果該參數(shù)的值能夠和某個(gè)枚舉成員的原始值匹配,則該構(gòu)造器會(huì)構(gòu)造相應(yīng)的枚舉成員,否則構(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."

注意:
如果枚舉沒有初始值, 那么就不會(huì)自動(dòng)生成init?(rawValue:)構(gòu)造器

3、構(gòu)造失敗的傳遞
  • 類,結(jié)構(gòu)體,枚舉的可失敗構(gòu)造器可以橫向代理到同類型中的其他可失敗構(gòu)造器。類似的,子類的可失敗構(gòu)造器也能向上代理到父類的可失敗構(gòu)造器。

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

注意
可失敗構(gòu)造器也可以代理到其它的非可失敗構(gòu)造器。通過這種方式,你可以增加一個(gè)可能的失敗狀態(tài)到現(xiàn)有的構(gòu)造過程中。

  • 下面這個(gè)例子,定義了一個(gè)名為 CartItemProduct 類的子類。這個(gè)類建立了一個(gè)在線購物車中的物品的模型,它有一個(gè)名為 quantity 的常量存儲(chǔ)型屬性,并確保該屬性的值至少為 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)
    }
}
  • CartItem 可失敗構(gòu)造器首先驗(yàn)證接收的 quantity 值是否大于等于 1 。倘若 quantity 值無效,則立即終止整個(gè)構(gòu)造過程,返回失敗結(jié)果,且不再執(zhí)行余下代碼。同樣地,Product 的可失敗構(gòu)造器首先檢查 name 值,假如 name 值為空字符串,則構(gòu)造器立即執(zhí)行失敗。

  • 如果你通過傳入一個(gè)非空字符串 name 以及一個(gè)值大于等于 1quantity 來創(chuàng)建一個(gè) CartItem 實(shí)例,那么構(gòu)造方法能夠成功被執(zhí)行:

if let twoSocks = CartItem(name: "sock", quantity: 2) {
    print("Item: \(twoSocks.name), quantity: \(twoSocks.quantity)")
}
// 打印 "Item: sock, quantity: 2"
  • 倘若你以一個(gè)值為 0quantity 來創(chuàng)建一個(gè) CartItem 實(shí)例,那么將導(dǎo)致 CartItem 構(gòu)造器失?。?/li>
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 來創(chuàng)建一個(gè) CartItem 實(shí)例,那么將導(dǎo)致父類 Product 的構(gòu)造過程失?。?/li>
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"
4、重寫一個(gè)可失敗構(gòu)造器
  • 如同其它的構(gòu)造器,你可以在子類中重寫父類的可失敗構(gòu)造器?;蛘吣阋部梢杂米宇惖姆强墒?gòu)造器重寫一個(gè)父類的可失敗構(gòu)造器。這使你可以定義一個(gè)不會(huì)構(gòu)造失敗的子類,即使父類的構(gòu)造器允許構(gòu)造失敗。

  • 注意,當(dāng)你用子類的非可失敗構(gòu)造器重寫父類的可失敗構(gòu)造器時(shí),向上代理到父類的可失敗構(gòu)造器的唯一方式是對(duì)父類的可失敗構(gòu)造器的返回值進(jìn)行強(qiáng)制解包。

注意
你可以用非可失敗構(gòu)造器重寫可失敗構(gòu)造器,但反過來卻不行。

  • 下例定義了一個(gè)名為 Document 的類,name 屬性的值必須為一個(gè)非空字符串或 nil,但不能是一個(gè)空字符串:
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) {
        self.name = name
        if name.isEmpty { return nil }
    }
}
  • 下面這個(gè)例子,定義了一個(gè) Document 類的子類 AutomaticallyNamedDocument。這個(gè)子類重寫了父類的兩個(gè)指定構(gòu)造器,確保了無論是使用 init() 構(gòu)造器,還是使用 init(name:) 構(gòu)造器并為參數(shù)傳遞空字符串,生成的實(shí)例中的 name 屬性總有初始 "[Untitled]"
class AutomaticallyNamedDocument: Document {
    override init() {
        super.init()
        self.name = "[Untitled]"
    }
    override init(name: String) {
        super.init()
        if name.isEmpty {
            self.name = "[Untitled]"
        } else {
            self.name = name
        }
    }
}
  • AutomaticallyNamedDocument 用一個(gè)非可失敗構(gòu)造器 init(name:) 重寫了父類的可失敗構(gòu)造器 init?(name:)。因?yàn)樽宇愑昧硪环N方式處理了空字符串的情況,所以不再需要一個(gè)可失敗構(gòu)造器,因此子類用一個(gè)非可失敗構(gòu)造器代替了父類的可失敗構(gòu)造器。

  • 你可以在子類的非可失敗構(gòu)造器中使用強(qiáng)制解包來調(diào)用父類的可失敗構(gòu)造器。比如,下面的 UntitledDocument 子類的 name 屬性的值總是 "[Untitled]",它在構(gòu)造過程中使用了父類的可失敗構(gòu)造器 init?(name:)

class UntitledDocument: Document {
    override init() {
        super.init(name: "[Untitled]")!
    }
}
  • 在這個(gè)例子中,如果在調(diào)用父類的可失敗構(gòu)造器 init?(name:) 時(shí)傳入的是空字符串,那么強(qiáng)制解包操作會(huì)引發(fā)運(yùn)行時(shí)錯(cuò)誤。不過,因?yàn)檫@里是通過非空的字符串常量來調(diào)用它,所以并不會(huì)發(fā)生運(yùn)行時(shí)錯(cuò)誤。
5、init!可失敗構(gòu)造器
  • 通常來說我們通過在 init 關(guān)鍵字后添加問號(hào)的方式(init?)來定義一個(gè)可失敗構(gòu)造器,但你也可以通過在 init 后面添加驚嘆號(hào)的方式來定義一個(gè)可失敗構(gòu)造器(init!),該可失敗構(gòu)造器將會(huì)構(gòu)建一個(gè)對(duì)應(yīng)類型的隱式解包可選類型的對(duì)象。

  • 你可以在 init? 中代理到 init!,反之亦然。你也可以用 init? 重寫 init!,反之亦然。你還可以用 init 代理到 init!,不過,一旦 init! 構(gòu)造失敗,則會(huì)觸發(fā)一個(gè)斷言。

七、必要構(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)造器的要求,則無須在子類中顯式提供必要構(gòu)造器的實(shí)現(xiàn)。

八、通過閉包或函數(shù)設(shè)置屬性的默認(rèn)值

  • 如果某個(gè)存儲(chǔ)型屬性的默認(rèn)值需要一些定制或設(shè)置,你可以使用閉包或全局函數(shù)為其提供定制的默認(rèn)值。每當(dāng)某個(gè)屬性所在類型的新實(shí)例被創(chuàng)建時(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
    }()
}
  • 注意閉包結(jié)尾的花括號(hào)后面接了一對(duì)空的小括號(hào)。這用來告訴 Swift 立即執(zhí)行此閉包。如果你忽略了這對(duì)括號(hào),相當(dāng)于將閉包本身作為值賦值給了屬性,而不是將閉包的返回值賦值給屬性。

注意
如果你使用閉包來初始化屬性,請(qǐng)記住在閉包執(zhí)行時(shí),實(shí)例的其它部分都還沒有初始化。這意味著你不能在閉包里訪問其它屬性,即使這些屬性有默認(rèn)值。同樣,你也不能使用隱式的 self 屬性,或者調(diào)用任何實(shí)例方法。

最后編輯于
?著作權(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),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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