初始化是準備類箱锐、街固體比勉、枚舉類型的實例的過程,包括給存儲屬性賦初值以及其他任何必要的設置和初始化驹止。開發(fā)者通過定義構(gòu)造器或者叫構(gòu)造函數(shù)(Initializers)來實現(xiàn)初始化浩聋。通過調(diào)用構(gòu)造函數(shù)來創(chuàng)建實例。swift的構(gòu)造函數(shù)沒有返回值臊恋,他們的主要作用是保證實例在第一次使用之前被正確的初始化衣洁。
類還可以定義一個析構(gòu)函數(shù)或者叫析構(gòu)器(Deinitializers)。實例銷毀之前抖仅,系統(tǒng)會自動調(diào)用類的析構(gòu)函數(shù)來做必要的清尾工作坊夫。
初始化(Initialization)
設定存儲屬性初值(Setting Initial Value for Stored Properties)
類和結(jié)構(gòu)體必須(must)在實例創(chuàng)建之前給所有存儲屬性設置初值,存儲屬性不能處于不確定的狀態(tài)。設定初值的方法有兩個:構(gòu)造函數(shù)和給定默認值撤卢。不管采用哪種方式环凿,這些屬性的初始值都是被直接設定的,不會調(diào)用任何屬性觀察者放吩。
構(gòu)造函數(shù)/初始化器(Initializers)
struct Fahrenheit {
var temperature : Double
init(temperature : Double) {
self.temperature = temperature
}
init() {
temperature = 32.0
}
}
默認屬性值(Default Property Value)
struct Fahrenheit {
var temperature : Double = 32.0
}
Customizing Initialization
開發(fā)者可以通過在初始化過程中傳入?yún)?shù)和可選屬性或者設定能夠?qū)傩灾禐槌?shù)來定制實例的初始化智听。
傳參初始化(Initialization Parameters)
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
}
}
Parameters Names and Arguement Label
Parameters Names : 在函數(shù)體里面使用
Arguement Label :調(diào)用函數(shù)是使用
由于構(gòu)造函數(shù)并沒有一個函數(shù)名(init是關鍵字),而一個類型可以有多個構(gòu)造函數(shù),創(chuàng)建實例的時候傳入?yún)?shù)的類型和名稱在構(gòu)造函數(shù)的選擇上起重要作用渡紫,所以如果開發(fā)者自己沒有設定Arguement Label到推,swift為構(gòu)造函數(shù)的每一個參數(shù)提供了一個默認的Arguement Label,與Parameters Names完全相同惕澎。
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) {
blue = white
green = white
red = white
}
}
let magenta = Color(red: 1.0, green: 0, blue: 1.0)
let halfGray = Color(white: 0.5)
Initializer Parameters without Argument Labels
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)
Optional Property Types
如果開發(fā)者自定義的類型的某個存儲屬性在邏輯及上允許為"no value"---可能原因之一初始化過程中無法賦值(其他依賴莉测?)或者在未來的某個時間點這個屬性可能為"no value"---定義這個屬性為可選類型∵蠛恚可選類型的屬性自動初始化為nil捣卤,表明這個屬性"no value yet"忍抽。
class SurverQuestion {
var text: String
var response: String?
init(text: String) {
self.text = text
}
func ask() {
print(text)
}
}
let cheeseQuestion = SurverQuestion(text: "Do you like cheese?")
cheeseQuestion.ask()
cheeseQuestion.response = "Yes, I do like cheese."
Assigning Constant Properties During Initialization
可以在初始化過程中給常量屬性設定初值,初始化結(jié)束長兩屬性就會有一個明確的值了腌零,后續(xù)都不可更改了梯找。
class SurverQuestion {
let text: String
var response: String?
init(text: String) {
self.text = text
}
func ask() {
print(text)
}
}
let beetsQuestion = SurverQuestion(text: "How about beets?")
beetsQuestion.ask()
beetsQuestion.response = "I also like cheese.(But not with cheese.)"
默認構(gòu)造函數(shù)(默認構(gòu)造器)
如果開發(fā)者沒有提供構(gòu)造函數(shù),swift會為類和結(jié)構(gòu)體提供一個默認構(gòu)造函數(shù)益涧。調(diào)用默認構(gòu)造函數(shù)會創(chuàng)建一個實例锈锤,這個實例的所有屬性值都是默認值。
class ShoppingListItem {
var name : String?
var quantity = 1
var purchased = false
}
var item = ShoppingListItem()
struct Point {
var x = 10
var y = 20
}
let point = Point()
print("Item : \(String(describing: item.name)),\(item.quantity),\(item.purchased)")
print("The coordinate of point is (\(point.x),\(point.y)).")
運行結(jié)果:
Item : nil,1,false
The coordinate of point is (10,20)
Memberwise Initializers for Structure Types
swift還會為結(jié)構(gòu)體提供一個默認的逐個成員初始化的構(gòu)造函數(shù)(Memberwise Initializers)闲询。結(jié)構(gòu)體所有成員屬性都有默認值的時候可有調(diào)用默認構(gòu)造函數(shù)或者逐個屬性初始化的構(gòu)造函數(shù)久免;否則在不提供構(gòu)造函數(shù)的情況下,會調(diào)用這這個構(gòu)造函數(shù)去逐個初始化屬性扭弧。
struct Point {
var x : Int
var y : Int
}
var point = Point(x: 10, y: 30)
Initializers Delegation for Value Types
構(gòu)造函數(shù)可以調(diào)用其他類的構(gòu)造函數(shù)完成實例的部分初始化阎姥,這個過程稱作構(gòu)造器代理,避免了多個構(gòu)造器中出現(xiàn)重復代碼鸽捻。值類型不支持繼承呼巴,所以代理過程性對簡單。在實現(xiàn)自己的構(gòu)造函數(shù)時御蒲,使用self.init去調(diào)用其他的構(gòu)造函數(shù)衣赶。注意:self.init只能在構(gòu)造函數(shù)中使用。
對于值類型厚满,如果開發(fā)者自己定義了構(gòu)造函數(shù)府瞄,那么默認構(gòu)造函數(shù)和逐個屬性初始化的構(gòu)造函數(shù)就不會再被調(diào)用用來創(chuàng)建實例了,這個限制機制預防了這樣一種情況:本來有一部分重要的工作要在自定義的構(gòu)造函數(shù)完成碘箍,創(chuàng)建的實例的時候卻錯誤的調(diào)用了系統(tǒng)自動產(chǎn)生的構(gòu)造函數(shù)遵馆。當然如果你想在自定義構(gòu)造函數(shù)的情況下還想使用系統(tǒng)產(chǎn)生的構(gòu)造函數(shù)调缨,解決方法:在extension中實現(xiàn)自己的構(gòu)造函數(shù)蠢箩。
struct Point {
var x = 0.0
var y = 0.0
}
struct Size {
var width = 0.0
var height = 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
//Initializer Delegation
self.init(origin: Point(x: originX, y: originY), size: size)
}
func toString() -> String {
return "Rect : origin = (\(origin.x),\(origin.y)), size = (width : \(size.width), height : \(size.height))"
}
}
let basicRect = Rect()
print(basicRect.toString())
let originRect = Rect(origin: Point(x: 2.0, y: 2.0), size: Size(width: 5.0, height: 5.0))
print(originRect.toString())
let centerRect = Rect(center: Point(x: 4.0, y: 4.0), size: Size(width: 3.0, height: 3.0))
print(centerRect.toString())
運行結(jié)果:
Rect : origin = (0.0,0.0), size = (width : 0.0, height : 0.0)
Rect : origin = (2.0,2.0), size = (width : 5.0, height : 5.0)
Rect : origin = (2.5,2.5), size = (width : 3.0, height : 3.0)
類的繼承和初始化(Class Inheritance and Initialization)
類的所有存儲屬性包括繼承自父類的在初始化過程中都必須(must)設定初值惑芭。Swift為了確保初始化過程正確完成柳畔,定義了兩種構(gòu)造函數(shù)(構(gòu)造器):指定構(gòu)造器(Designated Initializer)和便利構(gòu)造器(Convenience Initializer)。
指定構(gòu)造器和便利構(gòu)造器
指定構(gòu)造器是類的最重要的構(gòu)造器妓灌,可以定義一個或者多個攒至。指定構(gòu)造器必須完整的初始化所有實例存儲屬性(自定義的屬性和從父類繼承來的屬性)堕汞。指定構(gòu)造器可以通過父類鏈調(diào)用恰當?shù)母割惖臉?gòu)造器初始化繼承來的屬性峻黍,還必須對本類自定義的實例存儲屬性進行初始化复隆。
便利構(gòu)造器是次要的拨匆、輔助性的構(gòu)造器姆涩,類里面可以不定義遍歷構(gòu)造器,便利構(gòu)造器必須調(diào)用其他構(gòu)造器來完成初始化惭每。
類的構(gòu)造器代理(Initializer Delegation of Class Types)
swift使用下面三條規(guī)則來簡化便利構(gòu)造器和指定構(gòu)造器的調(diào)用關系:
- 指定構(gòu)造器必須調(diào)用直接父類的指定構(gòu)造器(如果有直接父類)骨饿。
- 便利構(gòu)造器必須調(diào)用同一個類中的其他構(gòu)造器亏栈。
- 便利構(gòu)造器調(diào)用的構(gòu)造器鏈的最終節(jié)點必須是指定構(gòu)造器。
簡化為:
- 指定構(gòu)造器必須向上代理(調(diào)用父類指定構(gòu)造器)宏赘。
- 便利構(gòu)造器必須橫向代理(調(diào)用當前類的其他構(gòu)造器绒北,調(diào)用的終點是指定構(gòu)造器)。
構(gòu)造器中調(diào)用其他構(gòu)造器來執(zhí)行構(gòu)造的過程叫做構(gòu)造器代理察署。
兩段式構(gòu)造(Two-Phase Initialization)
swift中的類的構(gòu)造需要兩個階段完成:
- 初始化存儲屬性闷游。使用本類的構(gòu)造器初始化本類定義的存儲屬性,沿著構(gòu)造器鏈向上逐個調(diào)用父類的構(gòu)造器初始化父類的存儲屬性贴汪。
- 從最頂層父類開始脐往,沿著頂部構(gòu)造器鏈向下,每個構(gòu)造器可以再次修改存儲屬性扳埂。
詳細過程如下:
第一階段:
- 調(diào)用子類的某個構(gòu)造器
- 為實例分配內(nèi)存业簿,此時實例的內(nèi)存還沒與被初始化
- 指定構(gòu)造器確保子類所有的存儲屬性都已經(jīng)初始化完成
- 指定構(gòu)造器調(diào)用父類的構(gòu)造器,完成父類定義的的存儲屬性的初始化
- 沿著繼承樹上溯阳懂,即沿著調(diào)用父類構(gòu)造器的構(gòu)造器鏈一直向上執(zhí)行梅尤,知道到達構(gòu)造器鏈的最頂部。到達最頂部時岩调,各個父類指定構(gòu)造器完成各自定義的存儲屬性的初始化
第二階段:
- 沿著繼承樹向下巷燥,構(gòu)造器鏈中的構(gòu)造器有機會進一步定制實例,構(gòu)造器此時可以修改實例屬性誊辉,訪問self矾湃,甚至可以調(diào)用實例方法。
- 最后構(gòu)造器鏈中的便利構(gòu)造器有機會定制實例和使用self堕澄。
兩段式構(gòu)造使得構(gòu)造過程更加安全邀跃,同時讓整個類層次中的類獲得了完全的控制權。兩段式構(gòu)造可以防止屬性初始化之前被訪問蛙紫,也可以防止屬性被另一個構(gòu)造器意外的賦予不同的值拍屑。
為保證兩段式構(gòu)造的順利完成,swift提供了四項安全檢查:
- 指定構(gòu)造器必須先初始化當前類中的定義的存儲屬性坑傅,在向上調(diào)用父類構(gòu)造器僵驰。
- 指定構(gòu)造器必須先向上調(diào)用父類的構(gòu)造器,才能對繼承得到的屬性賦值唁毒。原因:如果先賦值再調(diào)用父類構(gòu)造器蒜茴,那么父類構(gòu)造器會把剛剛指定構(gòu)造器賦的值覆蓋掉。
- 便利構(gòu)造器必須先調(diào)用同一個類的其他構(gòu)造器浆西,然后才能對屬性賦值粉私。原因:先對屬性賦值再調(diào)用其他構(gòu)造器,那么其他構(gòu)造器會把便利構(gòu)造器剛剛的賦值覆蓋掉近零。(self.init()必須位于屬性賦值诺核、屬性訪問代碼之前抄肖。)
- 構(gòu)造器在第一階段完成之前不能調(diào)用實例方法,不能讀取實例屬性窖杀。
class Fruit {
var name : String
var weight : Double
//Designated Initializer
init(name: String, weight : Double) {
self.name = name
self.weight = weight
}
//Convenience Initializer
//便利構(gòu)造器必須調(diào)用指定構(gòu)造器
convenience init(name : String){
self.init(name: name, weight: 0.0)
}
//Convenience Initializer
//必須先調(diào)用指定構(gòu)造器再設定屬性值(Safety check)
convenience init() {
self.init(name: "Fruit")
self.weight = 1.0
}
}
class Apple : Fruit {
var color : String
//指定構(gòu)造器必須先初始化自定義的屬性漓摩,在向上調(diào)用父類的指定構(gòu)造器
init(name : String,weight:Double,color:String){
self.color = color
super.init(name: name, weight: weight)
}
convenience init(name: String,color: String){
self.init(name: name, weight: 1.0, color: color)
}
}
構(gòu)造器的繼承和重寫(Initializer Inheritance and Overriding)
默認情況Swift的子類不會繼承父類的構(gòu)造器。只有滿足以下條件入客,子類才會自動繼承父類構(gòu)造器:
- 子類沒有提供任何指定構(gòu)造器管毙,則自動繼承父類所有的子類構(gòu)造器
- 子類實現(xiàn)了父類所有的指定構(gòu)造器,即子類繼承或者重寫了父類所有的指定構(gòu)造器桌硫,那么子類自動繼承父類的便利構(gòu)造器锅风。
如果子類中定義的構(gòu)造器和父類的指定構(gòu)造器形參列表、外部形參名相同鞍泉,即認為重寫了父類的構(gòu)造器皱埠。相反的,如果子類構(gòu)造器和父類的便利構(gòu)造器的形參列表咖驮、外部參數(shù)名相同边器,由于子類中永遠不可能直接調(diào)用父類的便利構(gòu)造器,因此不算構(gòu)造器重寫托修。
class Fruit {
var name : String
var weight : Double
//Designated Initializer
init(name: String, weight : Double) {
self.name = name
self.weight = weight
}
init() {
self.name = "Unnamed"
self.weight = 0.0
}
//Convenience Initializer
convenience init(name : String){
self.init(name: name, weight: 0.0)
}
}
//提供了自己的指定構(gòu)造器忘巧,不滿足規(guī)則1,所以不會繼承父類的構(gòu)造器
class Apple : Fruit {
var color : String
init(name : String,weight:Double,color:String){
self.color = color
super.init(name: name, weight: weight)
}
//重寫父類的指定構(gòu)造器
override init(name: String, weight: Double) {
color = "defaultColor"
super.init(name: name, weight: weight)
}
//與父類便利構(gòu)造器參數(shù)相同睦刃,但不是重寫
init(name: String) {
color = "defaultColor"
super.init(name: name, weight: 0.0)
}
convenience init(name: String,color: String){
self.init(name: name, weight: 1.0, color: color)
}
//通過便利構(gòu)造器重寫父類構(gòu)造器
override convenience init() {
self.init(name:"Unnamed")
}
}
//沒有定義任何構(gòu)造器砚嘴,所以繼承了父類的所有指定構(gòu)造器,進而繼承了父類的所有便利構(gòu)造器
class Fuji : Apple {
var vitamin : Double = 0.21
}
可能失敗的構(gòu)造器(Failable Initializers)
某些情況下可能會出現(xiàn)初始化失敗的情況:提供了無效的參數(shù)值涩拙,外部資源的缺失等等际长。比如:
let wholeNumber: Double = 12345.0
let pi = 3.14159
if let valueMaintained = Int(exactly: wholeNumber) {
print("\(wholeNumber) connversion to Int maintains value of \(valueMaintained).")
}
let valueChanged = Int(exactly: pi)
if nil == valueChanged {
print("\(pi) conversion to Int does not maintain value.")
}
解決方法是提供可能失敗的構(gòu)造器(Failable Initializers).
class Animal {
var name: String
init?(name: String) {
if name.isEmpty {
return nil
}
self.name = name
}
}
enum TemperatureUnit {
case kelvin, celcius, fahrenheit
init?(symbol: Character) {
switch symbol {
case "K":
self = .kelvin
case "C":
self = .celcius
case "F":
self = .fahrenheit
default:
return nil
}
}
}
//Enumetations with raw values automatically receive a failable initializer
enum TemperatureUint2 : Character {
case kelvin = "K"
case celcius = "C"
case fahrenheit = "F"
}
可能失敗構(gòu)造器的傳播(Propagation of Initialization Failure)
類、結(jié)構(gòu)體和枚舉的構(gòu)造器可以橫向調(diào)用該類兴泥、結(jié)構(gòu)體或者枚舉的另一個可能失敗的構(gòu)造器工育;類似的,子類的可能失敗構(gòu)造器也可以向上調(diào)用父類的可能失敗構(gòu)造器搓彻。并且可能失敗的構(gòu)造器和一般的構(gòu)造器之間也可以相互調(diào)用如绸。由于可能失敗的構(gòu)造器會進行傳播,因此可能失敗的構(gòu)造器在構(gòu)造過程失敗之后旭贬,構(gòu)造失敗行為會立即阻止原構(gòu)造器代碼繼續(xù)執(zhí)行(因為返回了nil)怔接。
class Product {
let name: String
init?(name: String) {
print("Start create Product!")
if name.isEmpty {
print("Failure to create Product because of empty name!")
return nil
}
self.name = name
}
}
class CartItem: Product {
let quantity: Int
init?(name: String, quantity: Int) {
print("Start create CartItem!")
if quantity < 1 {
print("Failure to create CartItem because of quantity!")
return nil
}
self.quantity = quantity
super.init(name: name)
}
}
let twoSock = CartItem(name: "Sock", quantity: 2)
if nil != twoSock {
print("Succeed to create twoSock!")
}
let zeroSock = CartItem(name: "Sock", quantity: 0)
if nil != zeroSock {
print("Succeed to create zeroSock!")
}
let unnamedSock = CartItem(name: "", quantity: 4)
if nil != zeroSock {
print("Succeed to create unnamedSock!")
}
運行結(jié)果:
Start create CartItem!
Start create Product!
Succeed to create twoSock!
Start create CartItem!
Failure to create CartItem because of quantity!
Start create CartItem!
Start create Product!
Failure to create Product because of empty name!
重寫可能失敗的構(gòu)造器(Overriding a Failable Initializer)
子類可以重寫父類中能夠可能失敗的構(gòu)造器,可以通過可能失敗的構(gòu)造器重寫父類可能失敗的構(gòu)造器稀轨,也可以通過一般的構(gòu)造器重寫父類可能失敗的構(gòu)造器-----保證被重寫后的構(gòu)造器一定成功扼脐。
需要注意:
- 子類的普通構(gòu)造器向上調(diào)用父類的可能失敗的構(gòu)造器,需要進行解析靶端,要么強制解析要么隱式解析谎势。
- 普通構(gòu)造器調(diào)用自身的可能失敗構(gòu)造器,也需要解析杨名。
class Document {
var name: String?
init() {
}
init?(name: String) {
if name.isEmpty {
return nil
}
self.name = name
}
}
class AutomaticallyNamedDocument: Document {
override init() {
super.init()
self.name = "Unnamed"
}
//一般構(gòu)造器重寫可能失敗構(gòu)造器脏榆,
override init(name: String) {
super.init()
if name.isEmpty {
self.name = "Unnamed"
} else {
self.name = name
}
}
}
class UntitledDocument: Document {
//一般構(gòu)造器調(diào)用可能失敗構(gòu)造器,需要解析
override init() {
super.init(name: "Untitled")!
}
}
子類必須實現(xiàn)的構(gòu)造器(Required Initializer)
Swift允許在構(gòu)造器前面加上required關鍵字台谍,表明所有子類必須實現(xiàn)這個構(gòu)造器须喂。具體是繼承還是自己編碼實現(xiàn),是指定構(gòu)造器還是便利構(gòu)造器趁蕊,swift不做明確要求坞生。
class Fruit {
var name: String
var weight: Double
//Required Designated Initializer
required init(name: String){
self.name = name
self.weight = 0.0
}
//Required Convenience Initializer
required convenience init(name: String, weight: Double) {
self.init(name: name)
self.weight = weight
}
}
class Apple: Fruit {
var color: String
//Implement Required Designated Initializer
required init(name: String) {
self.color = "Red"
super.init(name: name)
}
init(color: String) {
self.color = color
super.init(name: "")
}
//Implement Convenience Designated Initializer
required convenience init(name: String, weight: Double) {
self.init(color:"Red")
super.name = name
}
}
//Receive Required Initializers by Inheritance
class Grape: Apple {
var sugarRate: Double = 0.45
}
Setting a Default Property Value with a Clousure or Function
存儲屬性的默認值需要一系列的配置和計算的時候,可以使用全局函數(shù)或者閉包掷伙。創(chuàng)建實例的時候是己,閉包或者函數(shù)就會被調(diào)用,返回值作為該存儲屬性的默認值任柜。
struct ChessBoard {
var 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
}()
}
析構(gòu)(Deinitialization)
類的實例被釋的時候會立即調(diào)用析構(gòu)函數(shù)或者析構(gòu)器卒废。注意:只針對類。
在類的實例被銷毀之前宙地,程序需要釋放一些物理資源(并非內(nèi)存摔认,內(nèi)存由系統(tǒng)自己釋放),比如打開的文件和網(wǎng)絡連接等宅粥。這個工作可以在析構(gòu)函數(shù)(析構(gòu)器)中完成参袱。析構(gòu)器是一個名為dinit的函數(shù),無參數(shù)無返回值甚至沒有圓括號秽梅。析構(gòu)器由系統(tǒng)自己調(diào)用抹蚀,開發(fā)者不能手動調(diào)用。子類默認繼承父類的析構(gòu)器企垦。如果子類自己實現(xiàn)了析構(gòu)器况鸣,那么子類析構(gòu)器執(zhí)行結(jié)束時調(diào)用父類析構(gòu)器≈窆郏總之镐捧,子類析構(gòu)器一定會調(diào)用父類的析構(gòu)器。
由于析構(gòu)器被調(diào)用完成臭增,實例才被銷毀懂酱,因此可以在析構(gòu)器里面訪問類的存儲屬性或者根據(jù)這些屬性來關閉資源。
class Birds{
var sing = "sing"
deinit {
print("bird is to be deallocated!")
if "sing" == self.sing {
self.sing = "stop"
}
}
}
class Chick: Birds {
var run = "run"
deinit {
print("chick is to be deallocated!")
if self.run == "run" {
self.run = "stop"
}
}
}
var chick : Chick? = Chick()
chick = nil
運行結(jié)果:
chick is to be deallocated!
bird is to be deallocated!