Swift-構(gòu)造過程

  • 過定義構(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)
  1. 第一個 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)昨忆。
  2. 第二個 Rect 構(gòu)造器 init(origin:size:)丁频,在功能上跟結(jié)構(gòu)體在沒有自定義構(gòu)造器時獲得的逐一成員構(gòu)造器是一樣的。這個構(gòu)造器只是簡單地將 origin 和 size 的實參值賦給對應(yīng)的存儲型屬性邑贴。
  3. 第三個 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]")!
    }
  1. 如果在調(diào)用父類的可失敗構(gòu)造器 init?(name:) 時傳入的是空字符串害碾,那么強制解包操作會引發(fā)運行時錯誤矢劲。
  2. 不過,因為這里是通過字符串常量來調(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
    }()
}
  1. 注意閉包結(jié)尾的花括號后面接了一對空的小括號怜珍。
  2. 這用來告訴 Swift 立即執(zhí)行此閉包。
  3. 如果你忽略了這對括號凤粗,相當(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”
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末酝润,一起剝皮案震驚了整個濱河市燎竖,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌要销,老刑警劉巖构回,帶你破解...
    沈念sama閱讀 219,490評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異疏咐,居然都是意外死亡纤掸,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評論 3 395
  • 文/潘曉璐 我一進店門凳鬓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來茁肠,“玉大人,你說我怎么就攤上這事缩举。” “怎么了匹颤?”我有些...
    開封第一講書人閱讀 165,830評論 0 356
  • 文/不壞的土叔 我叫張陵仅孩,是天一觀的道長。 經(jīng)常有香客問我印蓖,道長辽慕,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,957評論 1 295
  • 正文 為了忘掉前任赦肃,我火速辦了婚禮溅蛉,結(jié)果婚禮上公浪,老公的妹妹穿的比我還像新娘。我一直安慰自己船侧,他們只是感情好欠气,可當(dāng)我...
    茶點故事閱讀 67,974評論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著镜撩,像睡著了一般预柒。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上袁梗,一...
    開封第一講書人閱讀 51,754評論 1 307
  • 那天宜鸯,我揣著相機與錄音,去河邊找鬼遮怜。 笑死淋袖,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的锯梁。 我是一名探鬼主播即碗,決...
    沈念sama閱讀 40,464評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼涝桅!你這毒婦竟也來了拜姿?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤冯遂,失蹤者是張志新(化名)和其女友劉穎蕊肥,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蛤肌,經(jīng)...
    沈念sama閱讀 45,847評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡壁却,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,995評論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了裸准。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片展东。...
    茶點故事閱讀 40,137評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖炒俱,靈堂內(nèi)的尸體忽然破棺而出盐肃,到底是詐尸還是另有隱情,我是刑警寧澤权悟,帶...
    沈念sama閱讀 35,819評論 5 346
  • 正文 年R本政府宣布砸王,位于F島的核電站粹舵,受9級特大地震影響互婿,放射性物質(zhì)發(fā)生泄漏漱凝。R本人自食惡果不足惜诫睬,卻給世界環(huán)境...
    茶點故事閱讀 41,482評論 3 331
  • 文/蒙蒙 一赵刑、第九天 我趴在偏房一處隱蔽的房頂上張望槽袄。 院中可真熱鬧芹关,春花似錦皿桑、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至骡显,卻和暖如春疆栏,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背惫谤。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評論 1 272
  • 我被黑心中介騙來泰國打工壁顶, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人溜歪。 一個月前我還...
    沈念sama閱讀 48,409評論 3 373
  • 正文 我出身青樓若专,卻偏偏與公主長得像,于是被迫代替她去往敵國和親蝴猪。 傳聞我的和親對象是個殘疾皇子调衰,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,086評論 2 355

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

  • 構(gòu)造過程是使用類、結(jié)構(gòu)體或枚舉類型的實例之前的準備過程自阱。在新實例可用前必須執(zhí)行這個過程嚎莉,具體操作包括設(shè)置實例中每個...
    CDLOG閱讀 339評論 0 1
  • 這是16年5月份編輯的一份比較雜亂適合自己觀看的學(xué)習(xí)記錄文檔,今天18年5月份再次想寫文章沛豌,發(fā)現(xiàn)簡書還為我保存起的...
    Jenaral閱讀 2,762評論 2 9
  • 中文文檔 一趋箩、存儲屬性的初始賦值 類和結(jié)構(gòu)體在創(chuàng)建實例時,必須為所有存儲型屬性設(shè)置合適的初始值加派。存儲型屬性的值不能...
    伯wen閱讀 186評論 0 1
  • ?構(gòu)造過程是使用類叫确、結(jié)構(gòu)體或枚舉類型一個實例的準備過程。在新實例可用前必須執(zhí)行這個過程芍锦,具體操作包括設(shè)置實例中每個...
    EndEvent閱讀 632評論 0 3
  • 本章將會介紹 存儲屬性的初始賦值自定義構(gòu)造過程默認構(gòu)造器值類型的構(gòu)造器代理類的繼承和構(gòu)造過程可失敗構(gòu)造器必要構(gòu)造器...
    寒橋閱讀 769評論 0 0