Swift 4.2 官方文档翻译 —— 继承

继承

类可以继承另一个类的方法、属性以及其他某些特征。当 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 类的所有特性,如 currentSpeeddescription 这两个属性,以及 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 的被重写属性能够通过在 gettersetter 实现中调用 super.someProperty 来访问父类中的 someProperty 属性。
  • 一个名为 someIndex 的被重写下标能够在重写下标实现中调用 super[someIndex] 来访问父类中的同名下标。

重写方法

我们能够通过在子类中重写一个实例方法或类型方法来得到此方法的定制或替代的实现方式。

下面的例子定义了一个名为 Train 的类,它是 Vehicle 的子类,它重写了继承自 VehiclemakeNoise() 方法:

class Train: Vehicle {
    override func makeNoise() {
        print("Choo Choo")
    }
}

如果我们创建一个 Train 的实例,然后调用 makeNoise 方法,就可以看到 Train 版本的这个方法运行结果:

let train = Train()
train.makeNoise()
// 运行结果
// Prints "Choo Choo"

重写属性

我们可以通过重写一个继承下来的实力属性或类型属性,进而提供我们自定义的此属性的 gettersetter 方法;也可以通过添加属性观察器,在重写的属性值发生变化时进行观察。

重写属性的 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 类的实例,并且设置了它的 gearcurrentSpeed 属性,我们可以看到它的 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 varfinal funcfinal class funcfinal subscript)。

任何尝试重写被 final 修饰的方法、属性或下标的行为,都会被报编译错误。在一个扩展中给一个类添加的方法、属性或下标也可以被标记为 final。

我们可以在 class 关键字前写上 final 来对整个类进行标记,所有继承自这个类的子类都会被报编译错误。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,402评论 6 499
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,377评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,483评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,165评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,176评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,146评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,032评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,896评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,311评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,536评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,696评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,413评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,008评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,815评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,698评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,592评论 2 353

推荐阅读更多精彩内容