Swift - 構造過程

構造過程


構造過程是使用類、結構體或枚舉類型的實例之前的準備過程狸涌。在新實例使用前有個過程是必須的削罩,它包括設置實例中每個存儲屬性的初始值和執(zhí)行其他必須的設置或構造過程胀屿。

你要通過定義構造器來實現(xiàn)構造過程,它就像用來創(chuàng)建特定類型新實例的特殊方法僻造。與 Objective-C 中的構造器不同憋他,Swift 的構造器沒有返回值。它們的主要任務是保證某種類型的新實例在第一次使用前完成正確的初始化嫡意。

類的實例也可以通過實現(xiàn)析構器來執(zhí)行它釋放之前自定義的清理工作举瑰。想了解更多關于析構器的內(nèi)容,請參考 析構過程蔬螟。

存儲屬性的初始賦值

類和結構體在創(chuàng)建實例時此迅,必須為所有存儲型屬性設置合適的初始值。存儲型屬性的值不能處于一個未知的狀態(tài)男应。

你可以在構造器中為存儲型屬性設置初始值,也可以在定義屬性時分配默認值滨达。以下小節(jié)將詳細介紹這兩種方法谓传。

注意

當你為存儲型屬性分配默認值或者在構造器中設置初始值時直颅,它們的值是被直接設置的养葵,不會觸發(fā)任何屬性觀察者庸娱。

構造器

構造器在創(chuàng)建某個特定類型的新實例時被調(diào)用着绊。它的最簡形式類似于一個不帶任何形參的實例方法,以關鍵字 init 命名:

init() {
    // 在此處執(zhí)行構造過程
}

下面例子中定義了一個用來保存華氏溫度的結構體 Fahrenheit熟尉,它擁有一個 Double 類型的存儲型屬性 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”

這個結構體定義了一個不帶形參的構造器 init归露,并在里面將存儲型屬性 temperature 的值初始化為 32.0(華氏溫度下水的冰點)。

默認屬性值

如前所述斤儿,你可以在構造器中為存儲型屬性設置初始值剧包。同樣,你也可以在屬性聲明時為其設置默認值往果。

注意

如果一個屬性總是使用相同的初始值疆液,那么為其設置一個默認值比每次都在構造器中賦值要好。兩種方法的最終結果是一樣的陕贮,只不過使用默認值讓屬性的初始化和聲明結合得更緊密堕油。它能讓你的構造器更簡潔、更清晰肮之,且能通過默認值自動推導出屬性的類型掉缺;同時,它也能讓你充分利用默認構造器戈擒、構造器繼承等特性眶明,后續(xù)章節(jié)將講到。

你可以通過在屬性聲明時為 temperature 提供默認值來使用更簡單的方式定義結構體 Fahrenheit

struct Fahrenheit {
    var temperature = 32.0
}

自定義構造過程

你可以通過輸入形參和可選屬性類型來自定義構造過程筐高,也可以在構造過程中分配常量屬性搜囱。這些都將在后面章節(jié)中提到。

形參的構造過程

自定義構造過程時凯傲,可以在定義中提供構造形參犬辰,指定其值的類型和名字。構造形參的功能和語法跟函數(shù)和方法的形參相同冰单。

下面例子中定義了一個用來保存攝氏溫度的結構體 Celsius幌缝。它定義了兩個不同的構造器:init(fromFahrenheit:)init(fromKelvin:),二者分別通過接受不同溫標下的溫度值來創(chuàng)建新的實例:

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

第一個構造器擁有一個構造形參诫欠,其實參標簽為 fromFahrenheit涵卵,形參命名為 fahrenheit;第二個構造器也擁有一個構造形參荒叼,其實參標簽為 fromKelvin轿偎,形參命名為 kelvin。這兩個構造器都將單一的實參轉換成攝氏溫度值被廓,并保存在屬性 temperatureInCelsius 中坏晦。

形參命名和實參標簽

跟函數(shù)和方法形參相同,構造形參可以同時使用在構造器里使用的形參命名和一個外部調(diào)用構造器時使用的實參標簽。

然而昆婿,構造器并不像函數(shù)和方法那樣在括號前有一個可辨別的方法名球碉。因此在調(diào)用構造器時,主要通過構造器中形參命名和類型來確定應該被調(diào)用的構造器仓蛆。正因如此睁冬,如果你在定義構造器時沒有提供實參標簽,Swift 會為構造器的每個形參自動生成一個實參標簽看疙。

以下例子中定義了一個結構體 Color豆拨,它包含了三個常量:redgreenblue能庆。這些屬性可以存儲 0.01.0 之間的值施禾,用來表明顏色中紅、綠相味、藍成分的含量拾积。

Color 提供了一個構造器,為紅藍綠提供三個合適 Double 類型的形參命名丰涉。Color 也提供了第二個構造器拓巧,它只包含名為 whiteDouble 類型的形參,它為三個顏色的屬性提供相同的值一死。

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

兩種構造器都能通過為每一個構造器形參提供命名值來創(chuàng)建一個新的 Color 實例:

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

注意肛度,如果不通過實參標簽傳值,這個構造器是沒法調(diào)用的投慈。如果構造器定義了某個實參標簽承耿,就必須使用它,忽略它將導致編譯期錯誤:

let veryGreen = Color(0.0, 1.0, 0.0)
// 報編譯期錯誤-需要實參標簽

不帶實參標簽的構造器形參

如果你不希望構造器的某個形參使用實參標簽伪煤,可以使用下劃線(_)來代替顯式的實參標簽來重寫默認行為加袋。

下面是之前 形參的構造過程Celsius 例子的擴展,多了一個用已經(jīng)的攝氏表示的 Double 類型值來創(chuàng)建新的 Celsius 實例的額外構造器:

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

構造器調(diào)用 Celsius(37.0) 意圖明確抱既,不需要實參標簽职烧。因此適合使用 init(_ celsius: Double) 這樣的構造器,從而可以通過提供未命名的 Double 值來調(diào)用構造器防泵。

可選屬性類型

如果你自定義的類型有一個邏輯上允許值為空的存儲型屬性——無論是因為它無法在初始化時賦值蚀之,還是因為它在之后某個時機可以賦值為空——都需要將它聲明為 可選類型〗菖ⅲ可選類型的屬性將自動初始化為 nil足删,表示這個屬性是特意在構造過程設置為空。

下面例子中定義了類 SurveyQuestion锁右,它包含一個可選 String 屬性 response

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? 類型讶泰,或者說是 “可選類型 String“。當 SurveyQuestion 的實例初始化時贱纠,它將自動賦值為 nil峻厚,表明“暫時還沒有字符“。

構造過程中常量屬性的賦值

你可以在構造過程中的任意時間點給常量屬性賦值谆焊,只要在構造過程結束時它設置成確定的值。一旦常量屬性被賦值浦夷,它將永遠不可更改辖试。

注意

對于類的實例來說,它的常量屬性只能在定義它的類的構造過程中修改劈狐;不能在子類中修改罐孝。

你可以修改上面的 SurveyQuestion 示例,用常量屬性替代變量屬性 text肥缔,表示問題內(nèi)容 textSurveyQuestion 的實例被創(chuàng)建之后不會再被修改莲兢。盡管 text 屬性現(xiàn)在是常量,我們?nèi)匀豢梢栽陬惖臉嬙炱髦性O置它的值:

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.)"

默認構造器

如果結構體或類為所有屬性提供了默認值续膳,又沒有提供任何自定義的構造器改艇,那么 Swift 會給這些結構體或類提供一個默認構造器。這個默認構造器將簡單地創(chuàng)建一個所有屬性值都設置為它們默認值的實例坟岔。

下面例子中定義了一個類 ShoppingListItem谒兄,它封裝了購物清單中的某一物品的名字(name)、數(shù)量(quantity)和購買狀態(tài) purchase state

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

由于 ShoppingListItem 類中的所有屬性都有默認值社付,且它是沒有父類的基類承疲,它將自動獲得一個將為所有屬性設置默認值的并創(chuàng)建實例的默認構造器(由于 name 屬性是可選 String 類型,它將接收一個默認 nil 的默認值鸥咖,盡管代碼中沒有寫出這個值)燕鸽。上面例子中使用默認構造器創(chuàng)造了一個 ShoppingListItem 類的實例(使用 ShoppingListItem() 形式的構造器語法),并將其賦值給變量 item啼辣。

結構體的逐一成員構造器

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

逐一成員構造器是用來初始化結構體新實例里成員屬性的快捷方法住涉。新實例的屬性初始值可以通過名字傳入逐一成員構造器中麸锉。

下面例子中定義了一個結構體 Size,它包含兩個屬性 widthheight舆声。根據(jù)這兩個屬性默認賦值為 0.0 花沉,它們的類型被推斷出來為 Double柳爽。

結構體 Size 自動獲得了一個逐一成員構造器 init(width:height:)。你可以用它來創(chuàng)建新的 Size 實例:

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

當你調(diào)用一個逐一成員構造器(memberwise initializer)時碱屁,可以省略任何一個有默認值的屬性磷脯。在上面這個例子中,Size 結構體的 heightwidth 屬性各有一個默認值娩脾。你可以省略兩者或兩者之一赵誓,對于被省略的屬性,構造器會使用默認值柿赊。舉個例子:

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"

值類型的構造器代理

構造器可以通過調(diào)用其它構造器來完成實例的部分構造過程俩功。這一過程稱為構造器代理,它能避免多個構造器間的代碼重復碰声。

構造器代理的實現(xiàn)規(guī)則和形式在值類型和類類型中有所不同诡蜓。值類型(結構體和枚舉類型)不支持繼承,所以構造器代理的過程相對簡單胰挑,因為它們只能代理給自己的其它構造器蔓罚。類則不同,它可以繼承自其它類(請參考 繼承)瞻颂。這意味著類有責任保證其所有繼承的存儲型屬性在構造時也能正確的初始化豺谈。這些責任將在后續(xù)章節(jié) 類的繼承和構造過程 中介紹。

對于值類型蘸朋,你可以使用 self.init 在自定義的構造器中引用相同類型中的其它構造器核无。并且你只能在構造器內(nèi)部調(diào)用 self.init

請注意藕坯,如果你為某個值類型定義了一個自定義的構造器团南,你將無法訪問到默認構造器(如果是結構體,還將無法訪問逐一成員構造器)炼彪。這種限制避免了在一個更復雜的構造器中做了額外的重要設置吐根,但有人不小心使用自動生成的構造器而導致錯誤的情況。

注意

假如你希望默認構造器辐马、逐一成員構造器以及你自己的自定義構造器都能用來創(chuàng)建實例拷橘,可以將自定義的構造器寫到擴展(extension)中,而不是寫在值類型的原始定義中喜爷。想查看更多內(nèi)容冗疮,請查看 擴展 章節(jié)。

下面例子定義一個自定義結構體 Rect檩帐,用來代表幾何矩形术幔。這個例子需要兩個輔助的結構體 SizePoint,它們各自為其所有的屬性提供了默認初始值 0.0湃密。

struct Size {
    var width = 0.0, height = 0.0
}

struct Point {
    var x = 0.0, y = 0.0
}

你可以通過以下三種方式為 Rect 創(chuàng)建實例——使用含有默認值的 originsize 屬性來初始化诅挑;提供指定的 originsize 實例來初始化四敞;提供指定的 centersize 來初始化。在下面 Rect 結構體定義中拔妥,我們?yōu)檫@三種方式提供了三個自定義的構造器:

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

第一個 Rect 構造器 init()忿危,在功能上跟沒有自定義構造器時自動獲得的默認構造器是一樣的。這個構造器是函數(shù)體是空的没龙,使用一對大括號 {} 來表示铺厨。調(diào)用這個構造器將返回一個 Rect 實例,它的 originsize 屬性都使用定義時的默認值 Point(x: 0.0, y: 0.0)Size(width: 0.0, height: 0.0)

let basicRect = Rect()
// basicRect 的 origin 是 (0.0, 0.0)兜畸,size 是 (0.0, 0.0)

第二個 Rect 構造器 init(origin:size:)努释,在功能上跟結構體在沒有自定義構造器時獲得的逐一成員構造器是一樣的。這個構造器只是簡單地將 originsize 的實參值賦給對應的存儲型屬性:

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)

第三個 Rect 構造器 init(center:size:) 稍微復雜一點。它先通過 centersize 的值計算出 origin 的坐標煞躬,然后再調(diào)用(或者說代理給)init(origin:size:) 構造器來將新的 originsize 值賦值到對應的屬性中:

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)

構造器 init(center:size:) 可以直接將 originsize 的新值賦值到對應的屬性中。然而恩沛,構造器 init(center:size:) 通過使用提供了相關功能的現(xiàn)有構造器將會更加便捷(而且意圖更清晰)在扰。

注意

如果你想用另外一種不需要自己定義 init()init(origin:size:) 的方式來實現(xiàn)這個例子,請參考 擴展雷客。

類的繼承和構造過程

類里面的所有存儲型屬性——包括所有繼承自父類的屬性——都必須在構造過程中設置初始值芒珠。

Swift 為類類型提供了兩種構造器來確保實例中所有存儲型屬性都能獲得初始值,它們被稱為指定構造器和便利構造器卦绣。

指定構造器和便利構造器

指定構造器是類中最主要的構造器宪祥。一個指定構造器將初始化類中提供的所有屬性官卡,并調(diào)用合適的父類構造器讓構造過程沿著父類鏈繼續(xù)往上進行。

類傾向于擁有極少的指定構造器娜汁,普遍的是一個類只擁有一個指定構造器。指定構造器像一個個“漏斗”放在構造過程發(fā)生的地方兄朋,讓構造過程沿著父類鏈繼續(xù)往上進行掐禁。

每一個類都必須至少擁有一個指定構造器。在某些情況下颅和,許多類通過繼承了父類中的指定構造器而滿足了這個條件傅事。具體內(nèi)容請參考后續(xù)章節(jié) 構造器的自動繼承

便利構造器是類中比較次要的峡扩、輔助型的構造器蹭越。你可以定義便利構造器來調(diào)用同一個類中的指定構造器,并為部分形參提供默認值有额。你也可以定義便利構造器來創(chuàng)建一個特殊用途或特定輸入值的實例般又。

你應當只在必要的時候為類提供便利構造器彼绷,比方說某種情況下通過使用便利構造器來快捷調(diào)用某個指定構造器,能夠節(jié)省更多開發(fā)時間并讓類的構造過程更清晰明了茴迁。

指定構造器和便利構造器的語法

類的指定構造器的寫法跟值類型簡單構造器一樣:

init(parameters) {
    statements
}

便利構造器也采用相同樣式的寫法寄悯,但需要在 init 關鍵字之前放置 convenience 關鍵字,并使用空格將它們倆分開:

convenience init(parameters) {
    statements
}

類類型的構造器代理

為了簡化指定構造器和便利構造器之間的調(diào)用關系堕义,Swift 構造器之間的代理調(diào)用遵循以下三條規(guī)則:

規(guī)則 1

指定構造器必須調(diào)用其直接父類的的指定構造器猜旬。

規(guī)則 2

便利構造器必須調(diào)用類中定義的其它構造器。

規(guī)則 3

便利構造器最后必須調(diào)用指定構造器倦卖。

一個更方便記憶的方法是:

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

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

這些規(guī)則可以通過下面圖例來說明:

構造器代理圖

如圖所示洒擦,父類中包含一個指定構造器和兩個便利構造器。其中一個便利構造器調(diào)用了另外一個便利構造器怕膛,而后者又調(diào)用了唯一的指定構造器。這滿足了上面提到的規(guī)則 2 和 3掸茅。這個父類沒有自己的父類昧狮,所以規(guī)則 1 沒有用到逗鸣。

子類中包含兩個指定構造器和一個便利構造器绰精。便利構造器必須調(diào)用兩個指定構造器中的任意一個,因為它只能調(diào)用同一個類里的其他構造器茬底。這滿足了上面提到的規(guī)則 2 和 3沪悲。而兩個指定構造器必須調(diào)用父類中唯一的指定構造器,這滿足了規(guī)則 1阱表。

注意

這些規(guī)則不會影響類的實例如何創(chuàng)建殿如。任何上圖中展示的構造器都可以用來創(chuàng)建完全初始化的實例。這些規(guī)則只影響類的構造器如何實現(xiàn)最爬。

下面圖例中展示了一種涉及四個類的更復雜的類層級結構涉馁。它演示了指定構造器是如何在類層級中充當“漏斗”的作用,在類的構造器鏈上簡化了類之間的相互關系爱致。

復雜構造器代理圖

兩段式構造過程

Swift 中類的構造過程包含兩個階段烤送。第一個階段,類中的每個存儲型屬性賦一個初始值糠悯。當每個存儲型屬性的初始值被賦值后帮坚,第二階段開始妻往,它給每個類一次機會,在新實例準備使用之前進一步自定義它們的存儲型屬性试和。

兩段式構造過程的使用讓構造過程更安全讯泣,同時在整個類層級結構中給予了每個類完全的靈活性。兩段式構造過程可以防止屬性值在初始化之前被訪問好渠,也可以防止屬性被另外一個構造器意外地賦予不同的值。

注意

Swift 的兩段式構造過程跟 Objective-C 中的構造過程類似霍掺。最主要的區(qū)別在于階段 1,Objective-C 給每一個屬性賦值 0 或空值(比如說 0nil)剩岳。Swift 的構造流程則更加靈活晓铆,它允許你設置定制的初始值,并自如應對某些屬性不能以 0nil 作為合法默認值的情況。

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

安全檢查 1

指定構造器必須保證它所在類的所有屬性都必須先初始化完成,之后才能將其它構造任務向上代理給父類中的構造器滋将。

如上所述,一個對象的內(nèi)存只有在其所有存儲型屬性確定之后才能完全初始化橱脸。為了滿足這一規(guī)則,指定構造器必須保證它所在類的屬性在它往上代理之前先完成初始化栏赴。

安全檢查 2

指定構造器必須在為繼承的屬性設置新值之前向上代理調(diào)用父類構造器。如果沒這么做,指定構造器賦予的新值將被父類中的構造器所覆蓋扩劝。

安全檢查 3

便利構造器必須為任意屬性(包括所有同類中定義的)賦新值之前代理調(diào)用其它構造器。如果沒這么做簇秒,便利構造器賦予的新值將被該類的指定構造器所覆蓋。

安全檢查 4

構造器在第一階段構造完成之前拆内,不能調(diào)用任何實例方法,不能讀取任何實例屬性的值刻肄,不能引用 self 作為一個值。

類的實例在第一階段結束以前并不是完全有效的麦到。只有第一階段完成后,類的實例才是有效的,才能訪問屬性和調(diào)用方法桃移。

以下是基于上述安全檢查的兩段式構造過程展示:

階段 1

  • 類的某個指定構造器或便利構造器被調(diào)用。

  • 完成類的新實例內(nèi)存的分配第步,但此時內(nèi)存還沒有被初始化刷袍。

  • 指定構造器確保其所在類引入的所有存儲型屬性都已賦初值。存儲型屬性所屬的內(nèi)存完成初始化。

  • 指定構造器切換到父類的構造器哥力,對其存儲屬性完成相同的任務寞射。

  • 這個過程沿著類的繼承鏈一直往上執(zhí)行,直到到達繼承鏈的最頂部侵浸。

  • 當?shù)竭_了繼承鏈最頂部混蔼,而且繼承鏈的最后一個類已確保所有的存儲型屬性都已經(jīng)賦值遵湖,這個實例的內(nèi)存被認為已經(jīng)完全初始化。此時階段 1 完成。

階段 2

  • 從繼承鏈頂部往下,繼承鏈中每個類的指定構造器都有機會進一步自定義實例挺智。構造器此時可以訪問 self赴涵、修改它的屬性并調(diào)用實例方法等等扇苞。

  • 最終,繼承鏈中任意的便利構造器有機會自定義實例和使用 self

下圖展示了在假定的子類和父類之間的構造階段 1:

構建過程階段1

在這個例子中蜒谤,構造過程從對子類中一個便利構造器的調(diào)用開始。這個便利構造器此時還不能修改任何屬性,它會代理到該類中的指定構造器。

如安全檢查 1 所示,指定構造器將確保所有子類的屬性都有值晌姚。然后它將調(diào)用父類的指定構造器,并沿著繼承鏈一直往上完成父類的構造過程。

父類中的指定構造器確保所有父類的屬性都有值。由于沒有更多的父類需要初始化,也就無需繼續(xù)向上代理畜隶。

一旦父類中所有屬性都有了初始值,實例的內(nèi)存被認為是完全初始化,階段 1 完成籽慢。

以下展示了相同構造過程的階段 2:

構建過程階段2

父類中的指定構造器現(xiàn)在有機會進一步自定義實例(盡管這不是必須的)浸遗。

一旦父類中的指定構造器完成調(diào)用,子類中的指定構造器可以執(zhí)行更多的自定義操作(這也不是必須的)箱亿。

最終,一旦子類的指定構造器完成調(diào)用郑藏,最開始被調(diào)用的便利構造器可以執(zhí)行更多的自定義操作阁吝。

構造器的繼承和重寫

跟 Objective-C 中的子類不同,Swift 中的子類默認情況下不會繼承父類的構造器碧聪。Swift 的這種機制可以防止一個父類的簡單構造器被一個更精細的子類繼承挺狰,而在用來創(chuàng)建子類時的新實例時沒有完全或錯誤被初始化。

注意

父類的構造器僅會在安全和適當?shù)哪承┣闆r下被繼承篇亭。具體內(nèi)容請參考后續(xù)章節(jié) 構造器的自動繼承

假如你希望自定義的子類中能提供一個或多個跟父類相同的構造器盟榴,你可以在子類中提供這些構造器的自定義實現(xiàn)镊绪。

當你在編寫一個和父類中指定構造器相匹配的子類構造器時,你實際上是在重寫父類的這個指定構造器。因此,你必須在定義子類構造器時帶上 override 修飾符纠屋。即使你重寫的是系統(tǒng)自動提供的默認構造器栅葡,也需要帶上 override 修飾符蛋欣,具體內(nèi)容請參考 默認構造器楣富。

正如重寫屬性免都,方法或者是下標茵乱,override 修飾符會讓編譯器去檢查父類中是否有相匹配的指定構造器孟岛,并驗證構造器參數(shù)是否被按預想中被指定瓶竭。

注意

當你重寫一個父類的指定構造器時,你總是需要寫 override 修飾符蚀苛,即使是為了實現(xiàn)子類的便利構造器在验。

相反,如果你編寫了一個和父類便利構造器相匹配的子類構造器堵未,由于子類不能直接調(diào)用父類的便利構造器(每個規(guī)則都在上文 類的構造器代理規(guī)則 有所描述)腋舌,因此,嚴格意義上來講渗蟹,你的子類并未對一個父類構造器提供重寫块饺。最后的結果就是赞辩,你在子類中“重寫”一個父類便利構造器時,不需要加 override 修飾符授艰。

在下面的例子中定義了一個叫 Vehicle 的基類辨嗽。基類中聲明了一個存儲型屬性 numberOfWheels淮腾,它是默認值為 Int 類型的 0糟需。numberOfWheels 屬性用在一個描述車輛特征 String 類型為 descrpiption 的計算型屬性中:

class Vehicle {
    var numberOfWheels = 0
    var description: String {
        return "\(numberOfWheels) wheel(s)"
    }
}

Vehicle 類只為存儲型屬性提供默認值,也沒有提供自定義構造器谷朝。因此洲押,它會自動獲得一個默認構造器,具體內(nèi)容請參考 默認構造器圆凰。默認構造器(如果有的話)總是類中的指定構造器杈帐,可以用于創(chuàng)建 numberOfWheels0Vehicle 實例:

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

下面例子中定義了一個 Vehicle 的子類 Bicycle

class Bicycle: Vehicle {
    override init() {
        super.init()
        numberOfWheels = 2
    }
}

子類 Bicycle 定義了一個自定義指定構造器 init()。這個指定構造器和父類的指定構造器相匹配专钉,所以 Bicycle 中這個版本的構造器需要帶上 override 修飾符挑童。

Bicycle 的構造器 init() 以調(diào)用 super.init() 方法開始,這個方法的作用是調(diào)用 Bicycle 的父類 Vehicle 的默認構造器跃须。這樣可以確保 Bicycle 在修改屬性之前站叼,它所繼承的屬性 numberOfWheels 能被 Vehicle 類初始化。在調(diào)用 super.init() 之后菇民,屬性 numberOfWheels 的原值被新值 2 替換大年。

如果你創(chuàng)建一個 Bicycle 實例,你可以調(diào)用繼承的 description 計算型屬性去查看屬性 numberOfWheels 是否有改變:

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

如果子類的構造器沒有在階段 2 過程中做自定義操作玉雾,并且父類有一個無參數(shù)的指定構造器翔试,你可以在所有子類的存儲屬性賦值之后省略 super.init() 的調(diào)用。

這個例子定義了另一個 Vehicle 的子類 Hoverboard 复旬,只設置它的 color 屬性垦缅。這個構造器依賴隱式調(diào)用父類的構造器來完成,而不是顯示調(diào)用 super.init()驹碍。

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

Hoverboard 的實例用 Vehicle 構造器里默認的輪子數(shù)量壁涎。

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

注意

子類可以在構造過程修改繼承來的變量屬性,但是不能修改繼承來的常量屬性志秃。

構造器的自動繼承

如上所述怔球,子類在默認情況下不會繼承父類的構造器。但是如果滿足特定條件浮还,父類構造器是可以被自動繼承的竟坛。事實上,這意味著對于許多常見場景你不必重寫父類的構造器,并且可以在安全的情況下以最小的代價繼承父類的構造器担汤。

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

規(guī)則 1

如果子類沒有定義任何指定構造器,它將自動繼承父類所有的指定構造器崭歧。

規(guī)則 2

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

即使你在子類中添加了更多的便利構造器率碾,這兩條規(guī)則仍然適用叔营。

注意

子類可以將父類的指定構造器實現(xiàn)為便利構造器來滿足規(guī)則 2。

指定構造器和便利構造器實踐

接下來的例子將在實踐中展示指定構造器所宰、便利構造器以及構造器的自動繼承审编。這個例子定義了包含三個類 FoodRecipeIngredient 以及 ShoppingListItem 的層級結構歧匈,并將演示它們的構造器是如何相互作用的。

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

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

    convenience init() {
        self.init(name: "[Unnamed]")
    }
}

下圖中展示了 Food 的構造器鏈:

Food 構造器鏈

類類型沒有默認的逐一成員構造器矮湘,所以 Food 類提供了一個接受單一參數(shù) name 的指定構造器斟冕。這個構造器可以使用一個特定的名字來創(chuàng)建新的 Food 實例:

let namedMeat = Food(name: "Bacon")
// namedMeat 的名字是 "Bacon"

Food 類中的構造器 init(name: String) 被定義為一個指定構造器,因為它能確保 Food 實例的所有存儲型屬性都被初始化缅阳。Food 類沒有父類磕蛇,所以 init(name: String) 構造器不需要調(diào)用 super.init() 來完成構造過程。

Food 類同樣提供了一個沒有參數(shù)的便利構造器 init()十办。這個 init() 構造器為新食物提供了一個默認的占位名字秀撇,通過橫向代理到指定構造器 init(name: String) 并給參數(shù) name 賦值為 [Unnamed] 來實現(xiàn):

let mysteryMeat = Food()
// mysteryMeat 的名字是 [Unnamed]

層級中的第二個類是 Food 的子類 RecipeIngredientRecipeIngredient 類用來表示食譜中的一項原料向族。它引入了 Int 類型的屬性 quantity(以及從 Food 繼承過來的 name 屬性)呵燕,并且定義了兩個構造器來創(chuàng)建 RecipeIngredient 實例:

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 類的構造器鏈:

RecipeIngredient 構造器

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

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

RecipeIngredient 的便利構造器 init(name: String) 使用了跟 Food 中指定構造器 init(name: String) 相同的形參从媚。由于這個便利構造器重寫了父類的指定構造器 init(name: String),因此必須在前面使用 override 修飾符(參見 構造器的繼承和重寫)患整。

盡管 RecipeIngredient 將父類的指定構造器重寫為了便利構造器拜效,但是它依然提供了父類的所有指定構造器的實現(xiàn)。因此各谚,RecipeIngredient 會自動繼承父類的所有便利構造器紧憾。

在這個例子中,RecipeIngredient 的父類是 Food昌渤,它有一個便利構造器 init()赴穗。這個便利構造器會被 RecipeIngredient 繼承。這個繼承版本的 init() 在功能上跟 Food 提供的版本是一樣的膀息,只是它會代理到 RecipeIngredient 版本的 init(name: String) 而不是 Food 提供的版本般眉。

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

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

類層級中第三個也是最后一個類是 RecipeIngredient 的子類,叫做 ShoppingListItem潜支。這個類構建了購物單中出現(xiàn)的某一種食譜原料甸赃。

購物單中的每一項總是從未購買狀態(tài)開始的。為了呈現(xiàn)這一事實冗酿,ShoppingListItem 引入了一個 Boolean(布爾類型) 的屬性 purchased埠对,它的默認值是 falseShoppingListItem 還添加了一個計算型屬性 description裁替,它提供了關于 ShoppingListItem 實例的一些文字描述:

class ShoppingListItem: RecipeIngredient {
    var purchased = false
    var description: String {
        var output = "\(quantity) x \(name)"
        output += purchased ? " ?" : " ?"
        return output
    }
}

注意

ShoppingListItem 沒有定義構造器來為 purchased 提供初始值项玛,因為添加到購物單的物品的初始狀態(tài)總是未購買。

因為它為自己引入的所有屬性都提供了默認值弱判,并且自己沒有定義任何構造器稍计,ShoppingListItem 將自動繼承所有父類中的指定構造器和便利構造器。

下圖展示了這三個類的構造器鏈:

三類構造器圖

你可以使用三個繼承來的構造器來創(chuàng)建 ShoppingListItem 的新實例:

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 ?

如上所述裕循,例子中通過字面量方式創(chuàng)建了一個數(shù)組 breakfastList臣嚣,它包含了三個 ShoppingListItem 實例,因此數(shù)組的類型也能被自動推導為 [ShoppingListItem]剥哑。在數(shù)組創(chuàng)建完之后硅则,數(shù)組中第一個 ShoppingListItem 實例的名字從 [Unnamed] 更改為 Orange juice,并標記狀態(tài)為已購買株婴。打印數(shù)組中每個元素的描述顯示了它們都已按照預期被賦值怎虫。

可失敗構造器

有時,定義一個構造器可失敗的類大审,結構體或者枚舉是很有用的粮彤。這里所指的“失敗” 指的是,如給構造器傳入無效的形參啥么,或缺少某種所需的外部資源登舞,又或是不滿足某種必要的條件等。

為了妥善處理這種構造過程中可能會失敗的情況饥臂。你可以在一個類,結構體或是枚舉類型的定義中似踱,添加一個或多個可失敗構造器隅熙。其語法為在 init 關鍵字后面添加問號(init?)。

注意

可失敗構造器的參數(shù)名和參數(shù)類型核芽,不能與其它非可失敗構造器的參數(shù)名囚戚,及其參數(shù)類型相同。

可失敗構造器會創(chuàng)建一個類型為自身類型的可選類型的對象轧简。你通過 return nil 語句來表明可失敗構造器在何種情況下應該 “失敗”驰坊。

注意

嚴格來說,構造器都不支持返回值哮独。因為構造器本身的作用拳芙,只是為了確保對象能被正確構造。因此你只是用 return nil 表明可失敗構造器構造失敗皮璧,而不要用關鍵字 return 來表明構造成功舟扎。

例如,實現(xiàn)針對數(shù)字類型轉換的可失敗構造器悴务。確保數(shù)字類型之間的轉換能保持精確的值睹限,使用這個 init(exactly:) 構造器。如果類型轉換不能保持值不變,則這個構造器構造失敗羡疗。

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”

下例中,定義了一個名為 Animal 的結構體叨恨,其中有一個名為 speciesString 類型的常量屬性柳刮。同時該結構體還定義了一個接受一個名為 speciesString 類型形參的可失敗構造器。這個可失敗構造器檢查傳入的species 值是否為一個空字符串特碳。如果為空字符串诚亚,則構造失敗。否則午乓,species 屬性被賦值站宗,構造成功。

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

你可以通過該可失敗構造器來嘗試構建一個 Animal 的實例益愈,并檢查構造過程是否成功:

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”

如果你給該可失敗構造器傳入一個空字符串到形參 species梢灭,則會導致構造失敗:

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”

注意

檢查空字符串的值(如 ""蒸其,而不是 "Giraffe" )和檢查值為 nil 的可選類型的字符串是兩個完全不同的概念敏释。上例中的空字符串("")其實是一個有效的,非可選類型的字符串摸袁。這里我們之所以讓 Animal 的可失敗構造器構造失敗钥顽,只是因為對于 Animal 這個類的 species 屬性來說,它更適合有一個具體的值靠汁,而不是空字符串蜂大。

枚舉類型的可失敗構造器

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

下例中,定義了一個名為 TemperatureUnit 的枚舉類型踢星。其中包含了三個可能的枚舉狀態(tài)(Kelvin澳叉、CelsiusFahrenheit),以及一個根據(jù)表示溫度單位的 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("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.”

帶原始值的枚舉類型的可失敗構造器

帶原始值的枚舉類型會自帶一個可失敗構造器 init?(rawValue:)藏否,該可失敗構造器有一個合適的原始值類型的 rawValue 形參泌枪,選擇找到的相匹配的枚舉成員,找不到則構造失敗秕岛。

因此上面的 TemperatureUnit 的例子可以用原始值類型的 Character 和進階的 init?(rawValue:) 構造器重寫為:

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.”

構造失敗的傳遞

類碌燕、結構體误证、枚舉的可失敗構造器可以橫向代理到它們自己其他的可失敗構造器。類似的修壕,子類的可失敗構造器也能向上代理到父類的可失敗構造器愈捅。

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

注意

可失敗構造器也可以代理到其它的不可失敗構造器青团。通過這種方式譬巫,你可以增加一個可能的失敗狀態(tài)到現(xiàn)有的構造過程中。

下面這個例子督笆,定義了一個名為 CartItemProduct 類的子類芦昔。這個類建立了一個在線購物車中的物品的模型,它有一個名為 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)
    }
}

CartItem 可失敗構造器首先驗證接收的 quantity 值是否大于等于 1 咕缎。倘若 quantity 值無效宝冕,則立即終止整個構造過程烘豹,返回失敗結果,且不再執(zhí)行余下代碼位仁。同樣地晒杈,Product 的可失敗構造器首先檢查 name 值嫂伞,假如 name 值為空字符串,則構造器立即執(zhí)行失敗拯钻。

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

if let twoSocks = CartItem(name: "sock", quantity: 2) {
    print("Item: \(twoSocks.name), quantity: \(twoSocks.quantity)")
}
// 打印“Item: sock, quantity: 2”

倘若你以一個值為 0 的 quantity 來創(chuàng)建一個 CartItem 實例,那么將導致 CartItem 構造器失斔低ァ:

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 來創(chuàng)建一個 CartItem 實例郑趁,那么將導致父類 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”

重寫一個可失敗構造器

如同其它的構造器,你可以在子類中重寫父類的可失敗構造器寡润±υ鳎或者你也可以用子類的非可失敗構造器重寫一個父類的可失敗構造器。這使你可以定義一個不會構造失敗的子類梭纹,即使父類的構造器允許構造失敗躲惰。

注意,當你用子類的非可失敗構造器重寫父類的可失敗構造器時变抽,向上代理到父類的可失敗構造器的唯一方式是對父類的可失敗構造器的返回值進行強制解包础拨。

注意

你可以用非可失敗構造器重寫可失敗構造器氮块,但反過來卻不行。

下例定義了一個名為 Document 的類诡宗。這個類模擬一個文檔并可以用 name 屬性來構造滔蝉,屬性的值必須為一個非空字符串或 nil,但不能是一個空字符串:

class Document {
    var name: String?
    // 該構造器創(chuàng)建了一個 name 屬性的值為 nil 的 document 實例
    init() {}
    // 該構造器創(chuàng)建了一個 name 屬性的值為非空字符串的 document 實例
    init?(name: String) {
        if name.isEmpty { return nil }
        self.name = name
    }
}

下面這個例子塔沃,定義了一個 Document 類的子類 AutomaticallyNamedDocument蝠引。這個子類重寫了所有父類引入的指定構造器。這些重寫確保了無論是使用 init() 構造器蛀柴,還是使用 init(name:) 構造器螃概,在沒有名字或者形參傳入空字符串時,生成的實例中的 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 用一個不可失敗構造器 init(name:) 重寫了父類的可失敗構造器 init?(name:)鸽疾。因為子類用另一種方式處理了空字符串的情況吊洼,所以不再需要一個可失敗構造器,因此子類用一個不可失敗構造器代替了父類的可失敗構造器肮韧。

你可以在子類的不可失敗構造器中使用強制解包來調(diào)用父類的可失敗構造器融蹂。比如,下面的 UntitledDocument 子類的 name 屬性的值總是 "[Untitled]"弄企,它在構造過程中使用了父類的可失敗構造器 init?(name:)

class UntitledDocument: Document {
    override init() {
        super.init(name: "[Untitled]")!
    }
}

在這個例子中超燃,如果在調(diào)用父類的可失敗構造器 init?(name:) 時傳入的是空字符串,那么強制解包操作會引發(fā)運行時錯誤拘领。不過意乓,因為這里是通過字符串常量來調(diào)用它,構造器不會失敗约素,所以并不會發(fā)生運行時錯誤届良。

init! 可失敗構造器

通常來說我們通過在 init 關鍵字后添加問號的方式(init?)來定義一個可失敗構造器,但你也可以通過在 init 后面添加感嘆號的方式來定義一個可失敗構造器(init!)圣猎,該可失敗構造器將會構建一個對應類型的隱式解包可選類型的對象士葫。

你可以在 init? 中代理到 init!,反之亦然送悔。你也可以用 init? 重寫 init!慢显,反之亦然。你還可以用 init 代理到 init!欠啤,不過荚藻,一旦 init! 構造失敗,則會觸發(fā)一個斷言洁段。

必要構造器

在類的構造器前添加 required 修飾符表明所有該類的子類都必須實現(xiàn)該構造器:

class SomeClass {
    required init() {
        // 構造器的實現(xiàn)代碼
    }
}

在子類重寫父類的必要構造器時应狱,必須在子類的構造器前也添加 required 修飾符,表明該構造器要求也應用于繼承鏈后面的子類祠丝。在重寫父類中必要的指定構造器時疾呻,不需要添加 override 修飾符:

class SomeSubclass: SomeClass {
    required init() {
        // 構造器的實現(xiàn)代碼
    }
}

注意

如果子類繼承的構造器能滿足必要構造器的要求除嘹,則無須在子類中顯式提供必要構造器的實現(xiàn)。

通過閉包或函數(shù)設置屬性的默認值

如果某個存儲型屬性的默認值需要一些自定義或設置岸蜗,你可以使用閉包或全局函數(shù)為其提供定制的默認值憾赁。每當某個屬性所在類型的新實例被構造時,對應的閉包或函數(shù)會被調(diào)用散吵,而它們的返回值會當做默認值賦值給這個屬性龙考。

這種類型的閉包或函數(shù)通常會創(chuàng)建一個跟屬性類型相同的臨時變量,然后修改它的值以滿足預期的初始狀態(tài)矾睦,最后返回這個臨時變量晦款,作為屬性的默認值。

下面模板介紹了如何用閉包為屬性提供默認值:

class SomeClass {
    let someProperty: SomeType = {
        // 在這個閉包中給 someProperty 創(chuàng)建一個默認值
        // someValue 必須和 SomeType 類型相同
        return someValue
    }()
}

注意閉包結尾的花括號后面接了一對空的小括號枚冗。這用來告訴 Swift 立即執(zhí)行此閉包缓溅。如果你忽略了這對括號,相當于將閉包本身作為值賦值給了屬性赁温,而不是將閉包的返回值賦值給屬性坛怪。

注意

如果你使用閉包來初始化屬性,請記住在閉包執(zhí)行時股囊,實例的其它部分都還沒有初始化袜匿。這意味著你不能在閉包里訪問其它屬性,即使這些屬性有默認值稚疹。同樣居灯,你也不能使用隱式的 self 屬性,或者調(diào)用任何實例方法内狗。

下面例子中定義了一個結構體 Chessboard怪嫌,它構建了西洋跳棋游戲的棋盤,西洋跳棋游戲在一副黑白格交替的 8 x 8 的棋盤中進行的:

西洋跳棋棋盤

為了呈現(xiàn)這副游戲棋盤柳沙,Chessboard 結構體定義了一個屬性 boardColors岩灭,它是一個包含 64Bool 值的數(shù)組。在數(shù)組中赂鲤,值為 true 的元素表示一個黑格噪径,值為 false 的元素表示一個白格。數(shù)組中第一個元素代表棋盤上左上角的格子蛤袒,最后一個元素代表棋盤上右下角的格子熄云。

boardColors 數(shù)組是通過一個閉包來初始化并設置顏色值的:

struct Chessboard {
    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]
    }
}

每當一個新的 Chessboard 實例被創(chuàng)建時膨更,賦值閉包則會被執(zhí)行妙真,boardColors 的默認值會被計算出來并返回。上面例子中描述的閉包將計算出棋盤中每個格子對應的顏色荚守,并將這些值保存到一個臨時數(shù)組 temporaryBoard 中珍德,最后在構建完成時將此數(shù)組作為閉包返回值返回练般。這個返回的數(shù)組會保存到 boardColors 中,并可以通過工具函數(shù) squareIsBlackAtRow 來查詢:

let board = Chessboard()
print(board.squareIsBlackAt(row: 0, column: 1))
// 打印“true”
print(board.squareIsBlackAt(row: 7, column: 7))
// 打印“false”

繼續(xù)閱讀 Swift - 析構過程

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末锈候,一起剝皮案震驚了整個濱河市薄料,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌泵琳,老刑警劉巖摄职,帶你破解...
    沈念sama閱讀 219,427評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異获列,居然都是意外死亡谷市,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,551評論 3 395
  • 文/潘曉璐 我一進店門击孩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來迫悠,“玉大人,你說我怎么就攤上這事巩梢〈葱梗” “怎么了?”我有些...
    開封第一講書人閱讀 165,747評論 0 356
  • 文/不壞的土叔 我叫張陵括蝠,是天一觀的道長鞠抑。 經(jīng)常有香客問我,道長忌警,這世上最難降的妖魔是什么碍拆? 我笑而不...
    開封第一講書人閱讀 58,939評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮慨蓝,結果婚禮上感混,老公的妹妹穿的比我還像新娘。我一直安慰自己礼烈,他們只是感情好弧满,可當我...
    茶點故事閱讀 67,955評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著此熬,像睡著了一般庭呜。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上犀忱,一...
    開封第一講書人閱讀 51,737評論 1 305
  • 那天募谎,我揣著相機與錄音,去河邊找鬼阴汇。 笑死数冬,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的搀庶。 我是一名探鬼主播拐纱,決...
    沈念sama閱讀 40,448評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼铜异,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了秸架?” 一聲冷哼從身側響起揍庄,我...
    開封第一講書人閱讀 39,352評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎东抹,沒想到半個月后蚂子,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,834評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡缭黔,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,992評論 3 338
  • 正文 我和宋清朗相戀三年缆镣,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片试浙。...
    茶點故事閱讀 40,133評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡董瞻,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出田巴,到底是詐尸還是另有隱情钠糊,我是刑警寧澤,帶...
    沈念sama閱讀 35,815評論 5 346
  • 正文 年R本政府宣布壹哺,位于F島的核電站抄伍,受9級特大地震影響,放射性物質發(fā)生泄漏管宵。R本人自食惡果不足惜截珍,卻給世界環(huán)境...
    茶點故事閱讀 41,477評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望箩朴。 院中可真熱鬧岗喉,春花似錦、人聲如沸炸庞。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,022評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽埠居。三九已至查牌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間滥壕,已是汗流浹背纸颜。 一陣腳步聲響...
    開封第一講書人閱讀 33,147評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留绎橘,地道東北人胁孙。 一個月前我還...
    沈念sama閱讀 48,398評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親浊洞。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,077評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 中文文檔 一胡岔、存儲屬性的初始賦值 類和結構體在創(chuàng)建實例時法希,必須為所有存儲型屬性設置合適的初始值。存儲型屬性的值不能...
    伯wen閱讀 186評論 0 1
  • 構造過程是使用類靶瘸、結構體或枚舉類型的實例之前的準備過程苫亦。在新實例可用前必須執(zhí)行這個過程,具體操作包括設置實例中每個...
    CDLOG閱讀 339評論 0 1
  • 過定義構造器來實現(xiàn)構造過程怨咪,它就像用來創(chuàng)建特定類型新實例的特殊方法屋剑。 Swift 的構造器沒有返回值。它們的主要任...
    DevXue閱讀 126評論 0 0
  • 構造過程是為了使用某個類诗眨、結構體或枚舉類型的實例而進行的準備過程唉匾。這個過程包含了為實例中的每個屬性設置初始值和為其...
    零度_不結冰閱讀 252評論 0 0
  • ?構造過程是使用類、結構體或枚舉類型一個實例的準備過程匠楚。在新實例可用前必須執(zhí)行這個過程巍膘,具體操作包括設置實例中每個...
    EndEvent閱讀 632評論 0 3