繼承
類可以繼承另一個類的方法、屬性以及其他某些特征。當 A 類繼承 B 類時,A 類為子類,B 類為父類。在 Swift 中,『繼承』是類(class)區(qū)別于其他類型的基本特性。
Swift 中,類能夠調(diào)用和訪問它的父類的方法、屬性和下標,也能夠?qū)@些方法、屬性和下標進行重寫,從而優(yōu)化或改變它們。Swift 通過檢查重寫定義是否有對應(yīng)的父類定義來確保我們的重寫的正確性。
類還能夠通過增加屬性觀察器來繼承屬性,當這個屬性變化時會得到通知。屬性觀察器能夠被添加到任何屬性上,無論這個屬性是存儲屬性還是計算屬性。
定義基類
任何沒有繼承其他類的類,都被稱作基類。
注意:Swift 中的類并非繼承自一個統(tǒng)一的基類。定義一個類時如果不指定父類,
則這個類自動變?yōu)榛悺?
下面的例子定義了一個名為 Vehicle 的基類。這個基類有一個叫做 currentSpeed 的存儲屬性,它的默認值為 0.0(推斷類型為 Double)。currentSpeed 屬性的值被一個叫做 description 的只讀 String 類型計算屬性使用,用來創(chuàng)建車輛的描述。
Vehicle 基類還定義了一個叫做 makeNoise 的方法,這個方法目前不做任何事情,之后會被 Vehicle 的子類重寫。
class Vehicle {
var currentSpeed = 0.0
var description: String {
return "traveling at \(currentSpeed) miles per hour"
}
func makeNoise() {
// 什么都不做 - 不是所有車輛都發(fā)出噪音
}
}
通過初始化語法,類名后加一對圓括號,我們創(chuàng)建了 Vehicle 類的新實例:
let someVehicle = Vehicle()
這之后,我們就可以用 someVehicle 實例來訪問 description 屬性來打印人類可讀的車輛當前速度的描述。
print("Vehicle: \(someVehicle.description)")
// 運行結(jié)果:
// Vehicle: traveling at 0.0 miles per hour
Vehicle 類為任意車輛定義了一些通用屬性特征,但是基本不會被它自己的實例所使用。為了讓這個類更加有用,我們需要定義一些具體的車輛。
子類化
子類化是在已有類的基礎(chǔ)上創(chuàng)建新類的過程。子類會繼承父類的特性,也可以優(yōu)化這些特性。我們也可以向子類添加新的特性。
將子類名稱寫在父類名稱之前,中間用分號分開,這樣就聲明了一個子類:
class SomeSubclass: SomeSuperclass {
// 在這里定義子類
}
下面的例子定義了一個叫 Bicycle 的子類,它的父類是 Vehicle 類:
class Bicycle: Vehicle {
var hasBasket = false
}
Bicycle 類自動獲得了 Vehicle 類的所有特性,如 currentSpeed 和 description 這兩個屬性,以及 makeNoise() 方法。
除了繼承的特性,Bicycle類還定義了一個名為 hasBasket 的存儲屬性,它的默認值為 false(推斷類型為 Bool 類型)。
任何我們創(chuàng)建的 Bicycle 實例默認都沒有車筐。在特定的 Bicycle 實例被創(chuàng)建后,我們可以設(shè)置它的 hasBasket 屬性為 true:
let bicycle = Bicycle()
bicycle.hasBasket = true
我們還可以修改這個實例繼承的 currentSpeed 屬性,并查詢這個實例繼承的 description 屬性:
bicycle.currentSpeed = 15.0
print("Bicycle: \(bicycle.description)")
// 運行結(jié)果:
// Bicycle:traveling at 15.0 miles per hour
子類也可以被繼承,下面的例子創(chuàng)建了 Bicycle 的子類,名為 Tandem 的雙座自行車:
class Tandem: Bicycle {
var currentNumberOfPassengers = 0
}
Tandem 繼承了 Bicycle 的所有屬性和方法,后者繼承了 Vehicle 的所有屬性和方法。Tandem 還添加了一個新的存儲屬性,叫做 currentNumberOfPassengers,它的默認值為 0。
如果你創(chuàng)建了 Tandem 的實例,就可以使用它的新屬性和繼承屬性,比如可以查詢它從 Vehicle 繼承的只讀 description 屬性:
let tandem = Tandem()
tandem.hasBasket = true
tandem.currentNumberOfPassengers = 2
tandem.currentSpeed = 22.0
print("Tandem: \(tandem.description)")
// 運行結(jié)果:
// Tandem: traveling at 22.0 miles per hour
重寫
子類能夠提供它自己的關(guān)于實例方法、類方法、實例屬性、類屬性或者下標的自定義實現(xiàn),如果沒有這些特性,它會從父類繼承,這被稱作重寫。
重寫從父類繼承的特性,我們需要使用 override 關(guān)鍵字作為重寫定義的前綴。這樣做就表明你想進行重寫,并且提供了正確的匹配定義。意外地重寫會導致不可預知的行為,如果沒有使用 override 關(guān)鍵字,在編譯時會被編譯器診斷為一個錯誤。
override 關(guān)鍵字還能夠使 Swift 編譯器檢查被重寫類的父類(或者父類之一)有相匹配的屬性或方法聲明。這種檢查能夠確保我們重寫定義的正確性。
訪問父類方法、屬性和下標
當我們對父類的方法、屬性或下標進行重寫時,通常會使用父類的實現(xiàn)作為我們重寫的一部分。例如,我們可以優(yōu)化現(xiàn)有的實現(xiàn)方式,或者在現(xiàn)有繼承變量中存儲修改后的值。
在適當情況下,我們可以通過 super 下標來訪問父類的方法、屬性或者下標:
- 一個名為
someMethod()的被重寫方法能夠通過在它的實現(xiàn)中調(diào)用super.someMethod()訪問父類的someMethod()方法。 - 一個名為
someProperty的被重寫屬性能夠通過在getter或setter實現(xiàn)中調(diào)用super.someProperty來訪問父類中的someProperty屬性。 - 一個名為
someIndex的被重寫下標能夠在重寫下標實現(xiàn)中調(diào)用 super[someIndex] 來訪問父類中的同名下標。
重寫方法
我們能夠通過在子類中重寫一個實例方法或類型方法來得到此方法的定制或替代的實現(xiàn)方式。
下面的例子定義了一個名為 Train 的類,它是 Vehicle 的子類,它重寫了繼承自 Vehicle 的 makeNoise() 方法:
class Train: Vehicle {
override func makeNoise() {
print("Choo Choo")
}
}
如果我們創(chuàng)建一個 Train 的實例,然后調(diào)用 makeNoise 方法,就可以看到 Train 版本的這個方法運行結(jié)果:
let train = Train()
train.makeNoise()
// 運行結(jié)果
// Prints "Choo Choo"
重寫屬性
我們可以通過重寫一個繼承下來的實力屬性或類型屬性,進而提供我們自定義的此屬性的 getter 和 setter 方法;也可以通過添加屬性觀察器,在重寫的屬性值發(fā)生變化時進行觀察。
重寫屬性的 getter 和 setter 方法
我們能夠通過提供一個自定義的 getter 方法來重寫任何繼承的屬性(如果需要,也可以自定義 setter 方法),無論這個屬性是存儲屬性還是計算屬性。子類根本就不知道繼承屬性的本質(zhì)是存儲還是計算 —— 它只知道繼承屬性有一個特定的類型和一個特定的名稱。你必須始終聲明要重寫的屬性的名稱和類型,這樣能夠使編譯器檢查你的重寫在名稱和類型上與對應(yīng)的父類屬性相匹配。
我們能夠在子類屬性重寫中同時提供 getter 和 setter 方法,使得繼承的只讀屬性作為可讀寫屬性來使用。然而,我們不能將繼承的可讀寫屬性作為只讀屬性來使用。
注意
只要在重寫屬性時提供了 setter 方法,就必須提供 getter 方法。
假如你不想在重寫的 getter 方法中改變繼承屬性的值,
可以簡單地返回一個 super.someProperty,
someProperty 是要重寫屬性的名稱。
下面的例子定義了一個新的類,叫做 Car,它是 Vehicle 類的子類。這個類引入了一個叫做 gear 的新存儲屬性,它的默認值是整型 1。這個類還重寫了繼承自 Vehicle 類的 description 屬性,用來提供對現(xiàn)有 gear 的自定義描述:
class Car: Vehicle {
var gear = 1
override var description: String {
return super.description + " in gear \(gear)"
}
}
description 屬性的重寫開始調(diào)用了 super.description,它返回 Vehicle 類的 description 屬性。Car 中的 description 屬性在最后增加了一些額外的關(guān)于現(xiàn)有 gear 的文本。
如果我們創(chuàng)建了 Car 類的實例,并且設(shè)置了它的 gear 和 currentSpeed 屬性,我們可以看到它的 description 屬性返回一句定制的文本:
let car = car()
car.currentSpeed = 25.0
car.gear = 3
print("Car: \(car.description)")
// 運行結(jié)果
// Car: traveling at 25.0 miles per hour in gear 3
重寫屬性觀察器
我們能夠使用屬性重寫來將屬性觀察器添加到一個繼承屬性上。這使得無論繼承屬性是否是原始的實現(xiàn),當它的值變化時我們可以被通知。獲取更多信息請移步到 屬性觀察器。
注意
我們不能將屬性觀察器添加到繼承常量存儲屬性上,也不能添加到繼承只讀計算屬性上。
這兩種屬性的值不能被設(shè)置,所以這種情況下我們不能在重寫時實現(xiàn) willSet 和 didSet。
我們也不能同時對一個屬性進行 setter 方法重寫或添加屬性觀察器。
如果我們想觀察一個屬性的值變化,并且已經(jīng)重寫了這個屬性的 setter 方法,
我們就能在這個 setter 方法里觀察值的變化了。
下面的例子定義了一個叫 AutomaticCar 的類,它是 Car 的子類。這輛車代表了一輛有自動變速箱的汽車,它會根據(jù)當前速度自動選擇檔位:
class AutomaticCar: Car {
override var currentSpeed: Double {
didSet {
gear = Int(currentSpeed / 10.0) + 1
}
}
}
無論何時我們對一個 AutomaticCar 的實例的 currentSpeed 屬性進行設(shè)置,這個屬性的 didSet 觀察器對實例的 gear 屬性進行設(shè)置,以便選擇一個對應(yīng)新的速度的檔位:
let automatic = AutomaticCar()
automatic.currentSpeed = 35.0
print("AutomaticCar: \(automatic.description)")
// AutomaticCar: traveling at 35.0 miles per hour in gear 4
禁止重寫
我們可以通過 final 關(guān)鍵字禁止對一個方法、屬性或者下標進行重寫。我們需要在方法、屬性或下標的聲明語句前加上 final 修飾語(例如 final var、final func、final class func 和 final subscript)。
任何嘗試重寫被 final 修飾的方法、屬性或下標的行為,都會被報編譯錯誤。在一個擴展中給一個類添加的方法、屬性或下標也可以被標記為 final。
我們可以在 class 關(guān)鍵字前寫上 final 來對整個類進行標記,所有繼承自這個類的子類都會被報編譯錯誤。