Swift枚舉,結(jié)構(gòu)體和類(lèi)的構(gòu)造器詳解

1.設(shè)置存儲(chǔ)屬性的初始值

在創(chuàng)建類(lèi)或結(jié)構(gòu)實(shí)例時(shí)茧痒,類(lèi)和結(jié)構(gòu)必須為所有存儲(chǔ)屬性設(shè)置初始值旺订。存儲(chǔ)屬性不能保留在不確定的狀態(tài)区拳。
可以在構(gòu)造器中設(shè)置存儲(chǔ)屬性的初始值,也可以在屬性聲明時(shí)設(shè)置默認(rèn)值笆凌。

注意:將默認(rèn)值分配給存儲(chǔ)屬性或在初始化程序中設(shè)置其初始值時(shí)乞而,將直接設(shè)置該屬性的值晦闰,而不調(diào)用任何屬性觀察者跪妥。

(1).構(gòu)造器
可以調(diào)用構(gòu)造器創(chuàng)建特定類(lèi)型的新實(shí)例眉撵。無(wú)參構(gòu)造器是一種最簡(jiǎn)單的初始化方法纽疟,使用init關(guān)鍵字:

init() {
    // perform some initialization here
}
struct Fahrenheit {
    var temperature: Double
    init() {
        temperature = 32.0
    }
}
var f = Fahrenheit()
print("The default temperature is \(f.temperature)° Fahrenheit")
// Prints "The default temperature is 32.0° Fahrenheit"

(2).默認(rèn)的屬性值
可以在構(gòu)造器中設(shè)置存儲(chǔ)屬性的初始值,如上所示蟆肆⊙坠Γ或者蛇损,將默認(rèn)屬性值指定為屬性聲明的一部分。通過(guò)在定義屬性時(shí)為其指定初始值更啄,可以指定默認(rèn)屬性值锈死。

struct Fahrenheit {
    var temperature = 32.0
}

2.自定義初始化

可以使用輸入?yún)?shù)和可選屬性類(lèi)型自定義初始化過(guò)程待牵,或者在初始化期間分配常量屬性,如以下部分所述贰拿。
(1).初始化參數(shù)
可以提供初始化參數(shù)作為構(gòu)造器定義的一部分膨更,來(lái)定義在自定義初始化過(guò)程中值的類(lèi)型和名稱(chēng)珍德。初始化參數(shù)與函數(shù)和方法參數(shù)具有相同的功能和語(yǔ)法锈候。

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 is 100.0
let freezingPointOfWater = Celsius(fromKelvin: 273.15)
// freezingPointOfWater.temperatureInCelsius is 0.0

(2).參數(shù)名稱(chēng)和參數(shù)標(biāo)簽
與函數(shù)和方法參數(shù)一樣,初始化參數(shù)既可以具有在構(gòu)造器體內(nèi)使用的參數(shù)名稱(chēng)获列,也可以具有在調(diào)用構(gòu)造器時(shí)使用的參數(shù)標(biāo)簽蛛倦。
構(gòu)造器參數(shù)的名稱(chēng)和類(lèi)型在確定調(diào)用哪個(gè)構(gòu)造器時(shí)起著特別重要的作用及皂。因此验烧,若沒(méi)有給構(gòu)造器中的每個(gè)參數(shù)提供參數(shù)標(biāo)簽若治,則Swift會(huì)給每個(gè)參數(shù)提供自動(dòng)參數(shù)標(biāo)簽端幼。

truct 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)
// this reports a compile-time error - argument labels are required

(3).無(wú)參數(shù)標(biāo)簽的初始化參數(shù)
如果不想為構(gòu)造器參數(shù)提供參數(shù)標(biāo)簽,則可以在參數(shù)名稱(chēng)添加下劃線_滑进。

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 is 37.0

(4).可選屬性類(lèi)型
可以在自定義的類(lèi)型中使用可選的存儲(chǔ)屬性阴汇■昙模可選類(lèi)型的屬性將自動(dòng)初始化為值nil,表示該屬性在初始化期間無(wú)值未斑。

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()
// Prints "Do you like cheese?"
cheeseQuestion.response = "Yes, I do like cheese."

(5).在初始化過(guò)程中分配常量屬性
只要在初始化完成時(shí)將其設(shè)置為確定值,就可以在初始化期間的任何時(shí)刻為常量屬性賦值芽突。一旦為常量屬性賦值寞蚌,就無(wú)法進(jìn)一步修改它。

注意:對(duì)于類(lèi)實(shí)例艘刚,只能在類(lèi)初始化期間修改常量屬性。而且常量屬性不能被子類(lèi)修改秋度。

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()
// Prints "How about beets?"
beetsQuestion.response = "I also like beets. (But not with cheese.)"

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

  • 類(lèi)的默認(rèn)構(gòu)造器
    當(dāng)一個(gè)類(lèi)的所有屬性都有默認(rèn)值,且沒(méi)有提供任何構(gòu)造器鲸拥,則系統(tǒng)提構(gòu)一個(gè)默認(rèn)的無(wú)參構(gòu)造器刑赶。
class ShoppingListItem {
    var name: String?
    var quantity = 1
    var purchased = false
}
var item = ShoppingListItem()
  • 結(jié)構(gòu)體的默認(rèn)構(gòu)造器
    如果結(jié)構(gòu)類(lèi)型沒(méi)有定義任何自己的構(gòu)造器金踪,則它們會(huì)自動(dòng)接收一個(gè)全能初始化器胡岔。與默認(rèn)構(gòu)造器不同,即使結(jié)構(gòu)體的存儲(chǔ)屬性沒(méi)有默認(rèn)值怨咪,該結(jié)構(gòu)體也會(huì)接收一個(gè)全能初始化器。
struct Size {
    var width = 0.0, height = 0.0
}
let twoByTwo = Size(width: 2.0, height: 2.0)

4.值類(lèi)型的構(gòu)造器委托

構(gòu)造器可以調(diào)用其他構(gòu)造器來(lái)執(zhí)行實(shí)例初始化的一部分匠楚。此過(guò)程稱(chēng)為構(gòu)造器委托典徘,可避免跨多個(gè)構(gòu)造器拷貝代碼逮诲。
對(duì)于值類(lèi)型和類(lèi)類(lèi)型裆甩,構(gòu)造器委托的工作原理和允許的委托形式的規(guī)則是不同的嗤栓。值類(lèi)型(結(jié)構(gòu)和枚舉)不支持繼承叨叙,因此它們的構(gòu)造器委托過(guò)程相對(duì)簡(jiǎn)單擂错,因?yàn)樗鼈冎荒芪薪o它們自己提供的另一個(gè)構(gòu)造器钮呀。然而,類(lèi)可以從其他類(lèi)繼承昨凡。這意味著類(lèi)有額外的職責(zé)爽醋,以確保在初始化期間為它們繼承的所有存儲(chǔ)屬性分配適當(dāng)?shù)闹怠?br> 如果為值類(lèi)型提供了一個(gè)自定義構(gòu)造器,則無(wú)法再訪問(wèn)該類(lèi)型的默認(rèn)構(gòu)造器便脊。

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's origin is (0.0, 0.0) and its size is (0.0, 0.0)

let centerRect = Rect(center: Point(x: 4.0, y: 4.0),
                      size: Size(width: 3.0, height: 3.0))
// centerRect's origin is (2.5, 2.5) and its size is (3.0, 3.0)

5.類(lèi)繼承和初始化

類(lèi)的所有存儲(chǔ)屬性(包括從父類(lèi)繼承的存儲(chǔ)屬性)都必須在初始化期間賦值子房。Swift為類(lèi)類(lèi)型定義了兩種構(gòu)造器就轧,以確保所有存儲(chǔ)屬性都能被初始化证杭。這兩種構(gòu)造器是指定構(gòu)造器便利構(gòu)造器

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

  • 指定構(gòu)造器是類(lèi)的主要構(gòu)造器妒御。它可以完全初始化該類(lèi)引入的所有屬性解愤,并調(diào)用適當(dāng)?shù)某?lèi)構(gòu)造器,以繼續(xù)超類(lèi)鏈上的初始化過(guò)程乎莉。
  • 每個(gè)類(lèi)必須至少有一個(gè)指定構(gòu)造器送讲。
  • 便利構(gòu)造器是次要的,支持類(lèi)的構(gòu)造器惋啃。

(2).指定構(gòu)造器和便利構(gòu)造器的語(yǔ)法格式

  • 指定構(gòu)造器
init(parameters) {
    statements
}
  • 便利構(gòu)造器
convenience init(parameters) {
    statements
}

(3).類(lèi)類(lèi)型的構(gòu)造器委托
為了簡(jiǎn)化指定構(gòu)造器和便利構(gòu)造器之間的關(guān)系哼鬓,為Swift對(duì)構(gòu)造器之間的委托調(diào)用應(yīng)用以下三個(gè)規(guī)則:

  • 規(guī)則1 :一個(gè)指定構(gòu)造器必須從其直接超類(lèi)中調(diào)用一個(gè)指定構(gòu)造器
  • 規(guī)則2:一個(gè)便利構(gòu)造器必須調(diào)用同一個(gè)類(lèi)中的另一個(gè)構(gòu)造器。
  • 規(guī)則3:便利構(gòu)造器最終必須調(diào)用指定構(gòu)造器边灭。

    簡(jiǎn)單來(lái)說(shuō):
  • 指定構(gòu)造器必須總是向上委托异希。
  • 便利構(gòu)造器必須總是橫向委托。

    規(guī)則如下圖所示:
    swift_00.png

下圖顯示了更復(fù)雜的類(lèi)層次結(jié)構(gòu)绒瘦。它說(shuō)明了這個(gè)層次結(jié)構(gòu)中指定構(gòu)造器如何作為類(lèi)初始化的“漏斗”點(diǎn)称簿,簡(jiǎn)化了鏈中的類(lèi)之間的相互關(guān)系:
swift_01.png

(4).兩階段初始化
在Swift中,類(lèi)的初始化分兩個(gè)階段:

  • 第一階段:每個(gè)存儲(chǔ)屬性都由引入它的類(lèi)分配一個(gè)初始值惰帽。
  • 第二階段:當(dāng)每個(gè)存儲(chǔ)屬性的初始化狀態(tài)都被確定之后憨降,開(kāi)始第二階段。在新實(shí)例被認(rèn)為可以使用之前该酗,每個(gè)類(lèi)都有機(jī)會(huì)進(jìn)一步自定義它的存儲(chǔ)屬性授药。

兩階段初始化可以防止每個(gè)屬性值在初始化之前被訪問(wèn);并能夠防止屬性值被另一個(gè)構(gòu)造器意外地設(shè)置為其他值。
Swift的編譯器執(zhí)行四個(gè)有用的安全檢查悔叽,以確保完成兩階段初始化而不會(huì)出現(xiàn)錯(cuò)誤:

  • 安全檢測(cè)一:指定構(gòu)造器必須確保其類(lèi)引入的所有屬性在向上委托給超類(lèi)構(gòu)造器之前都被初始化航邢。
    如上所述,只有當(dāng)對(duì)象的所有存儲(chǔ)屬性的初始狀態(tài)已知時(shí)骄蝇,才認(rèn)為對(duì)象的內(nèi)存是完全初始化的膳殷。為了滿足這一規(guī)則,指定構(gòu)造器必須確保在傳遞到繼承鏈上之前對(duì)其自身的所有屬性進(jìn)行初始化九火。
  • 安全檢測(cè)二:在給繼承屬性賦值之前赚窃,指定構(gòu)造器必須先調(diào)用超類(lèi)構(gòu)造器。若沒(méi)有岔激,指定構(gòu)造器賦的新值將被超類(lèi)覆蓋勒极,作為其初始化的一部分。
  • 安全檢測(cè)三:在為任何屬性(包括由同一個(gè)類(lèi)定義的屬性)賦值之前虑鼎,便利構(gòu)造器必須委托給另一個(gè)構(gòu)造器辱匿。如果沒(méi)有,那么便利構(gòu)造器賦的新值將被其類(lèi)的指定構(gòu)造器覆蓋炫彩。
  • 安全檢測(cè)四: 只有當(dāng)初始化的第一階段結(jié)束之后匾七, 構(gòu)造器才能調(diào)用實(shí)例方法,讀取實(shí)例屬性的值江兢,將self作為值引用昨忆。

基于上面的四個(gè)安全檢測(cè),以下是兩階段初始化的過(guò)程:

Phase 1

  • 在類(lèi)中調(diào)用指定構(gòu)造器杉允,或便利構(gòu)造器邑贴;
  • 給類(lèi)的新實(shí)例對(duì)象分配內(nèi)存。分配的內(nèi)存尚未初始化叔磷;
  • 該類(lèi)的指定構(gòu)造器確認(rèn)由該類(lèi)引入的所有存儲(chǔ)屬性都有一個(gè)值÷<荩現(xiàn)在分配給這些存儲(chǔ)屬性的內(nèi)存被初始化;
  • 指定構(gòu)造器交給超類(lèi)的構(gòu)造器改基,以對(duì)其自身的存儲(chǔ)屬性執(zhí)行相同的任務(wù)繁疤;
  • 這將沿著繼承鏈繼續(xù)向上,直到繼承鏈頂端寥裂;
  • 一旦到達(dá)繼承鏈頂端嵌洼,繼承鏈中的最后一個(gè)類(lèi)要確保它所有的存儲(chǔ)屬性都有值,則實(shí)例對(duì)象的內(nèi)存被認(rèn)為完全初始化封恰。第一階段完成
    Phase 1示例圖:
    swift_02.png
    上述例子:子類(lèi)通過(guò)調(diào)用便利構(gòu)造器開(kāi)始初始化。便利構(gòu)造器此時(shí)還不能修改任何屬性褐啡,它橫向調(diào)用同類(lèi)的指定構(gòu)造器诺舔。
    如安全檢測(cè)一所示,指定構(gòu)造器確保子類(lèi)中的所有屬性都有值。然后低飒,它調(diào)用父類(lèi)指定構(gòu)造器繼續(xù)繼承鏈上的初始化许昨。
    父類(lèi)構(gòu)造器確保父類(lèi)中的所有屬性都有值。沒(méi)有需要初始化的父類(lèi)褥赊,因此不需要進(jìn)一步的委托糕档。
    只要超類(lèi)的所有屬性都有一個(gè)初始值,它的內(nèi)存就被認(rèn)為是完全初始化的拌喉,階段1就完成了速那。
    Phase 2
  • 從繼承鏈的頂部向下執(zhí)行,鏈中的每個(gè)指定構(gòu)造器都可以進(jìn)一步選擇自定義實(shí)例尿背。構(gòu)造器現(xiàn)在可以訪問(wèn)self端仰,可以修改self的屬性,調(diào)用它的實(shí)例方法田藐,等等荔烧。
  • 最后,鏈中的任何便利構(gòu)造器都可以選擇自定義實(shí)例汽久,并可以使用self鹤竭。

    Phase 2示例圖:
    swift_03.png
    現(xiàn)在父類(lèi)的指定構(gòu)造器有機(jī)會(huì)進(jìn)一步自定義實(shí)例。(盡管它沒(méi)必要這么做)

    一旦父類(lèi)指定構(gòu)造器完成景醇,子類(lèi)的指定構(gòu)造器就可以執(zhí)行額外的自定義操作(盡管它也沒(méi)必這樣做)诺擅。
    最后,一旦子類(lèi)的指定構(gòu)造器完成啡直,最初調(diào)用的便利構(gòu)造器就可以執(zhí)行額外的自定義操作烁涌。

(5).構(gòu)造器的繼承和重寫(xiě)

  • 與Objective-C中的子類(lèi)不同,Swift的子類(lèi)默認(rèn)不繼承父類(lèi)構(gòu)造器酒觅。
class Vehicle {
    var numberOfWheels = 0
    var description: String {
        return "\(numberOfWheels) wheel(s)"
    }
}

let vehicle = Vehicle()
print("Vehicle: \(vehicle.description)")
// Vehicle: 0 wheel(s)
class Bicycle: Vehicle {
    override init() {
        super.init()
        numberOfWheels = 2
    }
}

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

(6).自動(dòng)構(gòu)造器繼承
如上所述撮执,子類(lèi)默認(rèn)情況下不會(huì)繼承父類(lèi)構(gòu)造器。但是舷丹,如果滿足某些條件抒钱,超類(lèi)構(gòu)造器將自動(dòng)被繼承。在實(shí)踐中颜凯,這意味著不需要在許多常見(jiàn)場(chǎng)景中重寫(xiě)構(gòu)造器谋币,只要這樣做是安全的,就可以以最小的代價(jià)繼承父類(lèi)構(gòu)造器症概。
假設(shè)為子類(lèi)中引入的任何新屬性都提供了默認(rèn)值蕾额,則適用以下兩條規(guī)則:

  • 規(guī)則一:如果你的子類(lèi)沒(méi)有定義任何指定構(gòu)造器,它會(huì)自動(dòng)繼承所有超類(lèi)的指定構(gòu)造器彼城。
  • 規(guī)則二:如果你的子類(lèi)提供了其所有超類(lèi)指定構(gòu)造器的實(shí)現(xiàn)------通過(guò)按照規(guī)則1繼承它們诅蝶,或者通過(guò)提供自定義實(shí)現(xiàn)作為其定義的一部分------那么它將自動(dòng)繼承所有超類(lèi)的便利構(gòu)造器退个。

子類(lèi)可以實(shí)現(xiàn)一個(gè)超類(lèi)的指定構(gòu)造器作為子類(lèi)的便利構(gòu)造器,來(lái)滿足規(guī)則2 调炬。

(7).指定和便利構(gòu)造器的應(yīng)用
下面的示例顯示了指定構(gòu)造器语盈、方便構(gòu)造器和自動(dòng)構(gòu)造器繼承的應(yīng)用。該示例定義了一個(gè)由三個(gè)類(lèi)組成的層次結(jié)構(gòu)缰泡,分別是Food刀荒、RecipeIngredient和ShoppingListItem,并演示了它們的構(gòu)造器如何相互作用棘钞。
層次結(jié)構(gòu)中的基類(lèi)稱(chēng)為Food缠借,這是一個(gè)簡(jiǎn)單的類(lèi),用來(lái)封裝Food的名稱(chēng)武翎。Food類(lèi)引入了一個(gè)名為name的字符串屬性烈炭,并提供了兩個(gè)用于創(chuàng)建Food實(shí)例的構(gòu)造器:

class Food {
    var name: String
     //指定構(gòu)造器
    init(name: String) {
        self.name = name
    }
    //便利構(gòu)造器
    convenience init() {
        self.init(name: "[Unnamed]")
    }
}

下圖顯示了Food類(lèi)的初始化鏈:
swift_04.png

類(lèi)沒(méi)有默認(rèn)的全能構(gòu)造器,因此Food類(lèi)提供了一個(gè)指定構(gòu)造器宝恶,它接受一個(gè)名為name的參數(shù)符隙。此構(gòu)造器可用于創(chuàng)建具有特定名稱(chēng)的新Food實(shí)例:

let namedMeat = Food(name: "Bacon")
// namedMeat's name is "Bacon"

Food類(lèi)中的init(name:String)構(gòu)造器是指定構(gòu)造器,因?yàn)樗_保了新Food實(shí)例的所有存儲(chǔ)屬性都被完全初始化垫毙。Food類(lèi)沒(méi)有超類(lèi)霹疫,因此init(name:String)構(gòu)造器不需要調(diào)用super.init()來(lái)完成初始化。
Food類(lèi)還提供了一個(gè)沒(méi)有參數(shù)的便利構(gòu)造器init()综芥,init()構(gòu)造器調(diào)用Food類(lèi)的指定構(gòu)造器丽蝎。

let mysteryMeat = Food()
// mysteryMeat's name is "[Unnamed]"

第二個(gè)類(lèi)RecipeIngredientFood的子類(lèi)。RecipeIngredient類(lèi)模擬烹飪配方中的一個(gè)成分膀藐。它引入了一個(gè)名為quantity的整型屬性(除了從Food繼承的name屬性之外)屠阻,并定義了兩個(gè)用于創(chuàng)建RecipeIngredient實(shí)例的構(gòu)造器:

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類(lèi)的初始化鏈:

swift_05.png

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

類(lèi)ShoppingListItemRecipeIngredient的子類(lèi)。ShoppingListItem類(lèi)模擬了出現(xiàn)在購(gòu)物清單中的配方配料额各。

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

因?yàn)?code>ShoppingListItem類(lèi)為它引入的所有屬性提供了默認(rèn)值国觉,并且它本身沒(méi)有定義任何構(gòu)造器,所以ShoppingListItem自動(dòng)從它的超類(lèi)繼承了所有指定構(gòu)造器和方便構(gòu)造器虾啦。
下圖顯示了三個(gè)類(lèi)的整體初始化鏈:

swift_06.png
可以使用所有三個(gè)繼承的構(gòu)造器來(lái)創(chuàng)建一個(gè)新的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 ?

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

在類(lèi)麻诀,結(jié)構(gòu)體和枚舉的初始化過(guò)程中,由于傳入無(wú)效的初始化參數(shù)值傲醉,或缺少某種所需的外部資源蝇闭,或阻止初始化成功的其他條件等,都會(huì)導(dǎo)致初始化失敗硬毕。為了處理這些可能失敗的初始化條件呻引,通常會(huì)定義一個(gè)或多個(gè)可失敗的構(gòu)造器作為類(lèi),結(jié)構(gòu)體或枚舉定義的一部分昭殉。通過(guò)在關(guān)鍵字init后面添加問(wèn)好(即init?)定義可失敗構(gòu)造器苞七∶晔兀可失敗構(gòu)造器創(chuàng)建一個(gè)其初始化類(lèi)型的可選值挪丢,初始化失敗時(shí)返回nil蹂风。

注意:不能定義具有相同參數(shù)類(lèi)型和名稱(chēng)的可失敗構(gòu)造器和不可失敗構(gòu)造器。

如下例所示:結(jié)構(gòu)體Animal含有一個(gè)常量字符串屬性species,和一個(gè)含有參數(shù) species的可失敗構(gòu)造器乾蓬。若參數(shù)為空惠啄,則會(huì)觸發(fā)初始化失敗任内;否則撵渡,設(shè)置Animal屬性的值,初始化成功:

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

使用可失敗構(gòu)造器初始化一個(gè)Animal實(shí)例死嗦,并檢查初始化是否成功:

let someCreature = Animal(species: "Giraffe")
// someCreature is of type Animal?, not Animal

if let giraffe = someCreature {
    print("An animal was initialized with a species of \(giraffe.species)")
}
// Prints "An animal was initialized with a species of Giraffe"

如果將空字符串值傳遞給可失敗構(gòu)造器的species參數(shù)趋距,則構(gòu)造器會(huì)觸發(fā)初始化失敗:

let anonymousCreature = Animal(species: "")
// anonymousCreature is of type Animal?, not Animal

if anonymousCreature == nil {
    print("The anonymous creature could not be initialized")
}
// Prints "The anonymous creature could not be initialized"

(1).枚舉的可失敗構(gòu)造器
可使用可失敗構(gòu)造器根據(jù)一個(gè)或多個(gè)參數(shù)來(lái)選擇合適的枚舉成員越除。 如果提供的參數(shù)與適當(dāng)?shù)拿杜e成員不匹配节腐,則構(gòu)造器可能會(huì)失敗。
如下例所示:

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è)可失敗構(gòu)造器為三種可能的狀態(tài)選擇適當(dāng)?shù)拿杜e情況摘盆,如果參數(shù)與以下三種狀態(tài)之一不匹配時(shí)會(huì)導(dǎo)致初始化失斠砣浮:

let fahrenheitUnit = TemperatureUnit(symbol: "F")
if fahrenheitUnit != nil {
    print("This is a defined temperature unit, so initialization succeeded.")
}
// Prints "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.")
}
// Prints "This is not a defined temperature unit, so initialization failed."

(2).具有原始值枚舉的可失敗構(gòu)造器
具有原始值的枚舉自動(dòng)接收一個(gè)可失敗構(gòu)造器init?(rawValue:),它接受一個(gè)與原始值類(lèi)型相匹配的參數(shù)rawValue孩擂,若找到匹配的枚舉成員狼渊,則選擇;若不存在匹配值类垦,則觸發(fā)初始化失敗狈邑。
如下例所示:

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.")
}
// Prints "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.")
}
// Prints "This is not a defined temperature unit, so initialization failed."

(3).初始化失敗的傳播
類(lèi),結(jié)構(gòu)體或枚舉的可失敗構(gòu)造器可以將其委托給同一類(lèi)蚤认,結(jié)構(gòu)體或枚舉的另一個(gè)可失敗構(gòu)造器米苹。類(lèi)似地,子類(lèi)的可失敗構(gòu)造器可以委托給超類(lèi)的可失敗構(gòu)造器烙懦。
在任何一種情況下驱入,若委托給另一個(gè)導(dǎo)致初始化失敗的構(gòu)造器,整個(gè)初始化過(guò)程立即失敗氯析,并且不會(huì)進(jìn)一步執(zhí)行初始化代碼亏较。

注意:一個(gè)可失敗構(gòu)造器也可以委托給一個(gè)不可失敗的構(gòu)造器。如果需要向現(xiàn)有的初始化過(guò)程添加一個(gè)潛在的失敗狀態(tài)掩缓,而該初始化過(guò)程在其他情況下不會(huì)失敗雪情,請(qǐng)使用此方法。

如下例所示:

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

如果創(chuàng)建CartItem實(shí)例時(shí)你辣,使用非空的name和大于1的quantity,則初始化成功巡通。

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

如果創(chuàng)建CartItem實(shí)例時(shí)尘执,傳入的quantity等于0,則初始化失敗。

if let zeroShirts = CartItem(name: "shirt", quantity: 0) {
    print("Item: \(zeroShirts.name), quantity: \(zeroShirts.quantity)")
} else {
    print("Unable to initialize zero shirts")
}
// Prints "Unable to initialize zero shirts"

類(lèi)似的宴凉,如果創(chuàng)建CartItem實(shí)例時(shí)誊锭,使用的name為空字符串,則初始化失敗弥锄。

if let oneUnnamed = CartItem(name: "", quantity: 1) {
    print("Item: \(oneUnnamed.name), quantity: \(oneUnnamed.quantity)")
} else {
    print("Unable to initialize one unnamed product")
}
// Prints "Unable to initialize one unnamed product"

(4).重寫(xiě)可失敗構(gòu)造器
可以在子類(lèi)中覆蓋超類(lèi)可失敗構(gòu)造器丧靡,就像任何其他構(gòu)造器一樣∽严荆或者温治,使用子類(lèi)不可失敗構(gòu)造器覆蓋超類(lèi)可失敗構(gòu)造器。這使你能夠定義一個(gè)初始化不能失敗的子類(lèi)戒悠,即使超類(lèi)的初始化允許失敗熬荆。
注意,如果使用不可失敗的子類(lèi)構(gòu)造器覆蓋可失敗的超類(lèi)構(gòu)造器绸狐,委托給超類(lèi)構(gòu)造器的唯一方式是強(qiáng)制解包可失敗超類(lèi)構(gòu)造器的結(jié)果卤恳。

注意: 可以使用不可失敗構(gòu)造器覆蓋可失敗構(gòu)造器,但是不能反過(guò)來(lái)六孵。

如下例所示:
下面的示例定義了一個(gè)Document類(lèi)纬黎。這個(gè)類(lèi)建模一個(gè)可以用name屬性初始化的文檔,該屬性可以是非空字符串值劫窒,也可以是nil本今,但不能是空字符串:

class Document {
    var name: String?
    // this initializer creates a document with a nil name value
    init() {}
    // this initializer creates a document with a nonempty name value
    init?(name: String) {
        if name.isEmpty { return nil }
        self.name = name
    }
}

下例是Document的子類(lèi)AutomaticallyNamedDocument。這個(gè)子類(lèi)重寫(xiě)超類(lèi)的兩個(gè)指定構(gòu)造器主巍。如果該子類(lèi)的實(shí)例初始化時(shí)沒(méi)有name參數(shù)冠息,或者傳入一個(gè)空字符串到init(name:)構(gòu)造器時(shí),該子類(lèi)中的兩個(gè)構(gòu)造器都能夠確保它的屬性name有一個(gè)初始值"[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子類(lèi)使用不可失敗構(gòu)造器init(name:)覆蓋了父類(lèi)的可失敗構(gòu)造器init?(name:)飘千。因?yàn)樵撟宇?lèi)用一種不同于超類(lèi)的方式處理了空字符串的情況窖逗,它的構(gòu)造器不會(huì)失敗显熏,因此提供類(lèi)不可失敗的構(gòu)造器喷兼。
可以在構(gòu)造器中使用強(qiáng)制解包來(lái)調(diào)用超類(lèi)的可失敗構(gòu)造器,作為子類(lèi)不可失敗構(gòu)造器實(shí)現(xiàn)的一部分肄渗。如下例镇眷,UntitledDocument子類(lèi)在初始化時(shí),調(diào)用了超類(lèi)的可失敗構(gòu)造器init(name:)翎嫡。

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

在這種情況下欠动,如果超類(lèi)的init(name:)構(gòu)造器在調(diào)用時(shí),傳入空字符串,則這個(gè)強(qiáng)制解包操作會(huì)導(dǎo)致運(yùn)行時(shí)錯(cuò)誤具伍。但是在子類(lèi)UntitledDocument的構(gòu)造器中翅雏,調(diào)用它時(shí)傳入了一個(gè)字符串常量,該構(gòu)造器不會(huì)失敗人芽,因此在運(yùn)行時(shí)不會(huì)導(dǎo)致錯(cuò)誤發(fā)生望几。

(5). 可失敗構(gòu)造器init!
我們通常會(huì)定義一個(gè)可失敗構(gòu)造器,該構(gòu)造器通過(guò)在init關(guān)鍵字(init?)后放置問(wèn)號(hào)來(lái)創(chuàng)建適當(dāng)類(lèi)型的可選實(shí)例啼肩¢献保或者衙伶,我們也可以定義一個(gè)可失敗構(gòu)造器祈坠,該構(gòu)造器創(chuàng)建一個(gè)隱式解包的可選實(shí)例,該實(shí)例具有適當(dāng)?shù)念?lèi)型矢劲。為此赦拘,在init關(guān)鍵字(init!)后面放置一個(gè)感嘆號(hào),而不是問(wèn)號(hào)芬沉。
你可以從init?委托給init!躺同,反之亦然;也可以使用init!覆蓋init?丸逸,反之亦然蹋艺。 你也可以從init委托給init !,但是這么做黄刚,如果init!構(gòu)造器導(dǎo)致初始化失敗捎谨,會(huì)觸發(fā)斷言操作。

7. Required構(gòu)造器

在類(lèi)構(gòu)造器的定義之前添加required修飾符憔维,來(lái)指示類(lèi)的每個(gè)子類(lèi)都必須實(shí)現(xiàn)該初始化器涛救。

class SomeClass {
    required init() {
        // initializer implementation goes here
    }
}

還要將required修飾符添加到所需構(gòu)造器的每個(gè)子類(lèi)實(shí)現(xiàn)之前,以表明初始化器需求適用于繼承鏈中的其他子類(lèi)业扒。當(dāng)重寫(xiě)一個(gè)必須實(shí)現(xiàn)的指定構(gòu)造器時(shí)检吆,不用再添加override修飾符。

class SomeSubclass: SomeClass {
    required init() {
        // subclass implementation of the required initializer goes here
    }
}

注意:如果可以使用繼承的構(gòu)造器滿足需求程储,則無(wú)需顯式實(shí)現(xiàn)required構(gòu)造器蹭沛。

8.使用閉包或函數(shù)設(shè)置默認(rèn)屬性值

如果存儲(chǔ)屬性的默認(rèn)值需要某些自定義或設(shè)置,則可以使用閉包或全局函數(shù)為該屬性提供自定義的默認(rèn)值章鲤。每當(dāng)初始化屬性所屬類(lèi)型的新實(shí)例時(shí)摊灭,將調(diào)用閉包或函數(shù),并將其返回值作為屬性的默認(rèn)值咏窿。
這些類(lèi)型的閉包或函數(shù)通常會(huì)創(chuàng)建與屬性相同類(lèi)型的臨時(shí)值斟或,調(diào)整該值以表示所需的初始狀態(tài),然后返回該臨時(shí)值作為屬性的默認(rèn)值集嵌。
使用閉包來(lái)提供默認(rèn)屬性值:

class SomeClass {
    let someProperty: SomeType = {
        // create a default value for someProperty inside this closure
        // someValue must be of the same type as SomeType
        return someValue
    }()
}

注意萝挤,閉包的結(jié)束花括號(hào)后面跟著一對(duì)空的括號(hào)御毅。這告訴Swift立即執(zhí)行閉包。如果省略這些括號(hào)怜珍,則試圖將閉包本身賦值給屬性端蛆,而不是閉包的返回值。

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

let board = Chessboard()
print(board.squareIsBlackAt(row: 0, column: 1))
// Prints "true"
print(board.squareIsBlackAt(row: 7, column: 7))
// Prints "false"

注意:如果使用閉包初始化屬性酥泛,請(qǐng)記住今豆,在執(zhí)行閉包時(shí),實(shí)例的其余部分尚未初始化柔袁。這意味著你不能在閉包中訪問(wèn)任何其他屬性值呆躲,即使這些屬性具有默認(rèn)值。也不能使用隱式self屬性捶索,或調(diào)用實(shí)例的任何方法插掂。

9.其他專(zhuān)題模塊

Swift 4.2 基礎(chǔ)專(zhuān)題詳解

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市腥例,隨后出現(xiàn)的幾起案子辅甥,更是在濱河造成了極大的恐慌,老刑警劉巖燎竖,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件璃弄,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡构回,警方通過(guò)查閱死者的電腦和手機(jī)夏块,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)捐凭,“玉大人拨扶,你說(shuō)我怎么就攤上這事∽鲁Γ” “怎么了患民?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)垦梆。 經(jīng)常有香客問(wèn)我匹颤,道長(zhǎng),這世上最難降的妖魔是什么托猩? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任印蓖,我火速辦了婚禮,結(jié)果婚禮上京腥,老公的妹妹穿的比我還像新娘赦肃。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布他宛。 她就那樣靜靜地躺著船侧,像睡著了一般。 火紅的嫁衣襯著肌膚如雪厅各。 梳的紋絲不亂的頭發(fā)上镜撩,一...
    開(kāi)封第一講書(shū)人閱讀 49,166評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音队塘,去河邊找鬼袁梗。 笑死,一個(gè)胖子當(dāng)著我的面吹牛憔古,可吹牛的內(nèi)容都是我干的遮怜。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼投放,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼奈泪!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起灸芳,我...
    開(kāi)封第一講書(shū)人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎拜姿,沒(méi)想到半個(gè)月后烙样,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蕊肥,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年谒获,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片壁却。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡批狱,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出展东,到底是詐尸還是另有隱情赔硫,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布盐肃,位于F島的核電站爪膊,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏砸王。R本人自食惡果不足惜推盛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望谦铃。 院中可真熱鬧耘成,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至控嗜,卻和暖如春茧彤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背疆栏。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工曾掂, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人壁顶。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓珠洗,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親若专。 傳聞我的和親對(duì)象是個(gè)殘疾皇子许蓖,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

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