一個類可以從另外一個類中繼承方法,屬性或者其它的一些特性。當一個類繼承于另外一個類時,這個繼承的類叫子類,被繼承的類叫父類。繼承是Swift中類區別于其它類型的一個基本特征。
Swift中的類可以調用父類的方法,使用父類的屬性和下標,還可以根據需要使用重寫方法或者屬性來重新定義和修改他們的一些特性。Swift可以幫助你檢查重寫的方法和父類的方法定義是相符的。
類還可以為它繼承的屬性添加觀察者,這樣可以能夠讓它在一個屬性變化的時候得到通知。屬性觀察者可以被添加給任何屬性,不管它之前是存儲屬性還是計算屬性。
1、定義一個基類
任何一個不繼承于其它類的類被稱作基類
注意:Swift的類不是從一個全局基類繼承而來。在你編寫代碼的時,只要是在類的定義中沒有繼承自父類的類都是基類。
下面的例子定義了一個叫Vehicle的基類。基類包含兩個所有交通工具通用的屬性numberOfWheels和maxPassengers。這兩個屬性被一個叫description的方法使用,通過返回一個String描述來作為這個交通工具的特征:
class Vehicle {
var numberOfWheels: Int
var maxPassengers: Int
func description() -> String {
return "/(numberOfWheels) wheels; up to /(maxPassengers) passengers"
}
init() {
numberOfWheels = 0
maxPassengers = 1
}
}
這個交通工具類Vehicle還定義了一個構造函數來設置它的屬性。構造函數更多的解釋在Initialization一章,但是為了說明子類如何修改繼承的屬性,這里需要簡要解釋一下什么叫構造函數。
通過構造函數可以創建一個類型的實例。盡管構造函數不是方法,但是它們在編碼的時候使用了非常相似的語法。構造函數通過確保所有實例的屬性都是有效的來創建一個新的實例。
構造函數最簡單的形式是使用init關鍵詞的一個類似方法的函數,并且沒有任何參數:
init() {
// perform some initialization here
}
使用構造函數語法TypeName和空的兩個小括號來完成一個Vehicle實例的創建:
let someVehicle = Vehicle()
Vehicle的構造函數為屬性設置了一些初始值(numberOfWheels = 0 然后 maxPassengers = 1)。
Vehicle類定義的是一個通用的交通工具特性,它本身沒有太多意義,所以就需要沖定義它的一些屬性或者方法來讓它具有實際的意義。
2、產生子類
產生子類就是根據一個已有的類產生新類的過程。子類繼承了父類的一些可以修改的特性。還可以為子類添加一些新的特性。
為了表明一個類是繼承自一個父類,需要將父類的名稱寫在子類的后面,并且用冒號分隔:
class SomeClass: SomeSuperclass {
// class definition goes here
}
下面的例子定義了一種特定叫Bicycle的交通工具。這個新類是基于已有的類Vehicle產生的。書寫方式是在類名Bicycle后加冒號加父類Vehicle名。
可以理解為:
定義一個新的類叫Bicycle,它繼承了Vehicle的特性:
class Bicycle: Vehicle {
init() {
super.init()
numberOfWheels = 2
}
}
Bicycle是Vehicle的子類,Vehicle是Bicycle的父類。Bicycle類繼承了Vehicle所有的特征,比如maxPassengers和numberOfWheels屬性。你還可以為Bicycle類添加心的屬性。
Bicycle類也定義了構造函數,在這個構造函數中調用了父類的構造函數super.init(),這樣可以確保在Bicycle修改他們之前,父類已經初始化了。
注意:跟Objective-C不同的是,Swift中的構造函數沒有默認繼承。更多信息可以參考Initializer Inheritance and Overriding這一章節。
maxPassengers屬性在繼承自父類的時候已經被初始化了,對于Bicycle來說是正確的,因此不需要再做更改。然后numberOfWheels是不對的,所以被替換成了2.
不僅屬性是繼承于Vehicle的,Bicycle還繼承了父類的方法。如果你創建一個實例,然后調用了已經繼承的description方法,可以得到該交通工具的描述并且看到它的屬性已經被修改:
let bicycle = Bicycle()
println("Bicycle: /(bicycle.description())")
// Bicycle: 2 wheels; up to 1 passengers
子類本身也可以作為父類被再次繼承:
class Tandem: Bicycle {
init() {
super.init()
maxPassengers = 2
}
}
上面的例子創建了Bicycle的子類,叫做tandem,也就可以兩個人一起騎的自行車。所以Tandem沒有修改numberOfWheels屬性,只是更新了maxPassengers屬性。
注意:子類只能夠在構造的時候修改變量的屬性,不能修改常量的屬性。
創建一個Tandem的實例,然后調用description方法檢查屬性是否被正確修改:
let tandem = Tandem()
println("Tandem: /(tandem.description())")
// Tandem: 2 wheels; up to 2 passengers
注意到description方法也被Tandem繼承了。
3、重寫方法
子類可以提供由父類繼承來的實例方法,類方法,實例屬性或者下標的個性化實現。這個特性被稱為重寫。
重寫一個由繼承而來的方法需要在方法定義前標注override關鍵詞。通過這樣的操作可以確保你所要修改的這個方法確實是繼承而來的,而不會出現重寫錯誤。錯誤的重寫會造成一些不可預知的錯誤,所以如果如果不標記override關鍵詞的話,就會被在代碼編譯時報錯。
override關鍵詞還能夠讓Swift編譯器檢查該類的父類是否有相符的方法,以確保你的重寫是可用的,正確的。
訪問父類方法,屬性和下標
當在重寫子類繼承自父類的方法,屬性或者下標的時候,需要用到一部分父類已有的實現。比如你可以重定義已知的一個實現或者在繼承的變量中存儲一個修改的值。
適當的時候,可以通過使用super前綴來訪問父類的方法,屬性或者下標:
叫someMethod的重寫方法可以在實現的時候通過super.someMethod()調用父類的someMethod方法。
叫someProperty的重寫屬性可以在重寫實現getter或者setter的時候通過super.someProperty調用父類的someProperty。
叫someIndex的重寫下標可以在實現下標的時候通過super[someIndex]來訪問父類的下標。
復寫方法
你可以在你的子類中實現定制的繼承于父類的實例方法或者類方法。
下面的例子演示的就是一個叫Car的Vehicle子類,重寫了繼承自Vehicle的description方法。
class Car: Vehicle {
var speed: Double = 0.0
init() {
super.init()
maxPassengers = 5
numberOfWheels = 4
}
override func description() -> String {
return super.description() + "; "
+ "traveling at /(speed) mph"
}
}
Car中定義了一個新的Double類型的存儲屬性speed。這個屬性默認值是0.0,意思是每小時0英里。Car還有一個自定義的構造函數,設置了最大乘客數為5,輪子數量是4.
Car重寫了繼承的description方法,并在方法名description前標注了override關鍵詞。
在description中并沒有給出了一個全新的描述實現,還是通過super.description使用了Vehicle提供的部分描述語句,然后加上了自己定義的一些屬性,如當前速度。
如果你創建一個Car的實例,然后調用description方法,會發現描述語句變成了這樣:
let car = Car()
println("Car: /(car.description())")
// Car: 4 wheels; up to 5 passengers; traveling at 0.0 mph
復寫屬性
你還可以提供繼承自父類的實例屬性或者類屬性的個性化getter和setter方法,或者是添加屬性觀察者來實現重寫的屬性可以觀察到繼承屬性的變動。
重寫屬性的Getters和Setters
不管在源類中繼承的這個屬性是存儲屬性還是計算屬性,你都可以提供一個定制的getter或者setter方法來重寫這個繼承屬性。子類一般不會知道這個繼承的屬性本來是存儲屬性還是計算屬性,但是它知道這個屬性有特定的名字和類型。在重寫的時候需要指明屬性的類型和名字,好讓編譯器可以檢查你的重寫是否與父類的屬性相符。
你可以將一個只讀的屬性通過提那家getter和setter繼承為可讀寫的,但是反之不可。
注意:如果你為一個重寫屬性提供了setter方法,那么也需要提供getter方法。如果你不想在getter中修改繼承的屬性的值,可以在getter中使用super.someProperty即可,在下面SpeedLimitedCar例子中也是這樣。
下面的例子定義了一個新類SpeedLimitedCar,是Car的一個子類。這個類表示一個顯示在40碼一下的車輛。通過重寫繼承的speed屬性來實現:
class SpeedLimitedCar: Car {
override var speed: Double {
get {
return super.speed
}
set {
super.speed = min(newValue, 40.0)
}
}
}
每當你要設置speed屬性的時候,setter都會檢查新值是否比40大,二者中較小的值會被設置給SpeedLimitedCar。
如果你嘗試為speed設置超過40的值,description的輸出依然還是40:
let limitedCar = SpeedLimitedCar()
limitedCar.speed = 60.0
println("SpeedLimitedCar: /(limitedCar.description())")
// SpeedLimitedCar: 4 wheels; up to 5 passengers; traveling at 40.0 mph
重寫屬性觀察者
你可以使用屬性重寫為繼承的屬性添加觀察者。這種做法可以讓你無論這個屬性之前是如何實現的,在繼承的這個屬性變化的時候都能得到提醒。更多相關的信息可以參考Property Observers這章。
注意:不能為繼承的常量存儲屬性或者是只讀計算屬性添加觀察者。這些屬性值是不能被修改的,因此不適合在重寫實現時添加willSet或者didSet方法。
注意:不能同時定義重寫setter和重寫屬性觀察者,如果想要觀察屬性值的變化,并且又為該屬性給出了定制的setter,那只需要在setter中直接獲得屬性值的變化就行了。
下面的代碼演示的是一個新類AutomaticCar,也是Car的一個子類。這個類表明一個擁有自動變速箱的汽車,可以根據現在的速度自動選擇檔位,并在description中輸出當前檔位:
class AutomaticCar: Car {
var gear = 1
override var speed: Double {
didSet {
gear = Int(speed / 10.0) + 1
}
}
override func description() -> String {
return super.description() + " in gear /(gear)"
}
}
這樣就可以實現,每次你設置speed的值的時候,didSet方法都會被調用,來看檔位是否需要變化。gear是由speed除以10加1計算得來,所以當速度為35的時候,gear檔位為4:
let automatic = AutomaticCar()
automatic.speed = 35.0
println("AutomaticCar: /(automatic.description())")
// AutomaticCar: 4 wheels; up to 5 passengers; traveling at 35.0 mph in gear 4
4、禁止重寫
你可以通過標記final關鍵詞來禁止重寫一個類的方法,屬性或者下標。在定義的關鍵詞前面標注@final屬性即可。
在子類中任何嘗試重寫父類的final方法,屬性或者下標的行為都會在編譯時報錯。同樣在擴展中為類添加的方法,屬性或者下標也可以被標記為final。
還可以在類關鍵詞class前使用@final標記一整個類為final(@final class)。任何子類嘗試繼承這個父類時都會在編譯時報錯。