- 過定義構(gòu)造器來實現(xiàn)構(gòu)造過程,它就像用來創(chuàng)建特定類型新實例的特殊方法燕雁。
- Swift 的構(gòu)造器沒有返回值诞丽。它們的主要任務(wù)是保證某種類型的新實例在第一次使用前完成正確的初始化鲸拥。
1. 存儲屬性的初始賦值
類和結(jié)構(gòu)體在創(chuàng)建實例時拐格,必須為所有存儲型屬性設(shè)置合適的初始值僧免。
-
存儲型屬性的值不能處于一個未知的狀態(tài)。
注意:當(dāng)你為存儲型屬性分配默認值或者在構(gòu)造器中為設(shè)置初始值時捏浊,它們的值是被直接設(shè)置的懂衩,不會觸發(fā)任何屬性觀察者。
1.1 構(gòu)造器
- 構(gòu)造器在創(chuàng)建某個特定類型的新實例時被調(diào)用金踪。它的最簡形式類似于一個不帶任何形參的實例方法浊洞,以關(guān)鍵字 init 命名:
init() {
// 在此處執(zhí)行構(gòu)造過程
}
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)體定義了一個不帶形參的構(gòu)造器 init,并在里面將存儲型屬性 temperature 的值初始化為 32.0(華氏溫度下水的冰點)胡岔。
1.2 默認屬性值
- 可以在屬性聲明時為其設(shè)置默認值法希。
- 可以通過在屬性聲明時為 temperature 提供默認值來使用更簡單的方式定義結(jié)構(gòu)體 Fahrenheit :
struct Fahrenheit {
var temperature = 32.0
}
注意
1. 如果一個屬性總是使用相同的初始值,那么為其設(shè)置一個默認值比每次都在構(gòu)造器中賦值要好靶瘸。
2. 兩種方法的最終結(jié)果是一樣的苫亦,只不過使用默認值讓屬性的初始化和聲明結(jié)合得更緊密。它能讓你的構(gòu)造器更簡潔怨咪、更清晰屋剑,且能通過默認值自動推導(dǎo)出屬性的類型;同時诗眨,它也能讓你充分利用默認構(gòu)造器唉匾、構(gòu)造器繼承等特性,后續(xù)章節(jié)將講到匠楚。
2. 自定義構(gòu)造過程
- 可以通過輸入形參和可選屬性類型來自定義構(gòu)造過程巍膘。
- 也可以在構(gòu)造過程中分配常量屬性
2.1 形參的構(gòu)造過程
- 可以在定義中提供構(gòu)造形參,指定其值的類型和名字芋簿。構(gòu)造形參的功能和語法跟函數(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òu)造器擁有一個構(gòu)造形參,其實參標簽為 fromFahrenheit益咬,形參命名為 fahrenheit逮诲;
第二個構(gòu)造器也擁有一個構(gòu)造形參,其實參標簽為 fromKelvin幽告,形參命名為 kelvin梅鹦。
這兩個構(gòu)造器都將單一的實參轉(zhuǎn)換成攝氏溫度值,并保存在屬性 temperatureInCelsius 中冗锁。
2.2 形參命名和實參標簽
- 構(gòu)造形參可以同時使用在構(gòu)造器里使用的形參命名和一個外部調(diào)用構(gòu)造器時使用的實參標簽齐唆。
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) // 報編譯期錯誤-需要實參標簽
注意,如果不通過實參標簽傳值冻河,這個構(gòu)造器是沒法調(diào)用的箍邮。如果構(gòu)造器定義了某個實參標簽茉帅,就必須使用它,忽略它將導(dǎo)致編譯期錯誤锭弊。
2.3 不帶實參標簽的構(gòu)造器形參
- 如果你不希望構(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
構(gòu)造器調(diào)用 Celsius(37.0) 意圖明確味滞,不需要實參標簽樱蛤。因此適合使用 init(_ celsius: Double) 這樣的構(gòu)造器,從而可以通過提供未命名的 Double 值來調(diào)用構(gòu)造器剑鞍。
2.4 可選屬性類型
- 如果你自定義的類型有一個邏輯上允許值為空的存儲型屬性——無論是因為它無法在初始化時賦值昨凡,還是因為它在之后某個時機可以賦值為空——都需要將它聲明為 可選類型。
- 可選類型的屬性將自動初始化為 nil蚁署,表示這個屬性是特意在構(gòu)造過程設(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? 類型光戈,或者說是 “可選類型 String“哪痰。
當(dāng) SurveyQuestion 的實例初始化時,它將自動賦值為 nil田度,表明“暫時還沒有字符“妒御。
2.5 構(gòu)造過程中常量屬性的賦值
- 可以在構(gòu)造過程中的任意時間點給常量屬性賦值,只要在構(gòu)造過程結(jié)束時它設(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)造過程中修改惋啃;
不能在子類中修改。
3. 默認構(gòu)造器
- 如果結(jié)構(gòu)體或類為所有屬性提供了默認值监右,又沒有提供任何自定義的構(gòu)造器边灭,那么 Swift 會給這些結(jié)構(gòu)體或類提供一個默認構(gòu)造器。
- 這個默認構(gòu)造器將簡單地創(chuàng)建一個所有屬性值都設(shè)置為它們默認值的實例健盒。
class ShoppingListItem {
var name: String?
var quantity = 1
var purchased = false
}
var item = ShoppingListItem()
上面例子中使用默認構(gòu)造器創(chuàng)造了一個 ShoppingListItem 類的實例(使用 ShoppingListItem() 形式的構(gòu)造器語法)绒瘦,并將其賦值給變量 item。(由于 name 屬性是可選 String 類型扣癣,它將接收一個默認 nil 的默認值惰帽,盡管代碼中沒有寫出這個值)
3.1 結(jié)構(gòu)體的逐一成員構(gòu)造器
- 結(jié)構(gòu)體如果沒有定義任何自定義構(gòu)造器,它們將自動獲得一個逐一成員構(gòu)造器(memberwise initializer)父虑。
- 不像默認構(gòu)造器该酗,即使存儲型屬性沒有默認值,結(jié)構(gòu)體也能會獲得逐一成員構(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)造器代理
- 通過調(diào)用其它構(gòu)造器來完成實例的部分構(gòu)造過程呜魄。這一過程稱為構(gòu)造器代理悔叽。
- 它能避免多個構(gòu)造器間的代碼重復(fù)。
- 值類型(結(jié)構(gòu)體和枚舉類型)不支持繼承爵嗅,所以構(gòu)造器代理的過程相對簡單娇澎,因為它們只能代理給自己的其它構(gòu)造器。
- 類則不同操骡,它可以繼承自其它類九火。這意味著類有責(zé)任保證其所有繼承的存儲型屬性在構(gòu)造時也能正確的初始化赚窃。
- 對于值類型册招,你可以使用 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)
- 第一個 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)昨忆。
- 第二個 Rect 構(gòu)造器 init(origin:size:)丁频,在功能上跟結(jié)構(gòu)體在沒有自定義構(gòu)造器時獲得的逐一成員構(gòu)造器是一樣的。這個構(gòu)造器只是簡單地將 origin 和 size 的實參值賦給對應(yīng)的存儲型屬性邑贴。
- 第三個 Rect 構(gòu)造器 init(center:size:) 稍微復(fù)雜一點席里。它先通過 center 和 size 的值計算出 origin 的坐標,然后再調(diào)用(或者說代理給)init(origin:size:) 構(gòu)造器來將新的 origin 和 size 值賦值到對應(yīng)的屬性中拢驾,構(gòu)造器 init(center:size:) 可以直接將 origin 和 size 的新值賦值到對應(yīng)的屬性中奖磁。然而,構(gòu)造器 init(center:size:) 通過使用提供了相關(guān)功能的現(xiàn)有構(gòu)造器將會更加便捷(而且意圖更清晰)繁疤。
5. 類的繼承和構(gòu)造過程
- 類里面的所有存儲型屬性——包括所有繼承自父類的屬性——都必須在構(gòu)造過程中設(shè)置初始值咖为。
- Swift 為類類型提供了兩種構(gòu)造器來確保實例中所有存儲型屬性都能獲得初始值:指定構(gòu)造器和便利構(gòu)造器。
5.1 指定構(gòu)造器和便利構(gòu)造器
- 指定構(gòu)造器是類中最主要的構(gòu)造器稠腊。一個指定構(gòu)造器將初始化類中提供的所有屬性躁染,并調(diào)用合適的父類構(gòu)造器讓構(gòu)造過程沿著父類鏈繼續(xù)往上進行。
- 每一個類都必須至少擁有一個指定構(gòu)造器麻养。在某些情況下褐啡,許多類通過繼承了父類中的指定構(gòu)造器而滿足了這個條件。
- 便利構(gòu)造器是類中比較次要的鳖昌、輔助型的構(gòu)造器备畦。你可以定義便利構(gòu)造器來調(diào)用同一個類中的指定構(gòu)造器低飒,并為部分形參提供默認值。你也可以定義便利構(gòu)造器來創(chuàng)建一個特殊用途或特定輸入值的實例懂盐。
5.2 指定構(gòu)造器和便利構(gòu)造器的語法
- 類的指定構(gòu)造器的寫法跟值類型簡單構(gòu)造器一樣:
init(parameters) {
statements
}
- 便利構(gòu)造器也采用相同樣式的寫法褥赊,但需要在 init 關(guān)鍵字之前放置 convenience 關(guān)鍵字,并使用空格將它們倆分開:
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òu)造器必須總是向上代理
- 便利構(gòu)造器必須總是橫向代理
5.4 兩段式構(gòu)造過程
Swift 中類的構(gòu)造過程包含兩個階段俐银。
- 第一個階段尿背,類中的每個存儲型屬性賦一個初始值。
- 當(dāng)每個存儲型屬性的初始值被賦值后捶惜,第二階段開始田藐,它給每個類一次機會,在新實例準備使用之前進一步自定義它們的存儲型屬性吱七。
Swift 編譯器將執(zhí)行 4 種有效的安全檢查汽久,以確保兩段式構(gòu)造過程不出錯地完成:
-
安全檢查 1: 指定構(gòu)造器必須保證它所在類的所有屬性都必須先初始化完成,之后才能將其它構(gòu)造任務(wù)向上代理給父類中的構(gòu)造器踊餐。
如上所述景醇,一個對象的內(nèi)存只有在其所有存儲型屬性確定之后才能完全初始化。為了滿足這一規(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)用任何實例方法蜓肆,不能讀取任何實例屬性的值颜凯,不能引用 self 作為一個值。
類的實例在第一階段結(jié)束以前并不是完全有效的仗扬。只有第一階段完成后症概,類的實例才是有效的,才能訪問屬性和調(diào)用方法早芭。
階段 1
- 類的某個指定構(gòu)造器或便利構(gòu)造器被調(diào)用彼城。
- 完成類的新實例內(nèi)存的分配,但此時內(nèi)存還沒有被初始化。
- 指定構(gòu)造器確保其所在類引入的所有存儲型屬性都已賦初值募壕。存儲型屬性所屬的內(nèi)存完成初始化调炬。
- 指定構(gòu)造器切換到父類的構(gòu)造器,對其存儲屬性完成相同的任務(wù)舱馅。
- 這個過程沿著類的繼承鏈一直往上執(zhí)行缰泡,直到到達繼承鏈的最頂部。
- 當(dāng)?shù)竭_了繼承鏈最頂部代嗤,而且繼承鏈的最后一個類已確保所有的存儲型屬性都已經(jīng)賦值棘钞,這個實例的內(nèi)存被認為已經(jīng)完全初始化。此時階段 1 完成干毅。
階段 2
- 從繼承鏈頂部往下宜猜,繼承鏈中每個類的指定構(gòu)造器都有機會進一步自定義實例。構(gòu)造器此時可以訪問 self溶锭、修改它的屬性并調(diào)用實例方法等等宝恶。
- 最終符隙,繼承鏈中任意的便利構(gòu)造器有機會自定義實例和使用 self趴捅。
5.5 構(gòu)造器的繼承和重寫
Swift 中的子類默認情況下不會繼承父類的構(gòu)造器。
-
Swift 的這種機制可以防止一個父類的簡單構(gòu)造器被一個更精細的子類繼承霹疫,而在用來創(chuàng)建子類時的新實例時沒有完全或錯誤被初始化拱绑。
注意:父類的構(gòu)造器僅會在安全和適當(dāng)?shù)哪承┣闆r下被繼承。丽蝎、
class Vehicle { // Vehicle 類只為存儲型屬性提供默認值猎拨,也沒有提供自定義構(gòu)造器。因此屠阻,它會自動獲得一個默認構(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òu)造器 init()红省。這個指定構(gòu)造器和父類的指定構(gòu)造器相匹配,所以 Bicycle 中這個版本的構(gòu)造器需要帶上 override 修飾符国觉。
override init() {
super.init() // 這個方法的作用是調(diào)用 Bicycle 的父類 Vehicle 的默認構(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)造過程修改繼承來的變量屬性痕寓,但是不能修改繼承來的常量屬性。
5.6 構(gòu)造器的自動繼承
- 規(guī)則 1 : 如果子類沒有定義任何指定構(gòu)造器蝇闭,它將自動繼承父類所有的指定構(gòu)造器呻率。
- 規(guī)則 2: 如果子類提供了所有父類指定構(gòu)造器的實現(xiàn)——無論是通過規(guī)則 1 繼承過來的,還是提供了自定義實現(xiàn)——它將自動繼承父類所有的便利構(gòu)造器呻引。
5.7 指定構(gòu)造器和便利構(gòu)造器實踐
class Food { // 一個簡單的用來封裝食物名字的類
var name: String
init(name: String) {
self.name = name
}
// RecipeIngredient 用來表示食譜中的一項原料
convenience init() { // 沒有參數(shù)的便利構(gòu)造器 init()礼仗,為新食物提供了一個默認的占位名字,通過橫向代理到指定構(gòu)造器 init(name: String) 并給參數(shù) name 賦值為 [Unnamed] 來實現(xiàn)
self.init(name: "[Unnamed]")
}
}
let namedMeat = Food(name: "Bacon") // 這個構(gòu)造器可以使用一個特定的名字來創(chuàng)建新的 Food 實例
// 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òu)建了購物單中出現(xiàn)的某一種食譜原料元践。
class ShoppingListItem: RecipeIngredient {
var purchased = false // 購買狀態(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)造器
有時挪丢,定義一個構(gòu)造器可失敗的類,結(jié)構(gòu)體或者枚舉是很有用的卢厂。
這里所指的“失敗” 指的是乾蓬,如給構(gòu)造器傳入無效的形參,或缺少某種所需的外部資源慎恒,又或是不滿足某種必要的條件等任内。
可以在一個類,結(jié)構(gòu)體或是枚舉類型的定義中融柬,添加一個或多個可失敗構(gòu)造器死嗦。其語法為在 init 關(guān)鍵字后面添加問號(init?)。
-
可失敗構(gòu)造器會創(chuàng)建一個類型為自身類型的可選類型的對象粒氧。你通過 return nil 語句來表明可失敗構(gòu)造器在何種情況下應(yīng)該 “失敗”越除。
注意: 1. 可失敗構(gòu)造器的參數(shù)名和參數(shù)類型,不能與其它非可失敗構(gòu)造器的參數(shù)名外盯,及其參數(shù)類型相同摘盆。 2. 嚴格來說,構(gòu)造器都不支持返回值饱苟。因為構(gòu)造器本身的作用孩擂,只是為了確保對象能被正確構(gòu)造。因此你只是用 return nil 表明可失敗構(gòu)造器構(gòu)造失敗箱熬,而不要用關(guān)鍵字 return 來表明構(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”
實現(xiàn)針對數(shù)字類型轉(zhuǎn)換的可失敗構(gòu)造器城须。確保數(shù)字類型之間的轉(zhuǎn)換能保持精確的值蚤认,使用這個 init(exactly:) 構(gòu)造器。如果類型轉(zhuǎn)換不能保持值不變糕伐,則這個構(gòu)造器構(gòu)造失敗砰琢。
struct Animal {
let species: String
init?(species: String) {
if species.isEmpty { // 這個可失敗構(gòu)造器檢查傳入的species 值是否為一個空字符串。
return nil // 如果為空字符串赤炒,則構(gòu)造失敗氯析。
}
self.species = species // 否則,species 屬性被賦值莺褒,構(gòu)造成功掩缓。
}
}
- 可以通過該可失敗構(gòu)造器來嘗試構(gòu)建一個 Animal 的實例,并檢查構(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)造器傳入一個空字符串到形參 species遵岩,則會導(dǎo)致構(gòu)造失斈憷薄:
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 的可選類型的字符串是兩個完全不同的概念。
2. 上例中的空字符串("")其實是一個有效的舍哄,非可選類型的字符串宴凉。
3. 這里我們之所以讓 Animal 的可失敗構(gòu)造器構(gòu)造失敗,只是因為對于 Animal 這個類的 species 屬性來說表悬,它更適合有一個具體的值弥锄,而不是空字符串。
6.1 枚舉類型的可失敗構(gòu)造器
- 可以通過一個帶一個或多個形參的可失敗構(gòu)造器來獲取枚舉類型中特定的枚舉成員蟆沫。
- 如果提供的形參無法匹配任何枚舉成員籽暇,則構(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)造器在三個枚舉成員中選擇合適的枚舉成員饭庞,當(dāng)形參不能和任何枚舉成員相匹配時戒悠,則構(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)造器
- 帶原始值的枚舉類型會自帶一個可失敗構(gòu)造器 init?(rawValue:)舟山。
- 該可失敗構(gòu)造器有一個合適的原始值類型的 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)造器。
-
無論是向上代理還是橫向代理拆座,如果你代理到的其他可失敗構(gòu)造器觸發(fā)構(gòu)造失敗,整個構(gòu)造過程將立即終止冠息,接下來的任何構(gòu)造代碼不會再被執(zhí)行挪凑。
注意:可失敗構(gòu)造器也可以代理到其它的不可失敗構(gòu)造器。通過這種方式逛艰,你可以增加一個可能的失敗狀態(tài)到現(xiàn)有的構(gòu)造過程中躏碳。
class Product {
let name: String
init?(name: String) {
if name.isEmpty { return nil }
self.name = name
}
}
class CartItem: Product { // 這個類建立了一個在線購物車中的物品的模型
let quantity: Int // 常量存儲型屬性,并確保該屬性的值至少為 1
init?(name: String, quantity: Int) {
if quantity < 1 { return nil } // CartItem 可失敗構(gòu)造器首先驗證接收的 quantity 值是否大于等于 1散怖, 倘若 quantity 值無效菇绵,則立即終止整個構(gòu)造過程,返回失敗結(jié)果镇眷,且不再執(zhí)行余下代碼咬最。
self.quantity = quantity
super.init(name: name)
}
}
// 傳入一個非空字符串 name 以及一個值大于等于 1 的 quantity 來創(chuàng)建一個 CartItem 實例
if let twoSocks = CartItem(name: "sock", quantity: 2) {
print("Item: \(twoSocks.name), quantity: \(twoSocks.quantity)")
} // 打印“Item: sock, quantity: 2”
// 以一個值為 0 的 quantity 來創(chuàng)建一個 CartItem 實例,那么將導(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”
// 傳入一個值為空字符串的 name 來創(chuàng)建一個 CartItem 實例欠动,那么將導(dǎo)致父類 Product 的構(gòu)造過程失斢牢凇:
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òu)造器
可以在子類中重寫父類的可失敗構(gòu)造器惑申。
或者你也可以用子類的非可失敗構(gòu)造器重寫一個父類的可失敗構(gòu)造器。
-
這使你可以定義一個不會構(gòu)造失敗的子類翅雏,即使父類的構(gòu)造器允許構(gòu)造失敗圈驼。
注意: 1. 當(dāng)你用子類的非可失敗構(gòu)造器重寫父類的可失敗構(gòu)造器時,向上代理到父類的可失敗構(gòu)造器的唯一方式是對父類的可失敗構(gòu)造器的返回值進行強制解包望几。 2. 你可以用非可失敗構(gòu)造器重寫可失敗構(gòu)造器绩脆,但反過來卻不行。
class Document {
var name: String?
// 該構(gòu)造器創(chuàng)建了一個 name 屬性的值為 nil 的 document 實例
init() {}
// 該構(gòu)造器創(chuàng)建了一個 name 屬性的值為非空字符串的 document 實例
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òu)造器 init(name:) 重寫了父類的可失敗構(gòu)造器 init?(name:)橄抹,子類用一個不可失敗構(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)造過程中使用了父類的可失敗構(gòu)造器 init?(name:)
super.init(name: "[Untitled]")!
}
- 如果在調(diào)用父類的可失敗構(gòu)造器 init?(name:) 時傳入的是空字符串害碾,那么強制解包操作會引發(fā)運行時錯誤矢劲。
- 不過,因為這里是通過字符串常量來調(diào)用它慌随,構(gòu)造器不會失敗芬沉,所以并不會發(fā)生運行時錯誤。
6.5 init! 可失敗構(gòu)造器
- init 關(guān)鍵字后添加問號的方式(init?)來定義一個可失敗構(gòu)造器阁猜。
- init 后面添加感嘆號的方式來定義一個可失敗構(gòu)造器(init!)丸逸,該可失敗構(gòu)造器將會構(gòu)建一個對應(yīng)類型的隱式解包可選類型的對象。
- 可以在 init? 中代理到 init!剃袍,反之亦然黄刚。
- 你也可以用 init? 重寫 init!,反之亦然民效。
- 你還可以用 init 代理到 init!憔维,不過,一旦 init! 構(gòu)造失敗畏邢,則會觸發(fā)一個斷言业扒。
7. 必要構(gòu)造器
- 在類的構(gòu)造器前添加 required 修飾符表明所有該類的子類都必須實現(xiàn)該構(gòu)造器:
class SomeClass {
required init() {
// 構(gòu)造器的實現(xiàn)代碼
}
}
- 在子類重寫父類的必要構(gòu)造器時,必須在子類的構(gòu)造器前也添加 required 修飾符舒萎,表明該構(gòu)造器要求也應(yīng)用于繼承鏈后面的子類程储。在重寫父類中必要的指定構(gòu)造器時,不需要添加 override 修飾符:
class SomeSubclass: SomeClass {
required init() {
// 構(gòu)造器的實現(xiàn)代碼
}
}
如果子類繼承的構(gòu)造器能滿足必要構(gòu)造器的要求臂寝,則無須在子類中顯式提供必要構(gòu)造器的實現(xiàn)章鲤。
8. 通過閉包或函數(shù)設(shè)置屬性的默認值
- 如果某個存儲型屬性的默認值需要一些自定義或設(shè)置,你可以使用閉包或全局函數(shù)為其提供定制的默認值咆贬。
- 每當(dāng)某個屬性所在類型的新實例被構(gòu)造時败徊,對應(yīng)的閉包或函數(shù)會被調(diào)用,而它們的返回值會當(dāng)做默認值賦值給這個屬性素征。
- 這種類型的閉包或函數(shù)通常會創(chuàng)建一個跟屬性類型相同的臨時變量集嵌,然后修改它的值以滿足預(yù)期的初始狀態(tài)萝挤,最后返回這個臨時變量,作為屬性的默認值根欧。
- 用閉包為屬性提供默認值:
class SomeClass {
let someProperty: SomeType = {
// 在這個閉包中給 someProperty 創(chuàng)建一個默認值
// someValue 必須和 SomeType 類型相同
return someValue
}()
}
- 注意閉包結(jié)尾的花括號后面接了一對空的小括號怜珍。
- 這用來告訴 Swift 立即執(zhí)行此閉包。
- 如果你忽略了這對括號凤粗,相當(dāng)于將閉包本身作為值賦值給了屬性酥泛,而不是將閉包的返回值賦值給屬性。
注意
1. 如果你使用閉包來初始化屬性嫌拣,請記住在閉包執(zhí)行時柔袁,實例的其它部分都還沒有初始化。
2. 這意味著你不能在閉包里訪問其它屬性异逐,即使這些屬性有默認值捶索。
3. 同樣,你也不能使用隱式的 self 屬性灰瞻,或者調(diào)用任何實例方法腥例。
struct Chessboard {
/*
boardColors:一個包含 64 個 Bool 值的數(shù)組
值為 true 的元素表示一個黑格
值為 false 的元素表示一個白格
數(shù)組中第一個元素代表棋盤上左上角的格子
最后一個元素代表棋盤上右下角的格子。
*/
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”