Swift 初始化(Initialization)

初始化(Initialization)

初始化是類、結構體、枚舉類型的準備過程。這個過程涉及到所有存儲屬性的初始化,以及類在被使用之前的其他設置和初始化。

通過定義初始化器來實現這一過程,初始化器可稱之為一種為特定類型創建實例的特殊方法。不像Objective-C,Swift的初始化器沒有返回值。初始化器的主要任務是保證新實例在第一次使用之前能被正確地初始化。

類的實例還可以實現解析器,這個解析器能夠在實例內存被回收之前做一些清理工作。

為存儲屬性設置初始值

類和結構體的實例在創建之前一定要保證所有的存儲屬性具有初始值,存儲屬性不能具有不確定的值。

你可以在初始化器中為存儲屬性設置初始值,也可以在存儲屬性定義的時候就為其設定默認值。

初始化器

初始化器在創建特定類型的實例的時候被調用的。最簡單的形式的初始化器就像一個沒有參數的實例方法,使用了init關鍵字。

init(){
    //執行初始化工作
}

以下是一個簡單的例子:

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”

默認屬性值

盡管可以在初始化器中初始化存儲屬性值,但是在屬性聲明的時候也能給屬性指定默認值。

注:如果在使用中總是保持相同的初始值,那么最好在屬性聲明的時候設定初始值,這樣可以讓初始化和聲明更為緊密,而且讓初始化更為簡潔和清晰,也可以從默認值中推斷類型。而且默認值可以讓你利用默認初始化器和初始化器的繼承。

你可以如下定義以上的結果體Fahrenheit

struct Fahrenheit {
    var temperature = 32.0
} 

自定義初始化

你可以通過輸入參數和可選的屬性類型自定義初始化過程,或者在初始化過程中賦值給常數屬性。

初始化參數

你可以在初始化器定義的時候給初始化器提供參數,初始化參數和函數、方法的參數具有相同的作用和語法。
以下是一個簡單的例子,定義了一個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”

參數名字和形參標簽

初始化器的參數同樣具有名字和標簽,名字給初始化器內部使用,標簽給外部使用。

因為初始化器不能擁有不同的名字,所以參數的類型和名字就非常重要了,以此來區分那個初始化器會被調用。所以,如果你沒有提供標簽的話,那么初始化器就會為每個參數提供一個默認的標簽。

以下是一個簡單的例子:

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

沒有標簽的初始化器參數

如果不想在初始化器參數中使用標簽,可以使用下劃線_來隱式代替默認標簽,這樣的話在調用初始化器的時候就無需寫標簽了,如以下的例子:

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

可選屬性類型

如果某些屬性在邏輯上可以沒有值,或者初始化過程中不方便賦值,可以將其定義為可選類型。可選類型的屬性可以不在初始化器中初始化。

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.

在初始化器中為常量賦值

你可以在初始化過程中為常量賦值,只要在實例初始化完成之前賦值都可以,一旦為常量賦值之后,就無法再修改了。

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

默認初始化器

Swift為結構體和類提供默認的初始化器,只要結構體和類:

  • 存儲屬性都有默認值
  • 沒有自定義初始化器
class ShoppingListItem {
    var name: String?
    var quantity = 1
    var purchased = false
}
var item = ShoppingListItem()

逐一成員初始化器

如果結構體沒有定義任何初始化器時,結構體可以自動接收一個逐一成員初始化器。不像默認初始化器,逐一成員初始化器可以為所有的存儲屬性賦值,這些存儲屬性有些可能沒有默認值。

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

值類型的初始化器委托

初始化器能夠調用其他的初始化器,來實現實例的部分初始化,這樣稱之為初始化器委托(Initializer delegation)這樣就可以避免在多個初始化器中存在重復的代碼了。

初始化器委托的工作規則,以及允許的委托形式,值類型和類類型存在些不同的地方。對于值類型(結構體和枚舉類型)來說,因為不支持繼承,所以它們的初始化器委托的過程相對簡單一些,因為他們只能委托他們自己的已經實現的初始化器。對于類來說,因為可能繼承了父類的屬性,所以初始化的過程需要保證所有的屬性都有合適的初始值。

對于值類型,你可以在自定義的初始化器中使用self.init來引用其他的初始化器,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)
    }
}
 

你可以采用三種方式來構造一個Rect實例。

let basicRect = Rect()
// basicRect's origin is (0.0, 0.0) and its size is (0.0, 0.0)”

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

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)

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)

類繼承和初始化

類的所有存儲屬性,包括從父類繼承的屬性,在初始化過程中都必須賦值。
Swift定義了兩種初始化器,確保所有的存儲屬性都有初始值,它們分別是指定初始化器(Designated Initializer)和便利初始化器(Convenience Initializer)。

指定初始化器和便利初始化器

指定初始化器是一個類最基本的初始化器。指定初始化器完全初始化所有的屬性,以及調用父類的初始化器來完成父類鏈的初始化工作。

類的指定構初始化器一般都比較少,一般一個類有一個指定初始化器的情況也是非常常見的。每一個類至少有一個指定初始化器,在一些情況下,可能還需要繼承父類的一個或者多個指定初始化器。

便利初始化器是次要的,你可以在便利初始化器里調用指定該類的初始化器。如果你的類中需要便利初始化器的時候你可以不定義初始化器。便利初始化器的目的是為了給常用的初始化模式提供捷徑節省時間,以及讓初始化過程更為清晰。

指定和便利初始化器的語法

指定初始化器的語法和值類型的簡單初始化器一樣:

init(parameters) {
    statements
}

對于便利初始化器則使用convenience:

convenience init(parameters) {
    statements
}

類類型的初始化委托 <span id="init_delegate">

為了簡化指定初始化器和便利初始化器之間的關系,Swift提供了以下的規則:

  • 規則1 指定初始化器必須調用直系父類的指定初始化器。
  • 規則2 便利初始化器必須調用該類的其他初始化器。
  • 規則3 便利初始化器最終需要調用指定初始化器。

一個簡單的口訣就是

  • 指定初始化器必須向上委托
  • 便利初始化器必須平行委托

如下圖所示:

圖中的父類有一個指定初始化器和兩個便利初始化器。便利初始化器調用便利初始化器,最終調用指定初始化器,所以滿足了規則2、3。因為父類沒有父類,所以規則1不用滿足。子類中有兩個指定初始化器,一個便利初始化器,其中便利初始化器調用了指定初始化器其,滿足規則2、3。指定初始化器都調用了父類的指定初始化器,所以滿足規則1。
以下這張圖則顯示了更為復雜的調用關系:

兩階段初始化

Swift中類的初始化分為兩個階段。第一個階段,為每一個存儲屬性分配初始值。一旦每個存儲屬性的初始狀態確定之后,第二階段開始。在新的實例被使用之前,每個類都有機會來進一步地自定義它的存儲屬性。

初始化過程使用兩個階段可以讓初始化安全,兩階段初始化可以防止屬性在初始化之前被訪問,而且可以防止屬性被其他的初始化器意外地修改。

Swift的編譯器會執行四個有用的安全檢查,確保兩階段的初始化無錯誤。

  • 安全檢查1<span id = "safe_check_1"></span>
    指定初始化器必須確保在向上委托父類初始化器之前,自己的所有存儲屬性都被初始化。
    一旦一個對象的所有存儲屬性的初始狀態確定下來之后,這個對象的內存就被認為是完全初始化了。所以,指定初始化器必須確保在處理父類鏈之前,它的所有存儲屬性是被初始化的。

  • 安全檢查2
    從父類繼承過來的屬性,必須在委托父類初始化器之后再為其賦值。否則的話,為這些屬性賦值之后,還是會被父類的初始化器所修改。

  • 安全檢查3
    便利初始化器為任何屬性賦值之前,必須調用其他初始化器。否則的話,為這些屬性賦值之后,還是會被其他初始化器所修改。

  • 安全檢查4
    在初始化過程第一階段完成之前,初始化器不能調用任何實例方法,不能讀取任何屬性的值,以及不能引用self 類的實例在第一階段完成之前并非完全有效的。只有當第一階段完成之后,屬性才能被訪問,方法才能被調用,類的實例才是有效的。

以下是基于四個安全檢查的兩階段初始化過程:
階段1

  • 一個指定初始化器或者便利初始化器被調用。
  • 為這個新實例申請內存,但是內存還沒被初始化。
  • 指定初始化器確保這個類的所有的存儲屬性都有值,那么這塊存儲屬性的內存就被初始化了。
  • 指定初始化器切換到父類的初始化器,執行相同的過程,確保父類的所有的存儲屬性都有值。
  • 沿著繼承鏈往上,切換成父類的初始化器,執行初始化過程,直至鏈的頂端。
  • 當到達繼承鏈頂端,而且最后一個父類的所有存儲屬性都有之后,那么這個實例的內存可以認為是完全初始化了。那么第一階段就完成了。

階段2

  • 從繼承鏈下來,每一個指定指定初始化器都有其他的選項來進一步定制實例,這時候初始化器就可以訪問self,修改屬性和調用實例方法等等。
  • 最后,繼承鏈上的所有的便利初始化器可以有選擇的定制實例和使用self來工作。

假設有一個子類繼承父類,其第一階段看起來如下圖。


在這個例子中,子類的便利初始化器被調用,這個初始化器目前不能修改任何的的屬性,它只能橫向委托指定初始化器。指定初始化器保證子類所有的存儲屬性都有值,正如安全檢查1中所述,子類完成自己的存儲屬性初始化之后,調用父類的指定初始化器完成父類存儲屬性的初始化。父類確保自己所有的存儲屬性都有值,因為沒有父類了,所以不用再委托初始化了。只要父類確保自己所有的存儲屬性都有值之后,它的內存可以認為是完全初始化了,第一階段就完成了。

第二階段看起來如下面的圖所示。

父類的指定初始化器現在有機會來進一步定制實例了(雖然這個是非必須的)。一旦父類的指定初始化器完成之后,子類的指定初始化器就可以執行額外的定制了(雖然這個也是非必須的)。一旦子類的指定初始化器完成之后,那么最開始調用的便利初始化器就可以執行額外的定制了。

初始化器的繼承和重寫

不像Objective-CSwift的子類并沒有默認繼承他們父類的初始化器。Swift的這種做法可以防止一種情況,就是一個更為特別的子類繼承了父類的一個簡單的初始化器,并且利用它來創建一個新的子類實例,創建的實例并沒有完成或者正確的初始化。

如果你想要子類保留和父類的相同初始化器的話,你只能在子類中自定義來實現這些初始化器了。

如果你寫的子類的初始化器和父類相同的話,那么你最好重寫父類的初始化器,用關鍵字override。甚至你在重寫自動生成的默認初始化器時,也是需要添加override的。

正如重寫的屬性、方法和下標一樣,override關鍵字會讓Swift去檢查父類是否有匹配的初始化器,驗證參數是否滿足要求。

當你重寫父類的指定初始化器時,你一般需要寫override關鍵字的,盡管你在子類實現的是便利初始化器。

相反,如果子類的初始化器和父類的便利初始化器相匹配的話,而正如類初始化委托那里所講,子類的初始化器無法直接調用父類的便利初始化器,所以這種情況下無法重寫父類的初始化器。所以說,當你子類的初始化器和父類的便利初始化器相匹配的時候,就能再使用override關鍵字了,因為并沒有重寫父類的初始化器。

以下是一個例子:

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

這里的父類Vehicle有兩個屬性,一個是存儲屬性,一個是計算屬性。而且存儲屬性有默認值,所以這個類有一個默認的初始化器,可以利用這個默認的初始化器來構造一個實例。

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

以下定義一個子類:

class Bicycle: Vehicle {
    override init() {
        super.init()
        numberOfWheels = 2
    }
}

子類Bicycle定義了一個自定義的初始化器init,因為和父類的默認初始化器相匹配,所以需要用關鍵override重寫。在初始化器中,因為子類沒有自己的存儲屬性,所以先調用了父類的初始化器確保父類的屬性都能被初始化,然后執行額外的操作定制自己的實例,讓繼承過來的屬性的值改為2

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

注:子類可以修改從父類繼承的存儲屬性,無法修改從父類繼承的常量屬性。

初始化器自動繼承

以上說到,子類在默認情況下是不會繼承父類的初始化器的。但是,在某些特定情況下,是可以自動繼承的。假設你已經為子類引入的存儲屬性賦值了,那么有以下兩個規則:

  • 規則1
    如果子類沒有自定義任何指定初始化器,那么子類將自動繼承父類所有的指定初始化器。

  • 規則2
    如果子類實現了父類的所有的指定初始化器,不管是由規則1自動繼承的,還是自定義實現的,那么子類將自動繼承父類所有的便利初始化器。
    即使子類添加了便利初始化器,這兩個規則也適用。

指定初始化器和便利初始化器示例

以下是一個關于指定初始化器、便利初始化器和初始化器自動繼承的例子。

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

Food是基類,含有一個存儲屬性name,另外有一個指定初始化器init(name:String)和一個便利初始化器init(),其中便利初始化器調用了指定初始化器。

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

RecipeIngredientFood的子類,它有自己的存儲屬性quantity,以及從父類繼承繼承過來的存儲屬性name,定義了兩個初始化器創建RecipeIngredient實例。

RecipeIngredient中定義了一個指定初始化器init(name: String, quantity: Int),這個初始化器首先初始化自己的存儲屬性quantity,然后向上委托父類初始化器來初始化父類的存儲屬性,這也滿足了安全檢查1
RecipeIngredient還定義了一個便利初始化器init(name: String),這個初始化器橫向調用了指定初始化器,可以使得創建實例更加便利和簡潔。另外,這個便利初始化器和父類的一個初始化器匹配,也就說這個便利初始化器需要override來重寫父類的初始化器。因為子類已經實現了父類的所有指定初始化器,所以自動繼承了父類的所有便利初始化器。
所以有三種方法創建實例

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

最后的一個子類如下:

class ShoppingListItem: RecipeIngredient {
    var purchased = false
    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 ?”

可失敗的初始化器

在某些情況下,一個類、結構體或者枚舉類型的的初始化過程可能失敗,造成失敗的原因可能是非法參數值、缺少外部資源或者其他因素。所以可失敗的初始化器在這些情況下還是挺有用的。可失敗初始化器意味著初始化過程可以失敗,但是返回一個nil值,所以在聲明初始化器的時候需要加一個問號?

注:你不能將一個初始化器同時聲明為可失敗的和不可失敗的,即這兩個初始化器具有相同的參數類型和名稱。

如下的例子:

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

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

注意,someCreature是一個可選類型的值。因為初始化過程可能失敗而返回一個nil。如果你傳入一個空字符串的話,則返回一個nil值。

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"

枚舉類型的可失敗初始化器

在構造枚舉類型實例的時候,可能根據輸入的一個或者多個參數來選擇合適的值。但是如果輸入的參數和已有的值不匹配的話,則可以返回一個nil。如以下的例子。

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

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.

具有原始值的枚舉類型的可失敗初始化器

具有原始值的枚舉類型本身就具有可失敗的初始化器init?(rawValue:),如果輸入的原始值匹配則輸出值,如果輸入的原始值不匹配的話就輸出nil

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.

初始化失敗的傳遞

一個類、結構體和枚舉類型的可失敗初始化器橫向可以調用自身的可失敗初始化器,向上可以委托父類的可失敗初始化器。如果你委托其他的初始化器造成初始化器過程失敗的話,則初始化器過程就戛然而止而不會再繼續下去了。

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

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

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

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

對于CartItem這個類來說,有兩個地方會導致初始化過程失敗。一是實例化時name的值為空串,二是實例化時quantity的值為0。

可失敗初始化器的重寫

你可以像其他初始化器一樣重寫可失敗初始化器。值得注意的是,你可以將父類的可失敗初始化器重寫為子類的不可失敗初始化器。盡管父類的初始化器是可失敗的,但是如果要重寫成不可失敗的初始化器的話,需要處理好失敗的情況。

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

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

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

注:如果對父類強制解包的話,可能會導致運行時錯誤,需要注意處理。

init!可失敗初始化器

你可以定義一個可選類型的可失敗初始化器,你也定義一個隱式可選類型的可失敗初始化器。將?變為!即可。

必需初始化器(Required Initializer)

在定義類的初始化器的時候用關鍵字required修飾的時候,說明每一個子類都必須實現這個初始化器。

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

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

注:你在子類中實現的那個必修初始化器必須加上關鍵字required,但是不用加override關鍵字。

使用閉包和函數設置屬性默認值

如果存儲屬性的默認值需要一些定制或者設置,可以使用閉包或者函數來實現。無論這個類型什么時候初始化,只要這個函數或者閉包被調用,那么他的返回值就是存儲屬性的默認值。函數或者閉包會產生一個臨時變量,然后將這個臨時變量賦給這個存儲屬性。以下是大致的骨架:

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

注意到這個閉包的末尾的花括號處有一對圓括號,也就說這個閉包會被立即執行,然后將執行結果返回給存儲屬性。如果沒有這對圓括號的話,就是將這個閉包賦給存儲屬性,顯然是不會被執行的。

因為類型的初始化還沒完成,所以在函數或者閉包中不可以訪問其他屬性,盡管這些屬性可能已經有默認值了,也不可以用self

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”
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,197評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,415評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,104評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,884評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,647評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,130評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,208評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,366評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,887評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,737評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,939評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,478評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,174評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,586評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,827評論 1 283
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,608評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,914評論 2 372

推薦閱讀更多精彩內容