继承
类可以继承另一个类的方法、属性以及其他某些特征。当 A 类继承 B 类时,A 类为子类,B 类为父类。在 Swift 中,『继承』是类(class)区别于其他类型的基本特性。
Swift 中,类能够调用和访问它的父类的方法、属性和下标,也能够对这些方法、属性和下标进行重写,从而优化或改变它们。Swift 通过检查重写定义是否有对应的父类定义来确保我们的重写的正确性。
类还能够通过增加属性观察器来继承属性,当这个属性变化时会得到通知。属性观察器能够被添加到任何属性上,无论这个属性是存储属性还是计算属性。
定义基类
任何没有继承其他类的类,都被称作基类。
注意:Swift 中的类并非继承自一个统一的基类。定义一个类时如果不指定父类,
则这个类自动变为基类。
下面的例子定义了一个名为 Vehicle
的基类。这个基类有一个叫做 currentSpeed
的存储属性,它的默认值为 0.0
(推断类型为 Double
)。currentSpeed
属性的值被一个叫做 description
的只读 String
类型计算属性使用,用来创建车辆的描述。
Vehicle
基类还定义了一个叫做 makeNoise
的方法,这个方法目前不做任何事情,之后会被 Vehicle
的子类重写。
class Vehicle {
var currentSpeed = 0.0
var description: String {
return "traveling at \(currentSpeed) miles per hour"
}
func makeNoise() {
// 什么都不做 - 不是所有车辆都发出噪音
}
}
通过初始化语法,类名后加一对圆括号,我们创建了 Vehicle
类的新实例:
let someVehicle = Vehicle()
这之后,我们就可以用 someVehicle
实例来访问 description
属性来打印人类可读的车辆当前速度的描述。
print("Vehicle: \(someVehicle.description)")
// 运行结果:
// Vehicle: traveling at 0.0 miles per hour
Vehicle
类为任意车辆定义了一些通用属性特征,但是基本不会被它自己的实例所使用。为了让这个类更加有用,我们需要定义一些具体的车辆。
子类化
子类化是在已有类的基础上创建新类的过程。子类会继承父类的特性,也可以优化这些特性。我们也可以向子类添加新的特性。
将子类名称写在父类名称之前,中间用分号分开,这样就声明了一个子类:
class SomeSubclass: SomeSuperclass {
// 在这里定义子类
}
下面的例子定义了一个叫 Bicycle
的子类,它的父类是 Vehicle
类:
class Bicycle: Vehicle {
var hasBasket = false
}
Bicycle
类自动获得了 Vehicle
类的所有特性,如 currentSpeed
和 description
这两个属性,以及 makeNoise()
方法。
除了继承的特性,Bicycle
类还定义了一个名为 hasBasket
的存储属性,它的默认值为 false
(推断类型为 Bool
类型)。
任何我们创建的 Bicycle
实例默认都没有车筐。在特定的 Bicycle
实例被创建后,我们可以设置它的 hasBasket
属性为 true
:
let bicycle = Bicycle()
bicycle.hasBasket = true
我们还可以修改这个实例继承的 currentSpeed
属性,并查询这个实例继承的 description
属性:
bicycle.currentSpeed = 15.0
print("Bicycle: \(bicycle.description)")
// 运行结果:
// Bicycle:traveling at 15.0 miles per hour
子类也可以被继承,下面的例子创建了 Bicycle
的子类,名为 Tandem
的双座自行车:
class Tandem: Bicycle {
var currentNumberOfPassengers = 0
}
Tandem
继承了 Bicycle
的所有属性和方法,后者继承了 Vehicle
的所有属性和方法。Tandem
还添加了一个新的存储属性,叫做 currentNumberOfPassengers
,它的默认值为 0
。
如果你创建了 Tandem
的实例,就可以使用它的新属性和继承属性,比如可以查询它从 Vehicle
继承的只读 description
属性:
let tandem = Tandem()
tandem.hasBasket = true
tandem.currentNumberOfPassengers = 2
tandem.currentSpeed = 22.0
print("Tandem: \(tandem.description)")
// 运行结果:
// Tandem: traveling at 22.0 miles per hour
重写
子类能够提供它自己的关于实例方法、类方法、实例属性、类属性或者下标的自定义实现,如果没有这些特性,它会从父类继承,这被称作重写。
重写从父类继承的特性,我们需要使用 override
关键字作为重写定义的前缀。这样做就表明你想进行重写,并且提供了正确的匹配定义。意外地重写会导致不可预知的行为,如果没有使用 override
关键字,在编译时会被编译器诊断为一个错误。
override
关键字还能够使 Swift 编译器检查被重写类的父类(或者父类之一)有相匹配的属性或方法声明。这种检查能够确保我们重写定义的正确性。
访问父类方法、属性和下标
当我们对父类的方法、属性或下标进行重写时,通常会使用父类的实现作为我们重写的一部分。例如,我们可以优化现有的实现方式,或者在现有继承变量中存储修改后的值。
在适当情况下,我们可以通过 super
下标来访问父类的方法、属性或者下标:
- 一个名为
someMethod()
的被重写方法能够通过在它的实现中调用super.someMethod()
访问父类的someMethod()
方法。 - 一个名为
someProperty
的被重写属性能够通过在getter
或setter
实现中调用super.someProperty
来访问父类中的someProperty
属性。 - 一个名为
someIndex
的被重写下标能够在重写下标实现中调用 super[someIndex] 来访问父类中的同名下标。
重写方法
我们能够通过在子类中重写一个实例方法或类型方法来得到此方法的定制或替代的实现方式。
下面的例子定义了一个名为 Train
的类,它是 Vehicle
的子类,它重写了继承自 Vehicle
的 makeNoise()
方法:
class Train: Vehicle {
override func makeNoise() {
print("Choo Choo")
}
}
如果我们创建一个 Train
的实例,然后调用 makeNoise
方法,就可以看到 Train
版本的这个方法运行结果:
let train = Train()
train.makeNoise()
// 运行结果
// Prints "Choo Choo"
重写属性
我们可以通过重写一个继承下来的实力属性或类型属性,进而提供我们自定义的此属性的 getter
和 setter
方法;也可以通过添加属性观察器,在重写的属性值发生变化时进行观察。
重写属性的 getter 和 setter 方法
我们能够通过提供一个自定义的 getter 方法来重写任何继承的属性(如果需要,也可以自定义 setter 方法),无论这个属性是存储属性还是计算属性。子类根本就不知道继承属性的本质是存储还是计算 —— 它只知道继承属性有一个特定的类型和一个特定的名称。你必须始终声明要重写的属性的名称和类型,这样能够使编译器检查你的重写在名称和类型上与对应的父类属性相匹配。
我们能够在子类属性重写中同时提供 getter 和 setter 方法,使得继承的只读属性作为可读写属性来使用。然而,我们不能将继承的可读写属性作为只读属性来使用。
注意
只要在重写属性时提供了 setter 方法,就必须提供 getter 方法。
假如你不想在重写的 getter 方法中改变继承属性的值,
可以简单地返回一个 super.someProperty,
someProperty 是要重写属性的名称。
下面的例子定义了一个新的类,叫做 Car
,它是 Vehicle
类的子类。这个类引入了一个叫做 gear
的新存储属性,它的默认值是整型 1
。这个类还重写了继承自 Vehicle
类的 description
属性,用来提供对现有 gear
的自定义描述:
class Car: Vehicle {
var gear = 1
override var description: String {
return super.description + " in gear \(gear)"
}
}
description
属性的重写开始调用了 super.description
,它返回 Vehicle
类的 description
属性。Car
中的 description
属性在最后增加了一些额外的关于现有 gear
的文本。
如果我们创建了 Car
类的实例,并且设置了它的 gear
和 currentSpeed
属性,我们可以看到它的 description
属性返回一句定制的文本:
let car = car()
car.currentSpeed = 25.0
car.gear = 3
print("Car: \(car.description)")
// 运行结果
// Car: traveling at 25.0 miles per hour in gear 3
重写属性观察器
我们能够使用属性重写来将属性观察器添加到一个继承属性上。这使得无论继承属性是否是原始的实现,当它的值变化时我们可以被通知。获取更多信息请移步到 属性观察器。
注意
我们不能将属性观察器添加到继承常量存储属性上,也不能添加到继承只读计算属性上。
这两种属性的值不能被设置,所以这种情况下我们不能在重写时实现 willSet 和 didSet。
我们也不能同时对一个属性进行 setter 方法重写或添加属性观察器。
如果我们想观察一个属性的值变化,并且已经重写了这个属性的 setter 方法,
我们就能在这个 setter 方法里观察值的变化了。
下面的例子定义了一个叫 AutomaticCar
的类,它是 Car
的子类。这辆车代表了一辆有自动变速箱的汽车,它会根据当前速度自动选择档位:
class AutomaticCar: Car {
override var currentSpeed: Double {
didSet {
gear = Int(currentSpeed / 10.0) + 1
}
}
}
无论何时我们对一个 AutomaticCar
的实例的 currentSpeed
属性进行设置,这个属性的 didSet
观察器对实例的 gear
属性进行设置,以便选择一个对应新的速度的档位:
let automatic = AutomaticCar()
automatic.currentSpeed = 35.0
print("AutomaticCar: \(automatic.description)")
// AutomaticCar: traveling at 35.0 miles per hour in gear 4
禁止重写
我们可以通过 final
关键字禁止对一个方法、属性或者下标进行重写。我们需要在方法、属性或下标的声明语句前加上 final
修饰语(例如 final var
、final func
、final class func
和 final subscript
)。
任何尝试重写被 final 修饰的方法、属性或下标的行为,都会被报编译错误。在一个扩展中给一个类添加的方法、属性或下标也可以被标记为 final。
我们可以在 class
关键字前写上 final
来对整个类进行标记,所有继承自这个类的子类都会被报编译错误。