本頁包含內(nèi)容:
[TOC]
構(gòu)造過程是使用類、結(jié)構(gòu)體或枚舉類型的實例之前的準備過程。在新實例可用前必須執(zhí)行這個過程,具體操作包含設(shè)置實例中每個存儲型屬性的初始值和執(zhí)行其他必須的設(shè)置或初始化工作。
通過定義構(gòu)造器來實現(xiàn)構(gòu)造過程,就像用來創(chuàng)建特定類型新實例的特殊方法。與Objective-C中的構(gòu)造器不同,Swift的構(gòu)造器無需返回值,它們的主要任務(wù)是保證新實例在第一次使用前完成正確的初始化。
類的實例也可以通過定義析構(gòu)器在實例釋放之前執(zhí)行特定的清除工作。
存儲屬性的初始賦值
類和結(jié)構(gòu)體在創(chuàng)建實例時,必須為所有存儲型屬性設(shè)置合適的初始值。存儲型屬性的值不能處于一個未知狀態(tài)。
你可以在構(gòu)造器中為存儲型屬性賦初值,也可以在定義屬性時為其設(shè)置默認值。
注意:當你為存儲型屬性設(shè)置默認值或者在構(gòu)造器中為其賦值時,它們的值是被直接設(shè)置的,不會觸發(fā)任何屬性觀察器。
構(gòu)造器
構(gòu)造器在創(chuàng)建某個特定類型的新實例時被調(diào)用。它的最簡形式類似于一個不帶任何參數(shù)的實例方法,以關(guān)鍵字init命名:
init() {
// 在此處執(zhí)行構(gòu)造過程
}
下面例子中定義了一個用來保存華氏溫度的結(jié)構(gòu)體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"
這個結(jié)構(gòu)體定義了一個不帶參數(shù)的構(gòu)造器init,并在里面講存儲型屬性temperature的值初始化為32.0(華氏溫度下水的冰點)。
默認屬性值
如前所述,你可以在構(gòu)造器中為存儲型屬性設(shè)置初始值。同樣,你也可以在屬性聲明時為其設(shè)置默認值。
注意:如果一個屬性總是使用相同的初始值,那么為其設(shè)置一個默認值比每次都在構(gòu)造器中賦值要好。兩種方法的效果是一樣的,只不過使用默認值讓屬性的初始化和聲明結(jié)合得更緊密。使用默認值能讓你的構(gòu)造器更簡潔。更清晰,且能通過默認值自動推導(dǎo)出屬性的類型;同時,它也能讓你充分利用默認構(gòu)造器、構(gòu)造器繼承等特性,后續(xù)章節(jié)將講到。
你可以使用更簡短的方式在定義結(jié)構(gòu)體Fahrenheit時為屬性temperature設(shè)置默認值:
struct Fahrenheit {
var temperature = 32.0
}
自定義構(gòu)造過程
你可以通過輸入?yún)?shù)和可選類型的屬性來自定義構(gòu)造過程,也可以在構(gòu)造過程中給常量屬性賦初值。
構(gòu)造參數(shù)
自定義構(gòu)造過程時,可以在定義中提供構(gòu)造參數(shù),指定參數(shù)值的類型和名字。構(gòu)造參數(shù)的功能和語法跟函數(shù)的方法的參數(shù)相同。
下面例子中定義了一個包含攝氏溫度的結(jié)構(gòu)體Celsius。它定義了兩個不同的構(gòu)造器:init(formFahrenheit:)和init(formKelvin:),二者分別通過接受不同溫標下的溫度來創(chuàng)建新的實例:
struct Celsius {
var temperatureInCelsius: Double
init(formFahrenheit 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òu)造器擁有一個構(gòu)造參數(shù),其外部名字為fromFahrenheit,內(nèi)部名字為fahrenheit;第二個構(gòu)造器也擁有一個構(gòu)造參數(shù),其外部名字為fromKelvin,內(nèi)部名字為kelvin。這兩個構(gòu)造器都將唯一的參數(shù)值轉(zhuǎn)換成攝氏溫度值,并保存在屬性 temperatureInCelsius 中。
參數(shù)的內(nèi)部名稱和外部名稱
跟函數(shù)和方法參數(shù)相同,構(gòu)造參數(shù)也擁有一個在構(gòu)造器內(nèi)部內(nèi)部使用的參數(shù)名字和一個在調(diào)用時使用的外部參數(shù)名字。
然而,構(gòu)造器并不像函數(shù)和方法那樣在括號前有一個可辨別的名字。因此在調(diào)用構(gòu)造器時,主要通過構(gòu)造器中的參數(shù)名和類型來確定應(yīng)該被調(diào)用的構(gòu)造器。正因為參數(shù)如此重要,如果你在定義構(gòu)造器時沒有提供參數(shù)的外部名字,Swift會為構(gòu)造器的每個參數(shù)自動生成一個跟內(nèi)部名字相同的外部名。
以下例子中定義了一個結(jié)構(gòu)體 Color,它包含了三個常量:red、green 和 blue。這些屬性可以存儲 0.0 到 1.0之間的值,用來指示顏色中紅、綠、藍成分的含量。
Color 提供了一個構(gòu)造器,其中包含三個Double類型的構(gòu)造參數(shù)。Color 也提供了第二個構(gòu)造器,它只包含名為white 的 Double 類型的參數(shù),它被用于給上述三個構(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)建一個新的Color實例:
let magenta = Color(red: 1.0, green: 0.0, blue: 1.0)
let halfGray = Color(white: 0.5)
注意,如果不通過外部參數(shù)名字傳值,你是沒法調(diào)用這個構(gòu)造器的。只要構(gòu)造器定義了某個外部參數(shù)名,你就必須使用它,忽略它將導(dǎo)致編譯錯誤:
let veryGreen = Color(0.0, 1.0, 0.0)
// 報編譯時錯誤,需要外部名稱
不帶外部名的構(gòu)造器參數(shù)
如果你不希望為構(gòu)造器的某個參數(shù)提供外部名字,你可以使用下劃線(_)來顯式描述它的外部名,以此重寫上面所說的默認行為。
下面是之前 Celsius 例子的擴展,跟之前相比添加了一個帶有 Double 類型參數(shù)的構(gòu)造器,其外部名用 _ 代替:
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) 意圖明確,不需要外部參數(shù)名稱。因此適合使用 init(_ celsius: Double) 這樣的構(gòu)造器,從而可以通過提供 Double 類型的參數(shù)值調(diào)用構(gòu)造器,而不需要加上外部名。
可選屬性類型
如果你定制的類型包含一個邏輯上允許取值為空的存儲型屬性 - - 無論是因為它無法再初始化時賦值,還是因為它在之后某個時間點可以賦值為空 - - 你都需要將它定義為可選類型。可選類型的屬性將自動初始化為nil,表示這個屬性是有意在初始化時設(shè)置為空的。
下面的例子中定義了類SurveyQuestion,它包含一個可選字符串屬性response:
class SurveQuestion {
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? 類型,或者說是可選字符串類型。當 SurveyQuestion 實例化時,它將自動賦值為nil,表明此字符串暫時還沒有值。
構(gòu)造過程中常量屬性的賦值
你可以在構(gòu)造過程中的任意時間點給常量屬性指定一個值,只要在構(gòu)造過程結(jié)束時是一個確定的值。一旦常量屬性被賦值,它將永遠不可更改。
注意:對于類的實例來說,它的常量屬性只能在定義它的類的構(gòu)造過程中修改;不能再子類中修改。
你可以修改上面的SurveQuestion示例,用常量屬性替代變量屬性text,表示問題內(nèi)容在SurveQuestion的實例被創(chuàng)建之后不會再被修改。盡管text屬性現(xiàn)在是常量,我們?nèi)匀豢梢栽陬惖臉?gòu)造器中設(shè)置它的值:
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.)"
默認構(gòu)造器
如果結(jié)構(gòu)體或類的所有屬性都有默認值,同時沒有自定義的構(gòu)造器,那么Swift會給這些結(jié)構(gòu)體或類提供一個默認構(gòu)造器。這個構(gòu)造器將簡單地創(chuàng)建一個所有屬性值都設(shè)置為默認值的實例。
下面的例子中創(chuàng)建了一個類ShoppingListItem,它封裝了購物清單中的某一物品的屬性:名字(name)、數(shù)量(quantity)和購買狀態(tài):
class ShoppingListItem {
var name: String?
var quantity = 1
var purchased = false
}
var item = ShoppingListItem()
由于shoppingListItem類中的所有屬性都有默認值,且它是沒有父類的基類,它將自動獲得一個可以為所有屬性設(shè)置默認值的默認構(gòu)造器。
上面例子中使用默認構(gòu)造器創(chuàng)造了一個 ShoppingListItem 類的實例(使用 ShoppingListItem() 形式的構(gòu)造器語法),并將其賦值給變量 item。
結(jié)構(gòu)體的逐一成員構(gòu)造器
除了上面提到的默認構(gòu)造器,如果結(jié)構(gòu)體沒有提供自定義的構(gòu)造器,它們將自動獲得一個逐一成員構(gòu)造器,即使結(jié)構(gòu)體的存儲型屬性沒有默認值。
逐一成員構(gòu)造器是用來初始化結(jié)構(gòu)體新實例里成員屬性的快捷方法。我們在調(diào)用逐一成員構(gòu)造器時,通過與成員屬性名相同的參數(shù)名進行傳值來完成對成員屬性的初始賦值。
下面的例子中,定義了一個結(jié)構(gòu)體Size,它包含兩個屬性width和height。Swift可以根據(jù)這兩個屬性的初始賦值為0.0自動推導(dǎo)出它們的類型為Double。
結(jié)構(gòu)體Size自動獲得了一個逐一成員構(gòu)造器init(width:height:)。你可以用它來創(chuàng)建新的Size實例:
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)造器來完成實例的部分構(gòu)造過程。這一過程稱為構(gòu)造器代理,它能避免多個構(gòu)造器間的代碼重復(fù)。
構(gòu)造器代理的實現(xiàn)規(guī)則和形式在值類型和類類型中有所不同。值類型不支持繼承,所以構(gòu)造器代碼的過程相對簡單,因為它們只能代理給自己的其他構(gòu)造器。類則不同,它可以繼承自其它類,這意味著類有責任保證其所有繼承的存儲型屬性在構(gòu)造時也能正確的初始化。
對于值類型,你可以使用self.init在自定義的構(gòu)造器中引用相同類型中的其它構(gòu)造器。并且你只能在構(gòu)造器內(nèi)部調(diào)用self.init。
請注意:如果你為某個值類型定義了一個自定義的構(gòu)造器,你將無法訪問到默認構(gòu)造器(如果是結(jié)構(gòu)體,還將無法訪問逐一成員構(gòu)造器)。這種限制可以防止你為值類型增加一個額外的且十分復(fù)雜的構(gòu)造器之后,仍然有人錯誤的使用自動生成的構(gòu)造器。
注意:假如你希望默認構(gòu)造器、逐一成員構(gòu)造器以及你自己的自定義構(gòu)造器都能用來創(chuàng)建實例,可以將自定義的構(gòu)造器寫到擴展(
extension)中,而不是寫在值類型的原始定義中。
下面例子將定義一個結(jié)構(gòu)體 Rect,用來代表幾何矩形。這個例子需要兩個輔助的結(jié)構(gòu)體 Size 和 Point,它們各自為其所有的屬性提供了默認初始值 0.0。
struct Size {
var width = 0.0, height = 0.0
}
struct Point {
var x = 0.0, y = 0.0
}
你可以通過下面三種方式為Rect創(chuàng)建實例:
- 使用含有默認值的
origin和size屬性來初始化; - 提供指定的
origin和size實例來初始化; - 提供指定的
center和size來初始化;
在下面的Rect結(jié)構(gòu)體定義中,我們?yōu)檫@三種方式提供了三個自定義的構(gòu)造器:
struct Rect {
var origin = Ponit()
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.0)
let originY = center.y - (size.height / 2.0)
self.init(origin: Point(x: originX, y: originY), size: size)
}
}
第一個Rect構(gòu)造器init(),在功能上跟沒有自定義構(gòu)造器時自動獲得的默認構(gòu)造器是一樣的。這個構(gòu)造器是一個空函數(shù),使用一對大括號{}來表示。調(diào)用這個構(gòu)造器將返回一個Rect實例,它的origin和size屬性都使用定義時的默認值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構(gòu)造器init(origin:size:),在功能上跟結(jié)構(gòu)體在沒有自定義構(gòu)造器是獲得的逐一成員構(gòu)造器是一樣的。這個構(gòu)造器只是簡單地將origin和size的參數(shù)值賦給對應(yīng)的存儲型屬性:
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 構(gòu)造器 init(center:size:) 稍微復(fù)雜一點。它先通過 center 和 size 的值計算出 origin 的坐標,然后再調(diào)用(或者說代理給)init(origin:size:) 構(gòu)造器來將新的 origin 和 size 值賦值到對應(yīng)的屬性中:
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)
構(gòu)造器init(center:size:)可以直接將origin和size的新值賦值給對應(yīng)的屬性中。然而,構(gòu)造器init(center:size:)通過使用提供了相關(guān)功能的現(xiàn)有構(gòu)造函數(shù)將會更加便捷。