【官方文檔翻譯】Swift 4.0.3 ? Initialization

原文鏈接

初始化(Initialization)

Initialization是為了使用一個類、結構體或者枚舉實例的準備過程。這個過程牽涉到給實例的每個存儲的屬性設置一個初始值和使用之前所需的任何其他設置或者初始化

你通過定義一個initializers構造器實現(xiàn)這個初始化過程,這是一個可以創(chuàng)建指定的類新實例的特殊方法。與OC的構造方法不同的是,Swift構造器不用返回值。它們的主要作用是確保一個新實例在它們第一次使用的時候被正確的初始化。

類的實例也可以實現(xiàn)一個deinitializer析構方法,用來當這個類的實例被銷毀之前執(zhí)行一些清理工作。更多的內容參見Deinitialization

給被存儲的屬性設置初始值(Setting Initial Values for Stored Properties)
類和結構體的實例在被創(chuàng)建之前必須給它們所有的屬性設置一個合適的初始值。存儲的屬性不能處于不確定狀態(tài)。

你可以在構造器內部給一個屬性設置初始值,或者在它們被定義的時候賦一個默認值。這些實現(xiàn)會在下面講解到。

注意
當你給存儲屬性一個默認值,或者在它的構造方法里設置它的初始值,這些方式都是直接設置屬性,不會調用任何屬性觀察者。

構造方法(Initializers)

構造方法在特定的類創(chuàng)建新實例的時候被調用。用它最簡單的方法,一個類似無參數(shù)實例方法的構造器,使用init關鍵字

init() {
     // perform some initialization here
}

下面這個例子定義了一個Fahrenheit的結構體在華氏溫度的范圍內存儲一個溫度。在Fahrenheit結構體中有一個存儲屬性,temperature,被定義為Double類:

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"

這個機構體定義了唯一的構造器,init,沒有參數(shù),在這個方法里初始化了temperature賦值為32.0。

默認屬性值(Default Property Values)

你可以像上面一樣,在構造器里給存儲屬性賦初始值,此外,指定一個默認值作為屬性聲明的一部分。你可以在屬性被定義的時候給屬性賦上一個初始值。

注意
如果一個屬性一直使用初始值,我們采用在定義的時候賦初始值,而不是在構造器中賦值。雖然結果一樣,但是賦默認值它和定義緊密的聯(lián)系在一起,這樣做使代碼更簡短、更清晰并且可以使你從初始值推斷屬性的類型。默認值也讓你更方便利用默認構造器和繼承,就像這節(jié)描述的這樣。

你可以將上面的Fahrenheit結構體用一種更簡單的方式寫出來,通過給它的temperature屬性在被定義的時候賦初值:

struct Fahrenheit {
    car temperature = 32.0
}

自定義初始化(Customizing Initialization)

你可以像下面這一節(jié)描述的那樣,在初始化時通過傳入?yún)?shù)、使用可選類型的屬性或者在初始化過程中給常量屬性賦值來自定義初始化過程。

初始化參數(shù)(Initialization Parameters)

你可以提供初始化參數(shù)作為定義構造函數(shù)的一部分,去定義傳入值的類型和名稱。初始化參數(shù)與函數(shù)和方法參數(shù)一樣,具備同樣的作用和語法。

下面這個例子定義了一個Celsius結構體,Celsius結構體實現(xiàn)了兩個自定義的構造器,分別叫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 - 237.15
    }
}

let boilingPointOfWater = Celsius(fromfahrenheit: 212.0)
// boilingPointOfWater.temperatureInCelsius is 100.0
let freezingPointOfWater = Celsius(fromKelvin: 273.15)
// freezingPointOfWater.temperatureInCelsius is 0.0

第一個構造器有一個構造參數(shù)使用了參數(shù)標簽fromFahrenheit和參數(shù)名稱fahrenheit。第二個構造器有一個構造參數(shù)使用了參數(shù)標簽fromKelvin和一個參數(shù)名稱為kelvin。兩個構造器轉化他們傳入的參數(shù)成為合理的值,并將它存入temperatureInCelsius中。

參數(shù)名稱和參數(shù)標簽(Parameter Names and Argument Labels)

像函數(shù)和方法的參數(shù)一樣,當調用構造方法時,初始化參數(shù)可以同時擁有一個在構造器方法中使用的名稱和一個參數(shù)標簽。

然而,構造器不能像函數(shù)和方法一樣通過圓括號前的函數(shù)名稱確定。因此,在確定哪個構造方法應該被調用時,構造參數(shù)的名稱和類型起到了一個重要的作用。正因為如此,如果你沒有提供參數(shù)標簽,Swift為構造函數(shù)提供了自動的參數(shù)標簽。

下面的例子定義了Color結構體,擁有red,green,和blue三個常量屬性。這些屬性存儲0.0到1.0之間的值表明顏色的數(shù)值。

Color提供了一個構造函數(shù)使用三個Double類型的參數(shù)給對應的參數(shù),還提供了一個參數(shù)的構造函數(shù),給所有參數(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
    }
}

兩個構造函數(shù)都可以創(chuàng)建新的Color實例,通過給每個構造參數(shù)提供數(shù)值

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

注意到調用這些構造函數(shù)不使用參數(shù)標簽是不行的。只要定義了構造函數(shù)就必須使用參數(shù)標簽,忽略它就會編譯報錯。

let veryGreen = Color(0.0, 1.0,10.0)
// this reports a compile-time error - argument labels are required

沒有參數(shù)標簽的構造器參數(shù)(Initializer Parameters Without Argument Labels)

如果你不想在構造器參數(shù)中使用參數(shù)標簽,寫一個下劃線(_),覆蓋默認添加標簽的行為。

這里有一個上面Celsius例子的延伸版本,添加了一個額外的構造器去創(chuàng)建一個Celsius實例,在相同類型下,使用Double類型數(shù)值創(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 is 37.0

這個構造器調用Celsius(37.0)是足夠清楚的在不適用參數(shù)標簽情況下。因此像這樣寫init(_ celsiue: Double)構造器是合適的。

可選類型屬性(Optional Property Types)

如果你的自定義的類有一個屬性的邏輯允許為空值--可能因為它的值不能在初始化的過程中被設置,或者因為在以后的某個時間點允許為空值--我們就聲明這個屬性為可選類型。可選類型的屬性在被初始化的過程中自動賦值為nil,指明這個屬性有可能在初始化期間沒有值。

下面這個例子定義了一個SurveyQuestion類,有一個可選類型的response屬性:

class SurveyQuestion {
    var text: String
    var response: String?
    init(text: String) {
       self.text = text
    }
 }

func ask() {
     print(text)
}

let cheseQuestion = SurveyQuestion(text: "Do you like cheese?")
cheeseQuestion.ask()
// Prints "Do you like cheese"
cheeseQuestion.response = "Yes, I do like cheese"

問題的答案在問之前都是未知的,所以response屬性被聲明為String?,或者叫“optional String”。當以個SurveyQuestion被初始化時,自動地被賦值為nil,意味著還沒有值。

在初始化過程中賦值常量屬性(Assigning Constant Properties During Initialization)

你可以在初始化過程中的任何時間給一個常量屬性賦值,只要在初始化完成時被設置一個確定的值。一旦常量屬性被賦值,永遠不能更改。

注意
對于類實例而言,一個常量的屬性可以在初始化過程中被修改,僅僅在當前類中,不能被子類修改。

你可以重寫上面SurveyQuestion的例子,把text寫為常量屬性而不是變量屬性,指明了SurveyQuestion實例一旦被創(chuàng)建就不能修改

class SurbeyQuestion {
    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.)

默認的構造器(Default Initializers)

當一個類或者結構體沒有提供任何一個構造器時,Swift 提供一個默認的構造器給所有的結構體和類,這個構造器給所有的屬性提供默認值。這個默認的構造器通過給所有屬性賦默認值來簡單的創(chuàng)建一個實例。

下面這個例子定義了一個ShoppingListItem類,封裝了購物單的name,quantity和purch項。

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

因為ShoppingListItem類的所有屬性有默認值,并且因為它是一個基類,沒有父類,所以ShoppingListItem自動的獲得默認的構造器去創(chuàng)建一個新實例,這個新實例的屬性均為默認值。(name屬性是一個可選字符串屬性,所以它自動設置為nil,即使沒有明確的寫出來)。上面這個例子使用了默認的構造器去創(chuàng)建一個ShoppingListItem新實例,使用了構造器語法,被寫為 ShoppingListItem(),并把它賦給了一個變量。

結構體類型的逐一成員構造器(Memberwise Initializers for Structure Types)

如果結構體類型沒有自定義任何的構造器,他們將自動的接收一個逐一成員構造器。與默認構造器不一樣,結構體類型接收一個逐一成員構造器即使被存儲的屬性不是默認值。

逐一成員構造器是一個快速的方式初始化一個新結構體實例的成員屬性,新實例屬性的初始值可以通過注意成員構造器依靠名稱傳遞。

下面的例子定義了一個Size結構體,有兩個屬性,分別叫做width和height。這兩個屬性通過賦初始值0.0被推斷為Double類型。

Size結構體自動接收一個init(width:height:)逐一成員構造器,當你初始化一個新實例:

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

let twoByTwo = Size(width: 2.0, height:2.0)

數(shù)值類型的構造代理(Initializer Delegation for Value Types)

在一個實例的初始化過程中,可以在自己的構造器中調用其他的構造器作為構造自己的一部分。這個過程被稱為 initializer delegation,避免了在構造過程中代碼重復。

構造代理如何工作以及構造代理的形式在值類型 和 對象類型的規(guī)則是不一樣的。值類型(結構體和枚舉)沒有繼承,所以他們的構造代理過程相對簡單,因為它們的代理只能提供它們自己的構造方法。但是,Classes類型,可以繼承其他類,正如被描述的這樣Inheritance。這意味著class類型有責任去保證他們繼承的所有屬性在初始化過程中都被賦上合適的值。這些要求闡述在Class Inheritance and Initialization。

對于值類型,你可以在自定義的構造器中調用self.init去調用相同類型的其它構造器。你可以在一個構造器內部調用self.init.

注意如果你給一個值類型自定義了一個構造器,你就不能再調用這個類型的默認構造器(如果是結構體類型,就是逐一成員構造器)。這個限制避免了一種情況,就是有時需要在更復雜的構造器中添加一些其它設置,卻不小心調用了默認構造器。

理解:結構體也有默認構造器,如果你自定義了構造器,是可以調用結構體的默認構造器的,只是不能調用結構體的逐一成員構造器(重寫除外)。

注意
如果你想自己定義的值類型即可以使用默認構造器或者逐一成員構造器也能使用自定義的構造器,你需要在Extensions中寫自動義的構造方法,而不是在類的實現(xiàn)文件中。更多的信息,請參考Extensions。

下面這個例子定義了一個Rect結構體去表示一個幾何矩形。這個例子需要兩個支持的結構體,分別叫做Size 和 Point,他們的屬性初始值都被設置為0.0

struct Size {
    var width = 0.0, height = 0.0
}
struct Point {
    var x = 0.0, y = 0.0
}

你可以用三種方式初始化一個Rect機構體-- 通過使用默認的zero-initialized 初始化size 和 point屬性,通過提供一個具體的origin point 和 size,或者通過提供一個中心點和size。這三種可選擇的構造方法作為機構體定義的一部分:

struct Rect {
    var  origin = Point()
    var size = Size()
    init() {}
    init(oringin: 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)
    }
}

第一個構造器,功能和沒有自定義構造器時接收的默認構造器一樣。這個構造器有一個空的函數(shù)體,用{}表示。調用這個構造器返回一個Rect實例,它的origin和size屬性都被初始化為定義時候的值:
let basicRect = Rect()
// basicRect's origin is (0.0, 0.0) and its size is (0.0, 0.0)

第二個構造器,init(origin:size:),功能和構造器沒有自定義構造器時接收的逐一成員構造器一樣。這個構造器簡單的指定了origin和size參數(shù)給存儲屬性。

let originRect = Rect(origin: Point(x: 2.0, y:2.0), size: Size(width: 5.0, height:5.0))
// originRect's origin is (2.0, 2.0) and its size is (5.0, 5.0)

第三個構造器,init(center:size), 有一些復雜,它基于center點計算出origin點 和 size 去構造。然后調用(或者代理)init(origin:size:)構造器,給對應的屬性存儲新值。

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)

init(center:size:)構造器會給它對應的屬性賦新值,但是,它更方便。

注意
還有一種方法,你自己不用定義init() 和 init(origin:size),參看 Extensions。

類繼承和初始化(Class Inheritance and Initialization)

所有類的存儲屬性,包括從父類繼承來的所有屬性--在初始化的過程中必須賦值。

Swift給class類型定義了兩種構造器,保證所有的存儲屬性接收一個初始值。它們被稱為指定構造器(designated initializers) 和 便利構造器(convenience initializers)。

指定構造器和便利構造器

Designated initializers 是一個類的主要構造器。指定構造器完全初始化類的所有屬性并且在父類鏈上調用父類的初始化方法。

類有可能有幾個指定構造器,大部分類只有一個指定構造器。當初始化過程發(fā)生時,指定構造器是“漏斗”點,并且通過它在繼承鏈上繼續(xù)。

每個類都必須至少有一個指定構造器。有些情況下,需要通過從父類繼承一個或者多個指定構造器,詳情參看Automatic Initializer Inheritance。

便利構造器是次要的,給一個類提供構造器。你可以通過在同一個類中調用指定構造器并傳入特定的值來定義一個便利構造器。你也可以為一些特殊的使用情況或者輸出值類型創(chuàng)建一個便利構造器。

如果你的類不需要便利構造器,就不用提供。創(chuàng)建便利構造器起到了便利、清晰的作用。

指定構造器和便利構造器的語法(Syntax for Designated and Convenience Initializers)

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

init(`parameters`) {
       `statements`
}

類Type的指定構造器(Initializer Delegation for Class Types)

為了簡單的描述指定構造器和便利構造器之間的關系,Swift遵循下面三條構造器之間的代理調用:

規(guī)則1:

指定構造器必須從調用它直屬父類的指定構造器。

規(guī)則2:

便利構造器必須調用同類的構造器。

規(guī)則3:

便利構造器最終必須調用指定構造器。

簡單的記法:

  • 指定構造器必須向上代理
  • 便利構造器必須橫向代理

看下圖:


這里,父類有唯一一個指定構造器和兩個便利構造器。一個便利構造器調用了另一個便利構造器,另一個構造器又調用了指定構造器。這滿足了上面提到的規(guī)則2和規(guī)則3,父類上面沒有父類,所以不用遵循規(guī)則1。

這幅圖里的子類有兩個指定構造器和一個便利構造器。便利構造器必須調用其中一個指定構造器,因為它只能在同類中調用。這符合上面的規(guī)則2和規(guī)則3。所有的指定構造器必須調用父類唯一的指定構造器,符合規(guī)則1。

注意
這些規(guī)則不影響如何使用你的類創(chuàng)建實例,上面的圖表中,任何的構造器都可以被用于創(chuàng)建一個完全初始化的該類的實例,這些規(guī)則只影響你書寫類的構造器的實現(xiàn)方法。

下面這個圖展示了4個類更復雜的繼承關系。它說明了在類的初始化過程中指定構造器是如何作為“漏斗”的。

兩階段初始化 (Two-Phase Initialization)

在Swift中,類的初始化是一個兩階段過程。第一個階段,給每個類引用的屬性賦初始值。一旦這個過程完成,開始第二個階段,每個類有機會在被使用前自定義它們的存儲屬性

使用兩階段初始化過程是安全的,同時,在類的繼承中,仍然給予了完成的靈活性。兩個階段初始化避免了屬性在沒有初始化完成的情況下被訪問,而且避免了屬性被另一個構造器設置不期望的值

注意
Swift的兩段初始化過程和OC相似。主要的不同在于第一個階段,OC賦值0或者null(例如0或者nil)給每個屬性。Swift的初始化更靈活在于讓你自定義屬性的初始值,并且沒有設置值時賦0或者nil。

理解:這里說的不同,指的是我們使用Swift時,可以像let A = 0.0 這樣賦初始值

Swift的編譯器執(zhí)行4個有用的安全檢查保證兩段初始化沒有錯誤完成。

安全檢查 1

一個指定構造器在向上代理父類的的構造器之前必須確保這個類的所有屬性都被初始化。

像上面提到的那樣,一個對象的內存當它所有的屬性都被初始化時才被認為完全初始化。滿足這一點,一個指定構造器在沿構造鏈向上之前必須初始化自己的所有屬性。

安全檢查 2

一個指定構造器必須在設置繼承類的屬性之前,必須向上代理父類的構造器,如果沒有這么做,這個指定構造器賦的新值會在父類的初始化過程中被覆蓋。

安全檢查 3

便利構造器在給任何屬性賦值時必須橫向代理其他構造器(包括被同類定義的屬性)。如果不這么做,這個便利構造器賦的新值會被所在類的指定構造器覆蓋。

安全檢查 4

在第一個階段沒有完成時,一個構造器不能調用任何實例方法,獲得實例的屬性值,或者使用self作為值。

類的實例在第一階段完成前不是完全有效的。只有在第一個階段完成后,類的實例才被成為有效,你才可以訪問屬性,調用實例的方法。

這里基于上面的四條安全檢查,展示了兩段初始化如何完成。

第一個階段
  • 指定構造器或者便利構造器在類中被調用。

  • 新實例的內存被分配。內存仍然沒有被初始化

  • 類的指定構造器確認所有引用的屬性都有值?,F(xiàn)在,這些存儲屬性的內存被初始化。

  • 指定構造器交給父類構造器為它的屬性執(zhí)行同樣的操作。

  • 繼續(xù)這個操作直到基類

  • 一旦到達繼承鏈的頂端,并且基類確保它的所有屬性都有值,此時,這個實例的內存被認為完全初始化,第一個階段完成。

第二階段
  • 從鏈的頂部開始,每個指定構造器有機會自定義實例。構造器現(xiàn)在可以訪問self并且可以修改它的屬性,調用實例的方法,等等。

  • 最終,所有的便利構造器有機會去自定義實例并且使用self。

這里展示了一個假設的子類和父類如何在第一個階段的初始化。

在這個例子中,首先調用了子類的便利構造器。這個便利構造器仍然不能修改任何屬性。它橫向代理給同類的指定構造器。

指定構造器確保子類的所有屬性有初始值,像安全檢查1描述的那樣。然后沿著繼承鏈調用它的父類的構造方法。

父類的指定構造器確保了父類的所有屬性都有值,因為更多的父類,所以不必繼續(xù)向上代理。

一旦父類的所有屬性都有值,它的內存被認為完全初始化,第一個階段完成。

下面看一下同一個初始化過程第二階段:

父類的指定狗仔器現(xiàn)在有機會去自定義實例(雖然不是必須的)。

一旦父類的指定構造器完成,子類的指定構造器可以自定義(同理,不是必須的)。

最終,一旦子類的指定構造器完成,便利構造器(最初被調用的那個)可以添加自定義操作。

構造器的繼承和重寫(Initializer Inheritance and Overriding)

與OC的子類不同,Swift子類默認不繼承他們父類的構造器。Swift避免了一種情況,一個更特別的子類通過繼承父類的簡單構造器完成初始化,導致沒有完全 或者 正確初始化。

注意
父類構造器在一些情況下被繼承,但是只有在安全的情況下適合這樣做。更多的闡述,參看Automatic Initializer Inheritance。

如果你想定制一個子類去實現(xiàn)一個或者多個和父類相同的構造器,你要在子類中提供自定義的實現(xiàn)。

當你寫一個子類構造器和父類的指定構造器相同時,你需要通過重寫使得這個指定構造器生效。因此,你必須寫在子類構造器定義的前面寫上override修飾符。即便你重寫了默認構造器,你也應該添加修飾符。參看Default Initializers。

作為一個被重寫的屬性、方法或者下標,override修飾符的出現(xiàn)會促使Swift檢查父類有一個相同的指定構造器被重寫,并且驗證你的重寫的構造器的參數(shù)是否指定正確。

注意
即使你的子類實現(xiàn)了父類的指定構造器作為便利構造器,你也要使用override修飾符。

相反的,如果你寫的子類的構造器和父類的便利構造器一樣,父類的便利構造器永遠不會被你的子類直接調用,像上面描述的那樣Initializer Delegation for Class Types。因此,嚴格的說你的子類沒有重寫父類的構造器。因此,你不用寫override修飾符。

下面這個例子定義了一個Vehicle基類。這個基類聲明了numberOfWheels的存儲屬性,賦初始值0。這個numberOfWheels屬性被用于一個叫description的計算屬性用于描述車的特征:

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

Vehicle類提供了一個默認值給它的存儲屬性,并且沒有提供任何的構造器。因此,它自動接收一個默認構造器,參看Default Initializers。這個默認構造器肯定是類的指定構造器,可以用來創(chuàng)建一個Vehicle實例,numberOfWheels為0:

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

下面的例子定義了Vehicle的子類 叫做Bicycle:

class Bicycle{
    override init (){ 
        super.init()
        numberOfwhieels = 2
    }
}

Bicycle類自定義了一個指定構造器,init()。這個指定構造器和父類的指定構造器相同所以Bicycle版本的構造器需要被override修飾。

Bicycle的構造器init()方法中首先調用了父類的super.init(),調用了父類的默認構造器,這確保了Bicycle修改從父類繼承來的numberOfWheels屬性之前,numberOfWheels已經(jīng)被父類Vehicle初始化。調用super.init()之后,numberOfWheels的初始值被修改為2.

如果你創(chuàng)建了一個Bicycle實例,你可以調用它的繼承來的description計算屬性看一看numberOfWheels屬性是否被更新了:

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

注意
子類可以在初始化過程中修改繼承來的變量屬性,但是不能修改繼承來的常量屬性。

自動構造器繼承(Automatic Initializer Inheritance)

像上面提到的,子類默認不能繼承它們父類的構造器。但是,父類構造器在一些情況下會被自動繼承。在開發(fā)中,這意味著在許多普通的情況下,你不需要重寫構造器,你可以很輕松繼承父類構造器,如果這么做是安全的。

假如你給子類所有引用的屬性提供了默認值,就符合下面的兩條規(guī)則:

規(guī)則 1

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

規(guī)則 2

如果你的子類提供了它的父類所有指定構造器的實現(xiàn)-- 要么通過規(guī)則1那樣繼承它們,要么自己定義--它將自動的繼承父類所有的便利構造器。(If your subclass provides an implementation of all of its superclass designated initializers—either by inheriting them as per rule 1, or by providing a custom implementation as part of its definition—then it automatically inherits all of the superclass convenience initializers.)

即使子類定義了更多的便利構造器,這些規(guī)則同樣適用。

注意
根據(jù)規(guī)則2,子類可以實現(xiàn)父類的指定構造器作為自己的便利構造器。

指定構造器和便利構造器的應用(Designated and Convenience Initializers in Action)

下面這個例子展示了指定構造器、便利構造器、和自動繼承的實現(xiàn)。這個例子定義了三個類的繼承分別叫做Food,RecipeIngredient,和ShoppingListItem,并且展示了他們的構造器如何交互。

Food是基類,這是一個簡單的類,封裝了所有食品的名稱。Food類引用了一個String類型的name屬性,提供了兩個構造器創(chuàng)建Food實例:

class Food {
    var name: String
    init(name: String) {
         self.name = name
    }
   convenience init() {
       self.init(name: "[Unnamed]")
   }
}

下面這個圖展示了Food類的構造器鏈

類是沒有默認的逐一成員構造器的,所以Food累提供了一個帶name參數(shù)的指定構造器。這個構造器可以被用來創(chuàng)建一個指定名稱的Food實例:

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

init(name: String) 構造器是Food類提供的指定構造器,因為它可以保證新實例的所有屬性被完全初始化。Food類沒有父類,所以init(name: Stirng)構造方法中不需要調用super.init()去完成它的初始化。

Food類也提供了一個便利構造器,init(),沒有參數(shù),這個init()構造器通過橫向調用Food類的指定構造器init(name: String),創(chuàng)建name為[Unnamed]的新的food實例:

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

在這個繼承關系中,第二個類叫RecipeIngredient,是Food的子類。RecipeIngredient類是食譜的組成模型。它引用了一個Int類型的quantity屬性(在它的父類Food的name屬性基礎上,添加了quantity屬性)并且定義了兩個創(chuàng)建實例的構造方法:

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類有一個指定構造器,init(name: Stirng, quantity: Int),可以初始化一個新的RecipeIngredient實例的所有屬性。這個構造器首先把傳遞過來的quantity參數(shù)賦給quantity屬性,這是RecipeIngredient僅有的新屬性(相對于父類)然后,構造器向上代理父類的init(name: String)構造器。這個過程滿足安全檢查1Two-Phase Initialization。

RecipeIngredient也定義了一個便利構造器,init(name: String),被用于只根據(jù)name創(chuàng)建實例。這個便利構造器指定了所有的實例的quantity屬性都為1。定義這個便利構造器使得RecipeIngredient實例更加快捷、方便的被創(chuàng)建,并且避免了創(chuàng)建幾個quantity為1 的實例時代碼重復。這個便利構造器簡單的通過橫向代理類的指定構造器,傳入quantity的值為1。

RecipeIngredient提供的便利構造器init(name: Stirng),和父類Food的指定構造器帶有相同的參數(shù)。因為這個便利構造器重寫了它父類的指定構造器,所以它必須用override修飾符修飾(參看Initializer Inheritance and Overriding

即便RecipeIngredient提供了init(name: String)構造器作為便利構造器,RecipeIngredient仍然算是實現(xiàn)了父類的所有指定構造器。因此,RecipeIngredient自動繼承它的父類的所有便利構造器。

在這個例子里,recipeIngredient的父類是Food,有一個便利構造器叫做init()。這個構造器因此被RecipeIngredient繼承。被繼承來的init()函數(shù)調用和在父類中完全一樣,但是它代理的調用方法init(name: Stirng) 是RecipeIngredient類的不是父類的。

現(xiàn)在有三種構造方法可以用于創(chuàng)建RecipeIngredient實例:

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

最后一個類繼承RecipeIngredient,叫做ShoppinglistItem。這個類表示了食譜在購物清單里的展現(xiàn)。

購物單中的每個項目,起初都是“未購買的”。表達這個意思,ShoppintListItem引用了一個布爾屬性叫做purchased,賦初始值為false。ShoppingListItem也添加了一個計算屬性description,為ShoppingListItem實例提供了一個文本描述。

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

注意
ShoppingListItem沒有通過構造器給purchased提供初始值,因為在購物單中的item開始總是未被購買的。

因為它給所有引用的屬性提供了默認值并且自身沒有定義任何構造器,ShoppingListItem自動從它的父類繼承所有的指定構造器和便利構造器

理解:上面就解釋了規(guī)則2。

下面這幅圖顯示了這三個類完整的構造器鏈:

你現(xiàn)在可以使用繼承來的所有構造器創(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 {
    pring(item.description)
}

// 1 x Orange juice ?
// 1 x Bacon ?
// 6 x Eggs ?

這里,一個新的breakfastList數(shù)組通過一個包含三個ShoppingListItem實例的字面量數(shù)組被創(chuàng)建。這個數(shù)組的類型被推斷為[ShoppingListItem]。這個數(shù)組被創(chuàng)建后,第一個元素的name從“[Unnamed]”被改為“Orange juice” 并且被標記為已購買。通過打印每個item的description顯示出他們的默認值已經(jīng)像預期那樣設置好了。

可失敗的構造器(Failable Initializers)

有時需要定義一個類、結構體、或者枚舉,允許它的初始化過程失敗。這個失敗可能是因為無效的初始化參數(shù)、缺少外界必須的資源、或者一些其他的組織初始化成功的情況。

處理初始化可以失敗的情形,定義一個或者多個可失敗構造器作為類、結構體、枚舉的一部分,你通過在init后面添加一個問號init? 表示一個可失敗構造器。

注意
你不能用相同的參數(shù)類型和名稱同時定義一個可失敗的和不可失敗的構造器。

一個可失敗的構造器創(chuàng)建它構造類型的可選值。你在可失敗的構造器中寫return nil來指示初始化失敗被調用的時刻。

注意
嚴格的來說,構造器不能返回值。它們的作用是確保初始化完成時,自己已經(jīng)被完全和正確的初始化。即使你寫return nil 調用初始化失敗,你不能使用return去表示初始化成功。

未完待續(xù)。。。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容