初始化(Initialization)

原文html

初始化時準備使用的類,結構體,枚舉的實例的過程。這個過程包括爲實例上的每一個存儲屬性設置一個初始值而且執行一些其餘的在新實例準備好使用以前必需的配置和初始化。swift

經過定義initializers實現初始過程,像能夠調用來建立一個新的特定類型的實例的特殊方法。不想Objective-C,swift的初始化方法沒有返回值。他們第一準則是確保一個類型的新的實例在他們第一次使用以前正確的初始化。數組

類類型的實例能夠實現deinitializer,在那個類的實例釋放以前執行自定義的清楚。更多關於deinitializers的信息,查看Deinitialization.安全

爲存儲屬性設置初始化值(Setting Initial Values for Stored Properties)

類和結構體必需在這個類或者結構體的實例建立以前把所有的存儲屬性設置爲合適的初始化值。bash

你能夠在屬性的定義中在一個初始化方法中或者經過分配一個默認的屬性值來給一個存儲屬性設置一個初始化值。微信

當你給存儲屬性分配一個默認值的時候,或者在初始化方法中設置初始化值,屬性的值時直接設置的,沒有調用任何屬性的觀察者。

初始化方法(Initializers)

調用初始化方法來建立一個特殊類型的實例。在他最簡單的形式中,初始化方法像滅有參數的方法實例,用init關鍵字寫:閉包

init() {
    // perform some initialization here
}複製代碼

下面的例子定義了一個新的名爲Fahrenheit的新結構體來存儲用Fahrenheit溫標表示的溫度。Fahrenheit結構體有一個存儲屬性,temperature,是Double類型:app

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,沒有參數,使用32初始化存儲屬性temperature(在Fahrenheit中水的冰點)。ide

默認屬性值(Default Property Values)

你能夠在初始化方法中設置一個存儲屬性的初始化方法,像上面展現的。另外能夠,在屬性聲明中指定一個默認的屬性值。經過在定義它的時候給屬性分配一個初始化值來指定一個默認的屬性值。函數

若是屬性通常使用相同的初始化值,在初始化方法中提供一個默認的值而不是設置一個值。最後的結果是同樣的,可是默認值把屬性的初始化和他的聲明綁的更近了。使初始化更簡短,清晰而且使你能夠從默認值中推導屬性的類型。默認初始值也使你更簡單的使用默認初始化方法和初始化繼承,在這章節後面描述。

能夠把上面Fahrenheit結構體用更簡單的形式寫,經過在屬性聲明的時候爲這個temperature屬性提供一個默認的值:

struct Fahrenheit {
    var temperature = 32.0
}複製代碼

自定義初始化方法(Customizing Inititalization)

你能夠用輸入的參數和可選的屬性類型自定義初始化方法,或者在初始化過程當中分配常量屬性,像下面章節中描述的。

初始化參數(Initialization Parameters)

在初始化定義中提供初始化參數,來定義自定義初始化過程當中值的類型和名稱。初始化參數有和函數和方法參數同樣的特色和語法。

下面的例子定義了一個名爲Celsius的結構體,存儲了用Celsius度表示的溫度。Celsius結構體實現了兩個名爲init(fromFahrenheit:)和init(fromKelvin:)的兩個自定義初始化方法,從一個不一樣的溫度度標中的值來初始化一個結構體的新的實例:

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複製代碼

第一個初始化方法有一個簡單的有fromFahrenheit參數標籤的初始化參數和一個名爲fahrenheit的參數。第二個初始化方法有一個名爲簡單的初始化參數哦raomKelvin和一個名爲kelvin的參數造成。兩個初始化把單個參數轉換爲對應的Celsius值而且把這個值存儲在名爲temperatureInCelsius屬性中。

實參名稱和形參標籤(Parameter Names and Argument Labels)

像函數和方法的參數,初始化參數能夠有在初始化方法體中使用的實參和調用初始化方法時使用的形參標籤。

不過,初始化方法在括號前沒有和函數與方法同樣的明確的函數名稱。因此,一個初始化方法的名字和類型在肯定應該調用哪一個初始化方法的時候扮演了一個特別重要的角色。所以,若是你沒有提供swift爲初始化方法中每個參數提供了自動的形參標籤。

下面的例子定義了一個名爲color的結構體,有三個名爲red,green和blue的常量屬性。這些屬性存儲了一個0.0到1.0之間的值來指示color中red,green和blue的值。

Color爲他的red,green和blue成員提供了一個有三個適當的Double類型的參數的初始化方法。Color也用簡單的white參數提供給了第二個初始化方法,用來爲所有三個color部分提供相同的值。

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
    }
}複製代碼

兩個初始化方法均可以用來建立一個新的Color實例,經過給每個初始化參數提供命名的值:

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複製代碼

沒有參數標籤的初始化參數(Initializer Parameters Without Argument Labels)

若是你不想在初始化參數使用參數標籤,經過underscore代替那個參數的明確的參數標籤來重寫默認的表示。

這裏是從上面Initialization Parameters中Celsius例子的擴展版本,用一個添加的初始化方法從一個已經在Celsius標度中存在的Double值來建立一個新的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)不須要參數標籤就有清晰地目的。因此適合把這個初始化器寫成init(_celsius:DOuble)讓他能夠經過提供一個未命名的Double值來被調用。

可選屬性類型(Optional Property Types)

若是你的自定義類型有一個邏輯上容許沒有值得存儲屬性--可能由於它的值能夠在初始化時設置,或者由於容許他在後面的時候是「no value」--用一個可選類型聲明屬性。可選類型的屬性用nil自動初始化,指明屬性是有意的想在初始化時是「no value yet」的。

下面的例子定義了一個名爲SurveyQuestion的類,用一個名爲response的可選String屬性:

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."複製代碼

調查問題的反饋知道它被詢問以前是不知道的,因此response屬性用一個String?類型聲明,或者「optional String」。它自動分配一個默認的值nil,意味着「no string yet」,當一個新的SurveyQuestion實例被初始化的時候。

在初始化的時候分配常量屬性(Assigning Constant Properties During Initialization)

你能夠在初始化的任什麼時候候給常量屬性分配一個值,只要在初始化結束以前設置爲一個確切的值。一旦常量屬性分配了值,它永遠都不能修改了。

對於類實例,一個常量屬性能夠只能在初始化的時候被引入它的類修改。不能被一個子類修改。

你能夠用上面的SurveyQuestion雷子來用一個常量屬性而不是變量屬性來表示問題的text屬性,來指定一旦SurveyQuestion的實例被建立,問題不能修改。即便如今text屬性是一個常量,他仍然能夠在類的初始化器中設置:

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.)"複製代碼

默認初始化器(Default Initializers)

swift爲給他們所有的屬性提供了默認值而且本身沒有提供初始化器的類或者結構體的提供了默認的初始化器。默認初始化器簡單的建立了一個新的所有的屬性都設置爲他們默認值的實例。

這個例子定義了一個名爲ShoppiingLIstItem的類,把一個東西的名字,量,和可能狀態放在購物列表中:

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

由於ShoppingListItem類型所有屬性都有默認值,也由於它是一個沒有父類的基礎類,ShoppingListItem自動獲取一個默認的建立一個所有屬性都設置爲默認值的新的實例的初始化器實現。(name屬性是一個可選的String屬性,它自動接受一個默認的值nil,即便這個值沒有在代碼中寫。)上面的例子用初始化器語法使用ShoppingListItem類的默認初始化器來建立一個新的類的實例,寫做ShoppingListItem(),而且給這個新的實例分配一個名爲Item的變量。

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

結構體類型若是他們沒有定義任何他們自定義的初始化器那麼會自動接受一個成員初始化器。不想默認初始化器,結構體接受一個成員初始化器,即便他有一些沒有默認值的存儲屬性。

成員初始化結構起是初始化新結構體實例的成員屬性的簡寫方式。能夠把新實例的屬性的初始化值用名字傳遞成員初始化器。

下面的例子定義了一個有兩個叫width和height的兩個屬性的名爲Size的結構體。兩個屬性經過分配一個默認的值0.0推導爲Double的類型。

Size結構體自動接收一個Init(width:height:)的成員初始化器,你能夠用來初始化一個新的Size實例:

struct Size {
    var width = 0.0, height = 0.0
}
let twoByTwo = Size(width: 2.0, height: 2.0)複製代碼

當你調用一個成員初始化器時,你能夠忽略有默認值的屬性的值。在上面的例子中,Size結構體對他的兩個height和width屬性有默認的值。你能夠忽略一個或者兩個屬性,初始化器對你忽略的屬性使用默認的值--例如:

let zeroByTwo = Size(height: 2.0)
print(zeroByTwo.width, zeroByTwo.height)
// Prints "0.0 2.0"

let zeroByZero = Size()
print(zeroByZero.width, zeroByZero.height)
// Prints "0.0 0.0"複製代碼

值類型的初始化器代理(Initializer Delegation for Value Types)

初始化器能夠調用其餘初始化器來執行一個實例的初始化的一部分。這個過程,成爲初始化代理,避免多個初始化器的重複代碼。

初始化器代理如何工做的規則,什麼形式的代理是容許的,值類型和類類型是不一樣的。值類型(structures和enumerations)不支持繼承,因此他們的初始化代理過程相對簡單,由於他們只能夠用他們本身提供的初始化器作代理。類,不管怎樣,能夠從其餘類繼承,像在Inheritance中描述的。這意味着那些類有額外的職責來保證他們繼承的所有存儲屬性在初始化的時候能被分配到一個合適的值。這些職責的描述在下面的Class Inheritance and Initialization

對於值類型,當你寫本身的自定義初始化器時使用self.init來指向相同值類型的其餘初始化器。你只能在初始化器中調用self.int。

注意若是你爲值類型定義了一個自定義的初始化器,你就不能在訪問那個類型的默認初始化值(或者成員初始化器,若是是一個結構體)。這個約束防止了一個狀況,在更復雜的初始化器中提供的額外的基礎配置意外的被默認用自動初始化器的人忽略掉。

若是你但願你的自定義值類型使用默認初始化器和成員初始化器來初始化,也能用你本身的自定義初始化器,在你的擴展中寫自定義初始化器,而不是在類型的原始的實現中。更多的信息,查看 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結構體--經過使用它的默認0初始化origin和size屬性的值,經過提供一個特別的原點和尺寸,或者經過提供一個指定的中心點和尺寸。這些初始化選項用三個Rect結構體中定義的自定義初始化器來表示:

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)
    }
}複製代碼

第一個Rect初始化器,init(),功能和若是結構體沒有本身的自定義初始化器時接受的默認的的初始化器同樣。初始化器有一個空的主體,用空的一對花括號表示。調用這個初始化器返回一個origin和size屬性都是用他們屬性定義中的默認值Point(x:0.0,y:0.0)和Size(width:0.0,height:0.0)來初始化的Rect實例。

let basicRect = Rect()
// basicRect's origin is (0.0, 0.0) and its size is (0.0, 0.0)複製代碼

第二個Rect初始化器,init(origin:size:),功能和若是結構體沒有本身的自定義初始化器時接收到的成員初始化器同樣。這個初始化器簡單的把origin和size參數值分配給合適的存儲屬性:

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)複製代碼

第三個Rect初始化器,init(center:size:),稍微更復雜。開始在一個center點和size值的基礎上計算一個合適的原點。而後調用(或者代理)帶init(origin:size:)初始化器,將新的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:)能夠把新的origin和size的值分配給他本身合適的屬性。不過,對於init(center:size:)初始化器來講使用已經存在的提供了那個功能的初始化器更方便。

關於另外一種不用本身定義Init()和Init(origin:size:)初始化器來寫這個例子的方式,看 Extensions

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

一個類的所有存儲屬性--包括任何類從父類繼承來的屬性--必須要在初始化時分配一個初始化值。

swift爲類類型定義了兩種初始化器來幫助所有存儲屬性接受一個初始化值。這些就是設計初始化器和便利初始化器。

設計初始化器和便利初始化器(Designated Initializers and Convenience Initializers)

設計初始化器是一個類的主要初始化器。一個設計初始化器徹底初始化類的所有屬性而且調用合適的父類初始化器來向上父類鏈繼續初始化過程。

類經常有很是少的設計初始化器,對類來講只有一個使很是同常的狀況。設計初始化器發生的「漏斗」點,經過跟着父類鏈向上繼續初始化過程。

每一個類必需有至少一個初始化器。在一些狀況下,這個需求經過從父類中繼承一個或者兩個來保證,以下面Automatic Initializer Inheritance描述的。

遍歷初始化器是給類的第二個,支持初始化的初始化器。你能夠定義一個遍歷初始化器來調用一個和遍歷構造器在一個類中的設計初始化器來吧設計構造器的參數設置爲默認值。你也能夠爲了特殊的使用狀況或者輸入值類型來定義一個遍歷構造器來建立一個這個類的實例。

若是你的類不須要他們,你不用提供遍歷初始化器。當一個一般的初始化模式的便捷方式能夠節約時間或者使類的初始化在目的上更明確的時候建立一個遍歷初始化器。

設計和便利初始化器的語法(Syntax for Designated and Convenience Initializers)

類的設計初始化器和值類型的簡單初始化器用同樣的方式書寫:

init(parameters) {
    statements
}複製代碼

遍歷構造器用同樣的風格書寫,可是把convenience修飾詞放在init關鍵字前面,用一個空格分隔:

convenience init(parameters) {
    statements
}複製代碼

類類型的初始化代理(Initializer Delegation for Class Types)

爲了簡化設計和便利初始化器之間的關係,swift在初始化器之間爲代理調用用了下面三個原則:

原則1

  設計初始化器必需從他的直接父類中調用一個設計初始化器。

原則2

  遍歷初始化器必需從相同的類中調用另外一個初始化器。

原則3

  遍歷初始化器必需最終調用一個設計初始化器。

一個簡單的記住這些的方式:

  • 設計初始化器必需向上代理
  • 遍歷初始化器必需穿過代理

這些原則在下面的圖形中解釋:


這裏,父類有一個單一的設計初始化器和兩個遍歷初始化器。一個遍歷初始化器調用另外一個初始化器,它轉而調用單一的設計初始化器。從上面這保證了原則2和3。父類沒有更多的父類,因此原則1沒有使用。

這個同種的子類有兩個設計初始化器,和一個遍歷初始化器。遍歷初始化器必需調用兩個設計初始化器之一,由於它必需調用相同類中的其餘遍歷器。在上面這確保了2和3.兩個設計初始化器必需調用弗雷中的單一的設計初始化器,來確保上面的原則1.

這些原則沒有影響你的類的使用者建立每一個類的實例。上面圖標的任何初始化器能夠用來建立一個徹底初始化的他們所屬於的類的實例。原則隻影響你如何寫類初始化器的實現。

下面面的圖標展現了一個四個類的更加複雜的類繼承。他解釋了在這個繼承中的設計初始化器如何爲類初始化執行「funnel」點,簡化了鏈中類之間的內部關係:



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

swift中的類初始化是一個兩階段的過程。在第一個階段,每一個存儲屬性被引入它的類分配了一個初始化值。一旦每一個存儲屬性的初始狀態肯定了,第二個階段開始,在新的實例認爲準備使用以前給每一個類自定義它的存儲屬性的機會。

兩階段初始化過程的應用是初始化很是安全,同時給與了類層中每個類完整的靈活性。兩階段初始化防止屬性值在他們初始化以前被訪問,防止屬性值被另外一個初始化器意外的設置爲不一樣的值。

swift的兩階段初始化過程和Objective-C中的初始化類似。主要的不一樣在階段1,Objective-C給每一個屬性分配了null或者0(例如0或者nil)。swift的初始化流更靈活,他讓你能夠設置自定義的初始值,能夠處理0和nil不是合法的默認值的類型。

swift的編譯器提供了四個有用的安全檢查來確保兩階段初始化沒有錯誤的完成:

安全檢查1

  一個設計的初始化器必需肯定在它代理到父類的初始化器以前所有由本身引入的屬性都被初始    化。

像上面提到的,一個對象的內存只用當它的所有存儲屬性的狀態都知道的時候才認爲是徹底初始化了。爲了能夠知足這個原則,設計初始化器必需確保他本身所有的屬性在向鏈上傳遞以前初始化完了。

安全檢查2

  一個設計初始化器必需在給他繼承的屬性分配值以前代理到它的父類初始化器中。若是沒有這樣,設計初始化器分配的新值將會被父類的初始化器重寫。

安全檢查3

  遍歷初始化器必定要在給任何屬性分配值以前代理到其餘初始化器中(包括同一個類中定義的屬性)。若是不這樣,遍歷初始化器分配的新值會被他的類的設計遍歷器重寫。

安全檢查4

  一個初始化器不能調用任何任何實例方法,讀取任何實例的屬性,或者把self引用爲一個值,知道初始化器的第一個階段完成。

類的實例在第一個階段結束前不是徹底合法的。一旦類實例在第一階段結束時才當作是有效的時才能訪問屬性,調用方法。

這裏是兩階段初始化如何完成,在上面四個安全檢查的基礎之上:

階段1

  • 一個設計或者遍歷初始化器在類上被調用
  • 那個類的新實例的內存被分配。內存尚未初始化。
  • 一個類的設計初始化器確認被那個類引入的所有存儲屬性有一個值。這些存儲屬性的內存如今初始完了。
  • 設計初始化器處理父類初始化器來爲他的存儲屬性執行同樣的任務。
  • 這會按類繼承鏈向上繼續指導到達鏈的最頂部。
  • 一旦到達了類繼承鏈的最頂部,鏈中最後一個類確保了所有的存儲屬性有一個值,實例的內存才認爲是徹底初始化,階段1結束。

階段2

  • 從鏈頂端向會進行,鏈中的每個設計初始化器能夠選擇更多的自定義實例。初始化器如今能夠訪問self而且能夠修改他的屬性,調用實例方法,等等。
  • 最後,鏈中的任何遍歷初始化器能夠自定義實例和對self進行操做。

這裏是對於一個假設地子類和父類的初始化調用的階段1看起來什麼樣:


在這個例子中,初始化在子類中調用一個遍歷初始化器開始。這個遍歷初始化器不能修改任何屬性。他代理到同一個類的一個設計初始化器。

設計初始化器肯定子類所有的屬性有一個值,像安全檢查1.而後調用一個父類中的設計初始化器來向鏈上繼續初始化。

父類的設計初始化器確保了全部父類的屬性有值。沒有更多的父類要初始化,因此不須要更多的代理。

只要所有的父類屬性有值,它的內存認爲徹底初始化了。階段1結束。

這裏是一樣的初始化調用的階段2看起來的樣子:


父類的設計初始化器如今有機會自定義實例(即便他不是必需要這樣)。

一旦父類的設計初始化器結束,子類的設計初始化器能夠執行額外的自定義(一樣的,不是同樣要這樣作)。

最後,一旦子類的設計初始化器結束了,原先調用的遍歷初始化器能夠執行額外的自定義。

初始化器的繼承和重寫(Initializer Inheritance and Overriding)

不想Objective-C中的子類,swift的子類不須要默認繼承父類的初始化器。swift的方案防止了父類中的簡單初始化器被一個更特殊的子類繼承而用來建立了一個不完整或者正確的初始化的子類的新的實例的狀況,

父類初始化器在明確的環境下繼承,可是隻有當他是安全的並且適合這樣作。更多的信息,查看下面的 Automatic Initializer Inheritance

若是你想自定義子類來呈現一個或者多個和父類同樣的初始化器,你能夠在子類中提供一個這些初始化器的自定義的實現。

當你寫了一個匹配一個父類的設計初始化器的子類初始化器的時候,你實際上提供了那個設計初始化器的一個重寫。因此,你必須在子類的初始化器定義前寫override修飾詞。即便你重寫了自動提供的默認初始化器也要這樣作,像Default Initializers中描述的。

像一個重寫的屬性,方法或者下標,override修飾詞的出現提示swift來檢查父類有一個匹配的設計初始化器來重寫,而且驗證你重寫的初始化器的參數是按需求同樣指定的。

當重寫父類的設計初始化器的時候你一般寫override修飾詞,即便你子類的初始化器的實現是一個遍歷初始化器。

相反的,若是你寫了一個子類的匹配父類遍歷初始化器的初始化器,那個父類的初始化器能夠不被你的子類直接調用,像上面Initializer Delegation for Class Types每條原則所表述的。因此,你的子類沒有(嚴格來說)提供父類初始化器的重寫。結果就是,當提供一個父類的遍歷初始化器的匹配實現時不寫override修飾詞。

下面的例子定義了一個名爲Vehicle的基礎類。基礎類聲明瞭一個名爲numberOfWheels的存儲屬性,使用一個默認Int值0.numberOfWheels屬性被名爲description的計算屬性用來建立一個String的vehicle的特性的描述:

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

若是一個子類的初始化器在初始化過程的階段2沒有執行自定義,父類有一個0參數的設計初始化器,你能夠在給子類的所有存儲屬性分配值以後忽略調用super.init()。

這個例子定義了另外一個Vehicle的子類,名爲Hoverboard。在它的初始化器中,Hoverboard類只設置它的color屬性。替代了明確的調用super.init,初始化器依靠隱式的調用它的父類的初始化器來完成過程。

class Hoverboard: Vehicle {
    var color: String
    init(color: String) {
        self.color = color
        // super.init() implicitly called here
    }
    override var description: String {
        return "\(super.description) in a beautiful \(color)"
    }
}複製代碼

一個Hoverboard的實例使用Vehicle初始化器提供的wheels的默認數字。

let hoverboard = Hoverboard(color: "silver")
print("Hoverboard: \(hoverboard.description)")
// Hoverboard: 0 wheel(s) in a beautiful silver複製代碼

子類能夠在初始化時修改繼承的變量屬性,可是不能修改繼承的常量屬性。

自動初始化器繼承(Automatic Initializer Inheritance)

像上面提到的,子類默認不繼承父類的初始化器。不過,若是肯定的條件知足父類的初始化器會自動被繼承。實際中,這意味着在一般狀況下你不須要寫初始化器的重寫,而且任什麼時候候都是安全的來花費很小的努力而繼承父類的初始化器。

假設在一個子類中你爲擬引入的新的屬性提供了默認的值,應用下面的兩個原則:

原則1

  若是你的子類沒有定義設計初始化器,自動繼承他父類所有的設計初始化器

原則2

  若是你的子類提供了父類設計初始化器的所有實現--即便按原則1的方式繼承的他們,或者在他本身的定義中提供了一個自定義的實現--他自動繼承父類的所有遍歷初始化器。

即便你的子類添加了更多的遍歷初始化器這些原則也適用。

子類能夠把父類設計初始化器的實現爲子類便利初始化器來做爲知足原則2的一部分

設計和便利初始化器使用(Designated and Convenience Initializers in Action)

下面的例子展現了設計初始化器,便利初始化器,和自動初始化器的繼承。這個例子定義了一個名爲Food,RecipeIngredient,和ShoppingListItem三個類的繼承,而且解釋了他們的初始化器的交互。

繼承中的基礎類名爲Food,是一個簡單的封裝了一個食品名字的類。Food類引入了一個String名爲name的屬性並未建立Food實例提供了兩個初始化器:

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

下面的圖標展現了Food類的初始化鏈:


類沒有默認的成員初始化器,因此Food類提供了一個有一個名爲name參數的設計初始化器。這個初始化器能夠用一個特殊的名字來建立一個Food實例:

let namedMeat = Food(name: "Bacon")
// namedMeat's name is "Bacon"複製代碼

Food類中的init(name:String)初始化器是一個設計初始化器,由於它確保了一個新的Food實例的所有存儲屬性都初始化。Food類沒有父類,因此init(name:String)初始化器不須要調用super.init()來完成他的初始化。

Food類也提供了一個便利初始化器,init(),沒有參數。init()初始化器經過用[Unnamed]的name值代理到Food類的init(name:String)來微信的food提供一個默認參數佔位名:

let mysteryMeat = Food()
// mysteryMeat's name is "[Unnamed]"複製代碼

層級中的第二個類是Food的子類名爲RecipeIngredient。類RecipeIngredient模型了作飯食譜中的原料。它引入了一個名爲Quantity的Int屬性(出去它從Food繼承的name屬性)而且定義了兩個建立RecipeIngredient實例的初始化器:

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:String,quantity:Int),能夠用來構造一個新的RecipeIngredient實例的所有屬性。這個初始化器開始給quantity屬性分配傳入的quantity參數,是RecipeIngredient引入的惟一新的屬性。作完這個以後,初始化器代理到Food類的init(name:String)。這個過程確保了上面Two-Phase Initialization安全檢查1。

RecipeIngredient也定義了一個便利初始化器,init(name:String),只經過name來建立RecipeIngredient實例。這個便利初始化器假設每一個沒有明確quantity的RecipeIngredient實例的quantity爲1。這個便利初始化器的定義使REcipeIngredient實例更快更方便的來建立,避免在建立多個單quantityRecipeIngredient實例的時候重複代碼。這個便利初始化器簡單的代理到類的設計初始化器,傳了一個值爲1的quantity。

RecipeIngredient提供的init(name:String)便利初始化器和Food中init(name:String)設計初始化器採用同樣的參數。由於這個便利初始化器重寫了它父類中的設計初始化器,必須使用override修飾詞標記(像在Initializer Inheritance and Overriding中描述的)。

即便recipleImg像個便利初始化器同樣提供init(name:String),可是REcipeIngredient提供父類所有設計初始化器的實現。因此,REcipeIngredient也自動繼承父類的所有便利初始化器。

在這個例子中,RecipeIngredient的父類是Food,有一個簡單的名爲init()的便利初始化器。因此這個初始化器被RecipeIngredient繼承。init()函數的繼承版本和Food版本徹底是同樣的方式,除了它是代理到RecipeIngredient版本的init(name:String)而不是Food版本。

這所有三個初始化器能夠用來建立新的RecipeIngredient實例:

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

層級中第三個和最後一個類是RecipeIngredient的子類,名爲ShoppingListItem。Shoppinglistitem類對出如今購物列表中的食譜原料進行建模。

在購物列表中的每一項開始是「unpurchased」。表示這個事實,ShoppingListItem引入一個名爲purchase的布爾屬性,有一個false的默認值。ShoppingListItem也增長了一個計算屬性description,提供了一個ShoppingListItem實例的文本描述:

class ShoppingListItem: RecipeIngredient {
    var purchased = false
    var description: String {
        var output = "\(quantity) x \(name)"
        output += purchased ? " ✔" : " ✘"
        return output
    }
}複製代碼

ShoppingListItem沒有定義一個初始化器來給purchased提供一個初始值,由於購物列表中的項一般在開始的時候都是unpurchased。

由於它爲它引入的所有屬性提供了默認的值而且沒有定義本身的初始化器,ShoppingListItem自動繼承它父類的所有設計初始化器和便利初始化器。

下面的圖標爲三個類展現了所有的初始化鏈:


你能夠用所有三個初始化器來建立一個新的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 {
    print(item.description)
}
// 1 x Orange juice ✔
// 1 x Bacon ✘
// 6 x Eggs ✘複製代碼

這裏,由含有三個新的ShoppingListItem實例的字面數組建立了一個新的名爲breakfastList的數組。數組的類型推導爲[ShoppingListItem]。在數組建立以後,數組開始的ShoppingListItem的名字有[Unnamed]改成Orange juice而且標記爲已經purchased。打印數組中每一個對象的description顯示他們默認的狀態定期望的改變了。

可失敗初始化器(Failable Initializers)

有時候定義一個初始化器可能失敗的類,結構體,或者枚舉是有用處的。這個失敗可能由無效的初始化參數值觸發的,必需外部數據的缺失,或者一些其餘阻止初始化成功的狀況。

要處理可能失敗的初始化狀況,在類,結構體,或者枚舉定義中定義一個或者多個可失敗的初始化器。經過在init關鍵字後面放置一個問號表示一個可能失敗的初始化器(init?)。

不能用相同的參數類型和名稱來定義一個可能失敗的和不可能失敗的初始化器

一個可失敗的初始化器建立一個可選的他初始化類型的值。在可失敗初始化器中寫return nil來表示能夠被觸發初始化失敗的地方。

嚴格來講,初始化不返回值。準確的說,他們的任務是確保他們本身在初始化結束以前被完整的正確的初始化。即便你寫return nil來觸發初始化的失敗,不用return關鍵字來指示初始化成功。

例如,失敗的初始化爲了數值類型轉換而實現。來確保數值類型之間的轉換保持值的正確,使用init(exactly:)初始化器。若是類型轉化不能保持值,初始化器失敗。

let wholeNumber: Double = 12345.0
let pi = 3.14159

if let valueMaintained = Int(exactly: wholeNumber) {
    print("\(wholeNumber) conversion to Int maintains value of \(valueMaintained)")
}
// Prints "12345.0 conversion to Int maintains value of 12345"

let valueChanged = Int(exactly: pi)
// valueChanged is of type Int?, not Int

if valueChanged == nil {
    print("\(pi) conversion to Int does not maintain value")
}
// Prints "3.14159 conversion to Int does not maintain value"複製代碼

下面的例子定義了一個名爲Animal的結構體,有一個名爲species的String常量屬性。Animal結構體也定義了一個接受一個單獨名爲species的參數的可能失敗的初始化器。這個初始化器價差傳給初始化器的species是不是空字符串。若是發現一個空字符串,初始化失敗。不然,species屬性的值設置,而且初始化成功:

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

你可使用可失敗的初始化器來初始化一個新的Animal實例而且檢查是否初始化成功了:

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"複製代碼

若是你傳了一個空的字符串值給可失敗的初始化器的species參數,初始化器觸發一個初始化失敗:

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"複製代碼

檢查空的字符串值(例如「」而不是「Giraffe」)和檢查來指明可選String值的缺失不同。在上面的例子中,空字符串是有效的,非可選String。不過,animal不適合擁有一個和他的species屬性同樣的空字符串。爲了模型化這個限制,若是發現了空字符串可失敗初始化器觸發初始化失敗。

枚舉的可失敗初始化器(Failable Initializer for Enumerations)

可使用可失敗的初始化器來在一個或者多個參數基礎上選擇一個合適的枚舉狀況。若是提供的參數沒有匹配合適的枚舉狀況初始化器可能失敗。

下面的例子定義了一個名爲TemperatureUnit的枚舉,有三個可能的狀態(kelvin,celsius,和fahrenheit)。一個可失敗的初始化器用來爲表示溫度符號的Character的值查找一個合適的枚舉狀況:

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
        }
    }
}複製代碼

可使用可失敗的初始化器來爲三個可能的狀態選擇一個合適的枚舉case而且若是參數沒有匹配三個狀態之一的時候使初始化失敗:

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."複製代碼

有RawValues枚舉的可失敗初始化器(Failable Initializers for Enumerations with Raw Values)

有raw values的枚舉自動接收一個可失敗的初始化器,init?(rawValue:),接受一個合適的raw-value類型的名爲rawValue的參數而且若是發現了一個則選擇一個匹配的枚舉狀況,或者若是沒有匹配的值存在觸發一個初始化失敗。

你能夠重寫上面離得TemperatureUnit來使用Character類型的raw values而且使用init?(rawValue:)初始化器:

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."複製代碼

失敗初始化器的傳播(Propagation of Initialization Failure)

一個類,結構體或者枚舉的可失敗的初始化器能夠代理到同一個類,結構體或者枚舉中的其餘可失敗的初始化器。類似的,一個子類可失敗初始化器能夠代理到父類可失敗初始化器。

在這些狀況中,若是你代理另一個能致使失敗的初始化,整個初始化過程立馬失敗,再也不有初始化代碼執行。

一個可失敗的初始化器能夠代理到不可失敗的初始化器。若是你須要給已存在的不會失敗的初始化過程增長潛在的失敗狀態使用這種方式。

下面的例子定義了一個名爲CartItem的子類。CartItem類模型化了一個在線購物車中的對象。CartItem引入了存儲的常量屬性名爲quantity而且確保這個屬性一般有一個至少爲1的值:

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)
    }
}複製代碼

CaritItem的可失敗初始化器開始驗證它接受了一個1或者更大的quantity。若是quantity是無效的,整個初始化過程當即失敗而且沒有更多的初始化代碼執行。一樣的,Product的可失敗的初始化器檢查name值,而且若是name是空字符串初始化器過程當即失敗。

若是你用一個非空的name和1或者大於1的quantity建立一個CartItem實例,初始化成功:

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

若是你嘗試用一個爲0的quantity值建立一個CartItem實例,CartItem初始化器使初始化失敗:

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"複製代碼

類似的,若是你嘗試用一個空的name值建立一個CartItem實例,父類的Product初始化器致使初始化失敗:

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"複製代碼

重寫可失敗初始化器(Overriding a Failable Initializer)

你能夠在子類中重寫父類的可失敗初始化器,就像其餘初始化器同樣。或者,你能夠用子類的不可失敗初始化器來重寫父類的可失敗初始化器。這是你能夠爲初始化不能失敗的子類,即便父類的初始化器是能夠失敗的。

注意若是你用子類的不可失敗初始化器來重寫一個但是白的父類初始化器,惟一代理到父類初始化器的方式是對父類可失敗的初始化器進行強解。

你能夠用不可失敗的初始化器重寫一個可失敗的初始化器可是反過來不行。

下面的例子定義了一個名爲Doucument的類。這個類模型化了一個文檔,能夠用一個非空字符串或者nil的name屬性來初始化,可是不能用空字符串:

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
    }
}複製代碼

下面的例子定義了一個名爲AutomaticallyNamedDocument的Document的子類。AutomaticallyNamedDocument子類重寫了Document的兩個設計初始化器。這些重寫確保了AutomaticallyNamedDocument實例有一個初始化name值「[Untitled]」,若是實例沒有name初始化,或者若是一個空字符串傳給了init(name:)初始化器:

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用非可失敗的初始化器重寫了父類的可失敗的init?(name:)初始化器。由於AutomaticallyNamedDocument用和他的父類不一樣的方式處理空字符串的狀況,它的初始化器不須要失敗,因此它提供了一個非失敗版本的初始化器代替。

在初始化器中你可使用強解包在子類的非失敗初始化器實現中來調用一個父類中的可失敗的初始化器。例以下面的UntitledDoucment子類一般命名爲「[Untitled]」,而且在初始化時使用父類中的可失敗初始化器init(name:)。

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

這種狀況,若是父類的init(name:)初始化器調用空字符串的name,強解包操做會致使運行時錯誤。不過,由於他用一個常量字符串調用,能夠看到初始化器不會失敗,因此沒有運行時錯誤在這個時候發生。

init!可失敗初始化器(The init! Failable Initializer)

一般經過在init關鍵字後面放置一個問號定義一個可失敗的初始化器來建立一個適當類型的可選實例。或者,能夠定義一個建立一個隱式解包了可選適當類型的實例初始化器。經過在init關鍵字後面替代問號放置一個感嘆號實現。

能夠從init?代理到init!,反之亦然,能夠用init!重寫init?,反之亦然。也能夠從init代理到init!,若是iinit!初始化器引發了初始化錯誤會觸發一個斷言。

必需的初始化器(Required Initializers)

在類初始化器前些required修飾詞來指定這個類的每一個子類必需實現這個初始化器:

class SomeClass {
    required init() {
        // initializer implementation goes here
    }
}複製代碼

在子類的必需初始化器實現以前也必需寫required修飾詞,來指明初始化器必需性在鏈中用於更多的子類。當重寫一個必需的設計初始化器時不寫override修飾詞。

若是能用繼承的初始化器知足必需性那你能夠不用提供必需初始化器的明確實現。


用閉包或者函數設置一個默認的屬性值(Setting a Defult Property Value with a Closure or Function)

若是一個存儲屬性的默認值須要一些自定義或者配置,你可使用閉包或者全局函數來給屬性提供一個自定義的默認值。無論任什麼時候候包含屬性的實例的初始化,閉包或者函數被調用,它的返回值做爲屬性的默認值分配給他。

這種閉包或者函數通常建立一個和屬性同樣類型的臨時值,修改那個值來表示想要的初始狀態,而後把臨時值返回做爲屬性的默認值。

這裏是閉包如何用來提供默認屬性值的一個大概說明:

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
    }()
}複製代碼

注意閉包最後的花括號後面跟着一對空的括號。這告訴swift當即執行閉包。若是你忽略了這些括號,你是把閉包分配給了屬性,並非閉包的返回值。

若是你使用一個閉包來初始化屬性,記住實例剩下的部分在閉包執行的時候尚未初始化。這意味着你不能在閉包中訪問任何其餘屬性值,即便這些屬性有默認值。也不能使用隱式的self屬性,後者調用任何實例方法。

下面的例子定義了一個名爲Chessboard的結構體,模型了一個國際象棋的板子。國際象棋在8*8的板子上玩,有黑色和白色的方格。


要表示這個遊戲板子,Chessboard結構體只有一個名爲boardColors的屬性,是一個64個布爾值的數組。數組中的true值表示黑色方格,false值表示白色方格。數組中第一個對象表示板子上上左的方格,而且數組的最後一個對象表示板子上右下的方格。

boardColors數組用閉包初始化來設置它的顏色值:

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]
    }
}複製代碼

任何建立新的Chessboard實例的時候,閉包都被執行,boardColors的默認值被計算並返回。上面例子的閉包爲板子上每一個方格計算並把合適的顏色放到了名爲temporaryBoard的臨時數組中,而後一旦他的設置完成做爲閉包的返回值返回這個臨時數組。返回的數組值存儲在boardColors中而且能夠用工具函數squareIsBlackAt(row:colum:)查詢:

let board = Chessboard()
print(board.squareIsBlackAt(row: 0, column: 1))
// Prints "true"
print(board.squareIsBlackAt(row: 7, column: 7))
// Prints "false"複製代碼
相關文章
相關標籤/搜索