初始化是為了使用某個類、結構體或枚舉類型的實例而進行的準備過程。這個過程包括為每個存儲的屬性設置一個初始值,然後執行新實例所需的任何其他設置或初始化。 初始化是通過定義構造器(Initializers)來實現的,這些構造器可以看做是用來創建特定類型實例的特殊方法。與 Objective-C 中的構造 ...
初始化是為了使用某個類、結構體或枚舉類型的實例而進行的準備過程。這個過程包括為每個存儲的屬性設置一個初始值,然後執行新實例所需的任何其他設置或初始化。
初始化是通過定義構造器(Initializers)來實現的,這些構造器可以看做是用來創建特定類型實例的特殊方法。與 Objective-C 中的構造器不同,Swift 的構造器無需返回值,它們的主要任務是保證新實例在第一次使用前完成正確的初始化。 類實例也可以通過定義析構器(deinitializer)在類實例釋放之前執行特定的清除工作。 存儲型屬性的初始化 類和結構體在實例創建時,必須為所有存儲型屬性設置合適的初始值。存儲型屬性的值不能處於一個未知的狀態。 你可以在構造器中為存儲型屬性賦初值,也可以在定義屬性時為其設置預設值。以下章節將詳細介紹這兩種方法。 註意:當你為存儲型屬性設置預設值或者在構造器中為其賦值時,它們的值是被直接設置的,不會觸發任何屬性觀測器(property observers)。 構造器 構造器在創建某特定類型的新實例時調用。它的最簡形式類似於一個不帶任何參數的實例方法,以init關鍵字命名。init() {
// perform some initialization here
}
下麵例子中定義了一個用來保存華氏溫度的結構體Fahrenheit,它擁有一個Double類型的存儲型屬性temperature:
struct Fahrenheit { var temperature: Double init() { temperature = 32.0 } } var f = Fahrenheit() println("The default temperature is \(f.temperature)° Fahrenheit") // 輸出 "The default temperature is 32.0° Fahrenheit”
這個結構體定義了一個不帶參數的構造器init,併在裡面將存儲型屬性temperature的值初始化為32.0(華攝氏度下水的冰點)。 預設屬性值 如前所述,你可以在構造器中為存儲型屬性設置初始值;同樣,你也可以在屬性聲明時為其設置預設值。 註意:如果一個屬性總是使用同一個初始值,可以為其設置一個預設值。無論定義預設值還是在構造器中賦值,最終它們實現的效果是一樣的,只不過預設值跟屬性構造過程結合的更緊密。使用預設值能讓你的構造器更簡潔、更清晰,且能通過預設值自動推導出屬性的類型;同時,它也能讓你充分利用預設構造器、構造器繼承等特性。 你可以使用更簡單的方式在定義結構體Fahrenheit時為屬性temperature設置預設值:
struct Fahrenheit { var temperature = 32.0 }
定製化構造過程 你可以通過輸入參數和可選屬性類型來定製構造過程,也可以在構造過程中修改常量屬性。 構造參數 你可以在定義構造器時提供構造參數,為其提供定製化構造所需值的類型和名字。構造器參數的功能和語法跟函數和方法參數相同。 下麵例子中定義了一個包含攝氏度溫度的結構體Celsius。它定義了兩個不同的構造器:init(fromFahrenheit:)和init(fromKelvin:),二者分別通過接受不同刻度表示的溫度值來創建新的實例
struct Celsius { var temperatureInCelsius: Double = 0.0 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”
第一個構造器擁有一個構造參數,其外部名字為fromFahrenheit,內部名字為fahrenheit;第二個構造器也擁有一個構造參數,其外部名字為fromKelvin,內部名字為kelvin。這兩個構造器都將唯一的參數值轉換成攝氏溫度值,並保存在屬性temperatureInCelsius中。 內部和外部參數名 跟函數和方法參數相同,構造參數也存在一個在構造器內部使用的參數名字和一個在調用構造器時使用的外部參數名字。 然而,構造器並不像函數和方法那樣在括弧前有一個可辨別的名字。所以在調用構造器時,主要通過構造器中的參數名和類型來確定需要調用的構造器。正因為參數如此重要,如果你在定義構造器時沒有提供參數的外部名字,Swift 會為每個構造器的參數自動生成一個跟內部名字相同的外部名,就相當於在每個構造參數之前加了一個哈希符號。 以下例子中定義了一個結構體Color,它包含了三個常量:red、green和blue。這些屬性可以存儲0.0到1.0之間的值,用來指示顏色中紅、綠、藍成分的含量。 Color提供了一個構造器,其中包含三個Double類型的構造參數
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) // 報編譯時錯誤,需要外部名稱
無外部名稱參數的構造器
如果你不希望為構造器的某個參數提供外部名字,你可以使用下劃線_來顯示描述它的外部名,以此覆蓋上面所說的預設行為。
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
可選屬性類型 如果你定製的類型包含一個邏輯上允許取值為空的存儲型屬性--不管是因為它無法在初始化時賦值,還是因為它可以在之後某個時間點可以賦值為空--你都需要將它定義為可選類型optional type。可選類型的屬性將自動初始化為空nil,表示這個屬性是故意在初始化時設置為空的。 下麵例子中定義了類SurveyQuestion,它包含一個可選字元串屬性response
class SurveyQuestion { var text: String var response: String? init(text: String) { self.text = text } func ask() { println(text) } } let cheeseQuestion = SurveyQuestion(text: "Do you like cheese?") cheeseQuestion.ask() // 輸出 "Do you like cheese?" cheeseQuestion.response = "Yes, I do like cheese.
調查問題在問題提出之後,我們才能得到回答。所以我們將屬性回答response聲明為String?類型,或者說是可選字元串類型optional String。當SurveyQuestion實例化時,它將自動賦值為空nil,表明暫時還不存在此字元串。 構造過程中常量屬性的賦值 只要在構造過程結束前常量的值能確定,你可以在構造過程中的任意時間點修改常量屬性的值。 註意:對某個類實例來說,它的常量屬性只能在定義它的類的構造過程中修改;不能在子類中修改。 你可以修改上面的SurveyQuestion示例,用常量屬性替代變數屬性text,指明問題內容text在其創建之後不會再被修改。儘管text屬性現在是常量,我們仍然可以在其類的構造器中修改它的值
class SurveyQuestion { let text: String var response: String? init(text: String) { self.text = text } func ask() { println(text) } } let beetsQuestion = SurveyQuestion(text: "How about beets?") beetsQuestion.ask() // 輸出 "How about beets?" beetsQuestion.response = "I also like beets. (But not with cheese.)
預設構造器 Swift 將為所有屬性已提供預設值的且自身沒有定義任何構造器的結構體或基類,提供一個預設的構造器。這個預設構造器將簡單的創建一個所有屬性值都設置為預設值的實例。 下麵例子中創建了一個類ShoppingListItem,它封裝了購物清單中的某一項的屬性:名字(name)、數量(quantity)和購買狀態 purchase state。
class ShoppingListItem {
var name: String?
var quantity = 1
var purchased = false
}
var item = ShoppingListItem()
由於ShoppingListItem類中的所有屬性都有預設值,且它是沒有父類的基類,它將自動獲得一個可以為所有屬性設置預設值的預設構造器(儘管代碼中沒有顯式為name屬性設置預設值,但由於name是可選字元串類型,它將預設設置為nil)。上面例子中使用預設構造器創造了一個ShoppingListItem類的實例(使用ShoppingListItem()形式的構造器語法),並將其賦值給變數item。 結構體的逐一成員構造器 除上面提到的預設構造器,如果結構體對所有存儲型屬性提供了預設值且自身沒有提供定製的構造器,它們能自動獲得一個逐一成員構造器。 逐一成員構造器是用來初始化結構體新實例里成員屬性的快捷方法。我們在調用逐一成員構造器時,通過與成員屬性名相同的參數名進行傳值來完成對成員屬性的初始賦值。 下麵例子中定義了一個結構體Size,它包含兩個屬性width和height。Swift 可以根據這兩個屬性的初始賦值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)
值類型的構造器代理 構造器可以通過調用其它構造器來完成實例的部分構造過程。這一過程稱為構造器代理,它能減少多個構造器間的代碼重覆。 構造器代理的實現規則和形式在值類型和類類型中有所不同。值類型(結構體和枚舉類型)不支持繼承,所以構造器代理的過程相對簡單,因為它們只能代理任務給本身提供的其它構造器。類則不同,它可以繼承自其它類(請參考繼承),這意味著類有責任保證其所有繼承的存儲型屬性在構造時也能正確的初始化。 對於值類型,你可以使用self.init在自定義的構造器中引用其它的屬於相同值類型的構造器。並且你只能在構造器內部調用self.init。 註意,如果你為某個值類型定義了一個定製的構造器,你將無法訪問到預設構造器(如果是結構體,則無法訪問逐一對象構造器)。這個限制可以防止你在為值類型定義了一個更複雜的,完成了重要準備構造器之後,別人還是錯誤的使用了那個自動生成的構造器。 註意:假如你想通過預設構造器、逐一對象構造器以及你自己定製的構造器為值類型創建實例,我們建議你將自己定製的構造器寫到擴展(extension)中,而不是跟值類型定義混在一起。 下麵例子將定義一個結構體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屬性;使用特定的origin和size實例來初始化;使用特定的center和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(),在功能上跟沒有自定義構造器時自動獲得的預設構造器是一樣的。這個構造器是一個空函數,使用一對大括弧{}來描述,它沒有執行任何定製的構造過程。調用這個構造器將返回一個Rect實例,它的origin和size屬性都使用定義時的預設值Point(x: 0.0, y: 0.0)和Size(width: 0.0, height: 0.0):
let basicRect = Rect() // basicRect 的原點是 (0.0, 0.0),尺寸是 (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 的原點是 (2.0, 2.0),尺寸是 (5.0, 5.0)
第三個Rect構造器init(center:size:)稍微複雜一點。它先通過center和size的值計算出origin的坐標。然後再調用(或代理給)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 的原點是 (2.5, 2.5),尺寸是 (3.0, 3.0) 構造器init(center:size:)可以自己將origin和size的新值賦值到對應的屬性中。然而儘量利用現有的構造器和它所提供的功能來實現init(center:size:)的功能,是更方便、更清晰和更直觀的方法。 註意:如果你想用另外一種不需要自己定義init()和init(origin:size:)的方式來實現這個例子,請參考擴展。 類的繼承和構造過程 類裡面的所有存儲型屬性--包括所有繼承自父類的屬性--都必須在構造過程中設置初始值。 Swift 提供了兩種類型的類構造器來確保所有類實例中存儲型屬性都能獲得初始值,它們分別是指定構造器和便利構造器。 指定構造器和便利構造器 指定構造器是類中最主要的構造器。一個指定構造器將初始化類中提供的所有屬性,並根據父類鏈往上調用父類的構造器來實現父類的初始化。 指定構造器實現
init(parameters) {
statements
}
便利構造器實現通過在init關鍵字前面加convenience關鍵字
convenience init(parameters) {
statements
}
每一個類都必須擁有至少一個指定構造器。在某些情況下,許多類通過繼承了父類中的指定構造器而滿足了這個條件。 便利構造器是類中比較次要的、輔助型的構造器。你可以定義便利構造器來調用同一個類中的指定構造器,併為其參數提供預設值。你也可以定義便利構造器來創建一個特殊用途或特定輸入的實例。 你應當只在必要的時候為類提供便利構造器,比方說某種情況下通過使用便利構造器來快捷調用某個指定構造器,能夠節省更多開發時間並讓類的構造過程更清晰。 構造器鏈 為了簡化指定構造器和便利構造器之間的調用關係,Swift 採用以下三條規則來限制構造器之間的代理調用: 規則 1 指定構造器必須調用其直接父類的的指定構造器。 規則 2 便利構造器必須調用同一類中定義的其它構造器。 規則 3 便利構造器必須最終以調用一個指定構造器結束。 一個更方便記憶的方法是: 指定構造器必須總是向上代理 便利構造器必須總是橫向代理 這些規則可以通過下麵圖例來說明:




class Food { var name: String init(name: String) { self.name = name } convenience init() { self.init(name: "[Unnamed]") } }
下圖中展示了Food的構造器鏈:

let namedMeat = Food(name: "Bacon") // namedMeat 的名字是 "Bacon”
Food類中的構造器init(name: String)被定義為一個指定構造器,因為它能確保所有新Food實例的中存儲型屬性都被初始化。Food類沒有父類,所以init(name: String)構造器不需要調用super.init()來完成構造。 Food類同樣提供了一個沒有參數的便利構造器 init()。這個init()構造器為新食物提供了一個預設的占位名字,通過代理調用同一類中定義的指定構造器init(name: String)並給參數name傳值[Unnamed]來實現:
let mysteryMeat = Food() // mysteryMeat 的名字是 [Unnamed]
類層級中的第二個類是Food的子類RecipeIngredient。RecipeIngredient類構建了食譜中的一味調味劑。它引入了Int類型的數量屬性quantity(以及從Food繼承過來的name屬性),並且定義了兩個構造器來創建RecipeIngredient實例
class RecipeIngredient: Food { var quantity: Int init(name: String, quantity: Int) { self.quantity = quantity super.init(name: name) } convenience init(name: String) { self.init(name: name, quantity: 1) } }
下圖中展示了RecipeIngredient類的構造器鏈:

let oneMysteryItem = RecipeIngredient()
let oneBacon = RecipeIngredient(name: "Bacon")
let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6)
類層級中第三個也是最後一個類是RecipeIngredient的子類,叫做ShoppingListItem。這個類構建了購物單中出現的某一種調味料。 購物單中的每一項總是從unpurchased未購買狀態開始的。為了展現這一事實,ShoppingListItem引入了一個布爾類型的屬性purchased,它的預設值是false。ShoppingListItem還添加了一個計算型屬性description,它提供了關於ShoppingListItem實例的一些文字描述
class ShoppingListItem: RecipeIngredient { var purchased = false var description: String { var output = "\(quantity) x \(name.lowercaseString)" output += purchased ? " ?" : " ?" return output } }
註意:ShoppingListItem沒有定義構造器來為purchased提供初始化值,這是因為任何添加到購物單的項的初始狀態總是未購買。 由於它為自己引入的所有屬性都提供了預設值,並且自己沒有定義任何構造器,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 { println(item.description) } // 1 x orange juice ? // 1 x bacon ? // 6 x eggs ?
如上所述,例子中通過字面量方式創建了一個新數組breakfastList,它包含了三個新的ShoppingListItem實例,因此數組的類型也能自動推導為ShoppingListItem[]。在數組創建完之後,數組中第一個ShoppingListItem實例的名字從[Unnamed]修改為Orange juice,並標記為已購買。接下來通過遍曆數組每個元素並列印它們的描述值,展示了所有項當前的預設狀態都已按照預期完成了賦值。 Failable Initializers 通過構造器初始化實際上是給class或struct,枚舉的每一個存儲屬性(參數)提供初始值,進行對象實例化的過程。在一些情況下,初始化的過程可能會因為參數錯誤是有可能失敗的。通過init?來聲明一個可失敗構造器 註意:你不能定義一個與可失敗構造器的參數和名稱相同的不可失敗構造器。 failable初始化創建的類型初始化一個可選值。返回nil表示初始化失敗可觸發的 使用可失敗構造器可極大程度的統一Swift中的構造對象語法,消除了構造器與工廠方法之間混亂、重覆的冗餘語法,使Swift更加簡潔。隨著可失敗構造器這一特性的加入,Swift將對大多數Cocoa中帶NSError參數的工廠初始化方法進行調整,從而加強Swift中構造對象語法的統一性,給開發者帶來更好的開發體驗。 通過閉包和函數來設置屬性的預設值 如果某個存儲型屬性的預設值需要特別的定製或準備,你就可以使用閉包或全局函數來為其屬性提供定製的預設值。每當某個屬性所屬的新類型實例創建時,對應的閉包或函數會被調用,而它們的返回值會當做預設值賦值給這個屬性。 這種類型的閉包或函數一般會創建一個跟屬性類型相同的臨時變數,然後修改它的值以滿足預期的初始狀態,最後將這個臨時變數的值作為屬性的預設值進行返回。 下麵列舉了閉包如何提供預設值的代碼概要:
class SomeClass { let someProperty: SomeType = { // 在這個閉包中給 someProperty 創建一個預設值 // someValue 必須和 SomeType 類型相同 return someValue }() }
註意閉包結尾的大括弧後面接了一對空的小括弧。這是用來告訴 Swift 需要立刻執行此閉包。如果你忽略了這對括弧,相當於是將閉包本身作為值賦值給了屬性,而不是將閉包的返回值賦值給屬性。 註意:如果你使用閉包來初始化屬性的值,請記住在閉包執行時,實例的其它部分都還沒有初始化。這意味著你不能夠在閉包里訪問其它的屬性,就算這個屬性有預設值也不允許。同樣,你也不能使用隱式的self屬性,或者調用其它的實例方法。 下麵例子中定義了一個結構體Checkerboard,它構建了西洋跳棋游戲的棋盤:

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 squareIsBlackAtRow(row: Int, column: Int) -> Bool {
return boardColors[(row * 8) + column]
}
}
每當一個新的Checkerboard實例創建時,對應的賦值閉包會執行,一系列顏色值會被計算出來作為預設值賦值給boardColors。上面例子中描述的閉包將計算出棋盤中每個格子合適的顏色,將這些顏色值保存到一個臨時數組temporaryBoard中,併在構建完成時將此數組作為閉包返回值返回。這個返回的值將保存到boardColors中,並可以通squareIsBlackAtRow這個工具函數來查詢。
let board = Chessboard()
print(board.squareIsBlackAtRow(0, column: 1))
// Prints "true"
print(board.squareIsBlackAtRow(7, column: 7))
// Prints "false”