枚举
- 使用 enum 关键词来创建枚举并且把它们的整个定义放在一对大括号内:与 C 和 Objective-C 语言不同,Swift 的枚举成员在被创建时不会被赋予一个默认的整型值
enum CompassPoint {
case north
case south
case east
case west
}
directionToHead = .south
switch directionToHead {
case .north:
print("Lots of planets have a north")
case .south:
print("Watch out for penguins")
case .east:
print("Where the sun rises")
case .west:
print("Where the skies are blue")
}
// 打印 "Watch out for penguins"
- 当不需要匹配每个枚举成员的时候,你可以提供一个 default 分支来涵盖所有未明确列出的枚举成员:
let somePlanet = Planet.earth
switch somePlanet {
case .earth:
print("Mostly harmless")
default:
print("Not a safe place for humans")
}
// 打印 "Mostly harmless"
- 在一些情况下,你会需要得到一个包含枚举所有成员的集合。你可以这样实现:在枚举的名称后编写 : CaseIterable 来启用此功能,令枚举遵循 CaseIterable 协议。Swift 会生成一个 allCases 属性,用于表示一个包含枚举所有成员的集合。
enum Beverage: CaseIterable {
case coffee, tea, juice
}
let numberOfChoices = Beverage.allCases.count
print("\(numberOfChoices) beverages available")
// 打印 "3 beverages available"
for beverage in Beverage.allCases {
print(beverage)
}
// coffee
// tea
// juice
- 你可以定义 Swift 枚举来存储任意类型的关联值,如果需要的话,每个枚举成员的关联值类型可以各不相同。枚举的这种特性跟其他语言中的可识别联合(discriminated unions),标签联合(tagged unions),或者变体(variants)相似。
enum Barcode {
case upc(Int, Int, Int, Int)
case qrCode(String)
}
var productBarcode = Barcode.upc(8, 85909, 51226, 3)
productBarcode = .qrCode("ABCDEFGHIJKLMNOP")
switch productBarcode {
case .upc(let numberSystem, let manufacturer, let product, let check):
print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).")
case .qrCode(let productCode):
print("QR code: \(productCode).")
}
// 打印 "QR code: ABCDEFGHIJKLMNOP."
- 如果一个枚举成员的所有关联值都被提取为常量,或者都被提取为变量,为了简洁,你可以只在成员名称前标注一个 let 或者 var:
switch productBarcode {
case let .upc(numberSystem, manufacturer, product, check):
print("UPC : \(numberSystem), \(manufacturer), \(product), \(check).")
case let .qrCode(productCode):
print("QR code: \(productCode).")
}
// 打印 "QR code: ABCDEFGHIJKLMNOP."
- 作为关联值的替代选择,枚举成员可以被默认值(称为原始值)预填充,这些原始值的类型必须相同。
- 在使用原始值为整数或者字符串类型的枚举时,不需要显式地为每一个枚举成员设置原始值,当你没有手动赋值时,Swift 将会自动为你赋值。
- 如果在定义枚举类型的时候使用了原始值,那么将会自动获得一个构造器,这个构造器接收一个叫做 rawValue 的参数,参数类型即为原始值的类型,返回值则是枚举成员或 nil。
enum Planet: Int {
case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
}
let possiblePlanet = Planet(rawValue: 7)
// possiblePlanet 类型为 Planet? 值为 Planet.uranus
let positionToFind = 11
if let somePlanet = Planet(rawValue: positionToFind) {
switch somePlanet {
case .earth:
print("Mostly harmless")
default:
print("Not a safe place for humans")
}
} else {
print("There isn't a planet at position \(positionToFind)")
}
// 打印 "There isn't a planet at position 11"
- 递归枚举是一种枚举类型,其中一个或多个枚举成员的关联值是同一种枚举的另一个实例。你可以在枚举成员前加上 indirect 来表示该成员可递归。使用递归枚举时,编译器会插入一个间接层。
- 你也可以在枚举类型前面加上 indirect 关键字来表明它的所有成员都是可递归的:
enum ArithmeticExpression {
case number(Int)
indirect case addition(ArithmeticExpression, ArithmeticExpression)
indirect case multiplication(ArithmeticExpression, ArithmeticExpression)
}
// 简洁写法
indirect enum ArithmeticExpression {
case number(Int)
case addition(ArithmeticExpression, ArithmeticExpression)
case multiplication(ArithmeticExpression, ArithmeticExpression)
}
let five = ArithmeticExpression.number(5)
let four = ArithmeticExpression.number(4)
let sum = ArithmeticExpression.addition(five, four)
let product = ArithmeticExpression.multiplication(sum, ArithmeticExpression.number(2))
func evaluate(_ expression: ArithmeticExpression) -> Int {
switch expression {
case let .number(value):
return value
case let .addition(left, right):
return evaluate(left) + evaluate(right)
case let .multiplication(left, right):
return evaluate(left) * evaluate(right)
}
}
print(evaluate(product))
// 打印 "18"
结构体和类
- 结构体和类是通用、灵活的构造,是程序代码的基石。 你可以使用与定义常量、变量和函数相同的语法, 为结构体和类添加属性和方法以增加功能。
- 与其他编程语言不同,Swift 不需要为自定义结构体和类创建单独的接口和实现文件。 在 Swift 中,当你在单个文件中定义结构体或类, 该类或结构体的外部接口会自动提供给其他代码使用。
struct SomeStructure {
// 结构体定义在这里
}
class SomeClass {
// 类定义在这里
}
- 值类型是一种在被赋值给变量或常量时, 或者在传递给函数时,其值会被复制的类型。
- Swift 中所有的基本类型:整数、浮点数、布尔值、字符串、数组和字典都是值类型, 在底层它们都是以结构体的形式实现的。
- 在 Swift 中,所有的结构体和枚举都是值类型。 这意味着它们的实例,以及实例中所包含的任何值类型的属性在代码中传递时都会被复制。
- 写时复制:Swift 标准库定义的集合类型,如数组、字典和字符串, 使用了一种优化技术来降低复制操作的性能消耗。它们不会立即进行复制, 而是共享了原始实例和任何副本之间存储元素的内存。 如果集合的某个副本被修改,则在修改之前会复制元素。 您在代码中看到的样子就像立即进行了复制一样。
enum CompassPoint {
case north, south, east, west
mutating func turnNorth() {
self = .north
}
}
var currentDirection = CompassPoint.west
let rememberedDirection = currentDirection
currentDirection.turnNorth()
print("The current direction is \(currentDirection)")
// 打印 "The current direction is north"
print("The remembered direction is \(rememberedDirection)")
// 打印 "The current direction is west"
- 类是引用类型:不同于值类型每次都会创建一个新的副本, 引用类型在被赋值给变量或常量, 或者在被传递给函数时不会被复制。 而是指向同一个现有实例的引用。
- 注意恒等(用三个等号 === 表示)与相等(用两个等号==表示)意义不同。 恒等意味着两个类型的常量或变量引用了完全相同的类实例。 相等意味着两个实例在值上被认为是相等或等价的, 具体的相等定义由类型的设计者决定。
let tenEighty = VideoMode()
tenEighty.resolution = hd
tenEighty.interlaced = true
tenEighty.name = "1080i"
tenEighty.frameRate = 25.0
let alsoTenEighty = tenEighty
alsoTenEighty.frameRate = 30.0
print("The frameRate property of tenEighty is now \(tenEighty.frameRate)")
// 打印 "The frameRate property of tenEighty is now 30.0"
if tenEighty === alsoTenEighty {
print("tenEighty and alsoTenEighty refer to the same VideoMode instance.")
}
// 打印 "tenEighty and alsoTenEighty refer to the same VideoMode instance."
属性
- 属性将值与特定的类、结构或枚举关联。
- 存储属性将常量和变量值作为实例的一部分进行存储,而计算属性则计算(而不是存储)一个值。
- 计算属性由类、结构和枚举提供。
- 存储属性仅由类和结构体提供。
- 存储和计算属性通常与特定类型的实例相关联。然而,属性也可以与类型本身相关联。这种属性称为类型属性。
- 当值类型的实例被声明为常量的时候,它的所有属性也就成了常量。
- 属于引用类型的类别则不一样,把一个引用类型的实例赋给一个常量后,依然可以修改该实例的可变属性。
struct FixedLengthRange {
var firstValue: Int
let length: Int
}
var rangeOfThreeItems = FixedLengthRange(firstValue: 0, length: 3)
// 该区间表示整数 0,1,2
rangeOfThreeItems.firstValue = 6
// 该区间现在表示整数 6,7,8
let rangeOfFourItems = FixedLengthRange(firstValue: 0, length: 4)
// 该区间表示整数 0,1,2,3
rangeOfFourItems.firstValue = 6
// 尽管 firstValue 是个可变属性,但这里还是会报错
- 延时加载存储属性是指当第一次被调用的时候才会计算其初始值的属性。在属性声明前使用 lazy 来标示一个延时加载存储属性。
- 必须将延时加载属性声明成变量(使用 var 关键词),因为属性的初始值可能在实例构造完成之后才会得到。而常量属性在构造过程完成之前必须要有初始值,因此无法声明成延时加载。
- 延时加载属性在属性的初始值依赖于外部因素,且这些因素的值在实例初始化完成后才会知道时非常有用。或者当获得属性的值因为需要复杂或者大量的计算,而应该采用需要的时候再计算的方式,延时加载属性也会很有用。
- 如果一个被标记为 lazy 的属性在没有初始化时就同时被多个线程访问,则无法保证该属性只会被初始化一次。
class DataImporter {
/*
DataImporter 是一个负责将外部文件中的数据导入的类。
这个类的初始化会消耗不少时间。
*/
var filename = "data.txt"
// 这里会提供数据导入功能
}
class DataManager {
lazy var importer = DataImporter()
var data: [String] = []
// 这里会提供数据管理功能
}
let manager = DataManager()
manager.data.append("Some data")
manager.data.append("Some more data")
// DataImporter 实例的 importer 属性还没有被创建
print(manager.importer.filename)
// DataImporter 实例的 importer 属性现在被创建了
// 输出“data.txt”
- 除存储属性外,类、结构体和枚举还可以定义计算属性。计算属性不直接存储值,而是提供一个 getter 和一个可选的 setter,来间接获取和设置其他属性或变量的值。
struct Point {
var x = 0.0, y = 0.0
}
struct Size {
var width = 0.0, height = 0.0
}
struct Rect {
var origin = Point()
var size = Size()
var center: Point {
get {
let centerX = origin.x + (size.width / 2)
let centerY = origin.y + (size.height / 2)
return Point(x: centerX, y: centerY)
}
set(newCenter) {
origin.x = newCenter.x - (size.width / 2)
origin.y = newCenter.y - (size.height / 2)
}
}
}
var square = Rect(origin: Point(x: 0.0, y: 0.0),
size: Size(width: 10.0, height: 10.0))
let initialSquareCenter = square.center
// initialSquareCenter 位于(5.0, 5.0)
square.center = Point(x: 15.0, y: 15.0)
print("square.origin is now at (\(square.origin.x), \(square.origin.y))")
// 打印“square.origin is now at (10.0, 10.0)”
- 属性观察器用于监测并响应属性值的变化。每次属性值被设置时,无论新值是否与当前值相同,属性观察器都会被调用。属性观察器可以添加在以下位置:自定义的存储属性, 继承的存储属性, 继承的计算属性
- 对于继承的属性,可以通过在子类中重写该属性来添加属性观察器。对于自定义的计算属性,应使用属性的 setter 来观察和响应值的变化,而不是试图创建一个观察器
class StepCounter {
var totalSteps: Int = 0 {
willSet(newTotalSteps) {
print("About to set totalSteps to \(newTotalSteps)")
}
didSet {
if totalSteps > oldValue {
print("Added \(totalSteps - oldValue) steps")
}
}
}
}
let stepCounter = StepCounter()
stepCounter.totalSteps = 200
// 将 totalSteps 的值设置为 200
// 增加了 200 步
stepCounter.totalSteps = 360
// 将 totalSteps 的值设置为 360
// 增加了 160 步
stepCounter.totalSteps = 896
// 将 totalSteps 的值设置为 896
// 增加了 536 步
- 属性包装器在管理属性存储方式的代码和定义属性的代码之间添加了一层分离。例如,如果有一些属性需要提供线程安全检查或将其底层数据存储在数据库中,那么你必须在每个属性上编写这些代码。而使用属性包装器时,只需在定义包装器时编写一次管理代码,然后通过将其应用于多个属性来重复使用这些管理代码。
- 要定义属性包装器,需要创建一个结构体、枚举或类,并定义一个 wrappedValue 属性。
- 可以通过在属性前作为特性写上包装器的名称来应用包装器。
@propertyWrapper
struct TwelveOrLess {
private var number = 0
var wrappedValue: Int {
get { return number }
set { number = min(newValue, 12) }
}
}
struct SmallRectangle {
@TwelveOrLess var height: Int
@TwelveOrLess var width: Int
}
var rectangle = SmallRectangle()
print(rectangle.height)
// 打印 "0"
rectangle.height = 10
print(rectangle.height)
// 打印 "10"
rectangle.height = 24
print(rectangle.height)
// 打印 "12"
- 为了支持设置初始值或其他自定义,属性包装器需要添加一个构造器。
@propertyWrapper
struct SmallNumber {
private var maximum: Int
private var number: Int
var wrappedValue: Int {
get { return number }
set { number = min(newValue, maximum) }
}
init() {
maximum = 12
number = 0
}
init(wrappedValue: Int) {
maximum = 12
number = min(wrappedValue, maximum)
}
init(wrappedValue: Int, maximum: Int) {
self.maximum = maximum
number = min(wrappedValue, maximum)
}
}
struct ZeroRectangle {
@SmallNumber var height: Int
@SmallNumber var width: Int
}
var zeroRectangle = ZeroRectangle()
print(zeroRectangle.height, zeroRectangle.width)
// 打印 "0 0"
struct UnitRectangle {
@SmallNumber var height: Int = 1
@SmallNumber var width: Int = 1
}
var unitRectangle = UnitRectangle()
print(unitRectangle.height, unitRectangle.width)
// 打印 "1 1"
struct NarrowRectangle {
@SmallNumber(wrappedValue: 2, maximum: 5) var height: Int
@SmallNumber(wrappedValue: 3, maximum: 4) var width: Int
}
var narrowRectangle = NarrowRectangle()
print(narrowRectangle.height, narrowRectangle.width)
// 打印 "2 3"
narrowRectangle.height = 100
narrowRectangle.width = 100
print(narrowRectangle.height, narrowRectangle.width)
// 打印 "5 4"
struct MixedRectangle {
@SmallNumber var height: Int = 1
@SmallNumber(maximum: 9) var width: Int = 2
}
var mixedRectangle = MixedRectangle()
print(mixedRectangle.height)
// 打印 "1"
mixedRectangle.height = 20
print(mixedRectangle.height)
// 打印 "12"
- 除了被包装的值之外,属性包装器还可以通过定义被呈现值来提供额外的功能
@propertyWrapper
struct SmallNumber {
private var number: Int
private(set) var projectedValue: Bool
var wrappedValue: Int {
get { return number }
set {
if newValue > 12 {
number = 12
projectedValue = true
} else {
number = newValue
projectedValue = false
}
}
}
init() {
self.number = 0
self.projectedValue = false
}
}
struct SomeStructure {
@SmallNumber var someNumber: Int
}
var someStructure = SomeStructure()
someStructure.someNumber = 4
print(someStructure.$someNumber)
// 打印 "false"
someStructure.someNumber = 55
print(someStructure.$someNumber)
// 打印 "true"
- 使用 static 关键字定义类型属性。对于类类型的计算类型属性,可以使用 class 关键字,允许子类重写父类的实现。
- 类型属性的查询和设置使用点语法,就像实例属性一样。然而,类型属性是针对类型本身进行查询和设置的,而不是针对该类型的某个实例。
struct SomeStructure {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 1
}
}
enum SomeEnumeration {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 6
}
}
class SomeClass {
static var storedTypeProperty = "Some value."
static var computedTypeProperty: Int {
return 27
}
class var overrideableComputedTypeProperty: Int {
return 107
}
}
print(SomeStructure.storedTypeProperty)
// 打印 "Some value."
SomeStructure.storedTypeProperty = "Another value."
print(SomeStructure.storedTypeProperty)
// 打印 "Another value."
print(SomeEnumeration.computedTypeProperty)
// 打印 "6"
print(SomeClass.computedTypeProperty)
// 打印 "27"
方法
- 方法是与特定类型关联的函数。
类、结构体和枚举都可以定义实例方法,这些方法封装了特定的任务和功能,用于处理给定类型的实例。
- 类、结构体和枚举还可以定义类型方法,这些方法与类型本身相关联。
- 结构体和枚举在 Swift 中能够定义方法,这是与 C 和 Objective-C 的一个重大区别。
- 枚举不能有实例化的存储属性,但是可以有类型的存储属性。枚举可以有计算属性。
- 实例方法是属于某个类、结构体或枚举实例的函数。 它们通过提供访问和修改实例属性的方式,或者提供与实例功能相关的操作,来支持实例的整体功能。
- 每个类型的实例都有一个隐式属性,称为 self,它与实例本身完全等同。 你可以在实例方法中使用 self 属性来引用当前实例。
- 实际上,在代码中你不需要经常写 self。 如果你没有显式地写出 self,当你在方法中使用已知的属性或方法名称时,Swift 会假设你是在引用当前实例的属性或方法。
// 默认不写self
class Counter {
var count = 0
func increment() {
count += 1
}
func increment(by amount: Int) {
count += amount
}
func reset() {
count = 0
}
}
let counter = Counter()
// the initial counter value is 0
counter.increment()
// the counter's value is now 1
counter.increment(by: 5)
// the counter's value is now 6
counter.reset()
// the counter's value is now 0
- 此规则的主要例外情况发生在实例方法的形参名称与该实例的属性名称相同时。 在这种情况下,形参名称优先,此时就需要以更明确的方式引用属性。你可以使用 self 属性来区分形参名称和属性名称。
struct Point {
var x = 0.0, y = 0.0
// self 用于区分一个名为 x 的方法形参和一个同样名为 x 的实例属性
func isToTheRightOf(x: Double) -> Bool {
return self.x > x
}
}
let somePoint = Point(x: 4.0, y: 5.0)
if somePoint.isToTheRightOf(x: 1.0) {
print("This point is to the right of the line where x == 1.0")
}
// Prints "This point is to the right of the line where x == 1.0"
- 结构体和枚举是 值类型。默认情况下,值类型的属性不能在其实例方法内部被修改。
- 如果你需要在特定方法内部修改结构体或枚举的属性,你可以为该方法启用 mutating 行为。 这样方法就可以在内部改变其属性,并且所有的更改在方法结束时会写回到原始结构体中。 该方法还可以将一个全新的实例赋值给隐式的 self 属性,并且这个新实例将在方法结束时替换现有实例。
struct Point {
var x = 0.0, y = 0.0
mutating func moveBy(x deltaX: Double, y deltaY: Double) {
x += deltaX
y += deltaY
}
}
var somePoint = Point(x: 1.0, y: 1.0)
somePoint.moveBy(x: 2.0, y: 3.0)
print("The point is now at (\(somePoint.x), \(somePoint.y))")
// Prints "The point is now at (3.0, 4.0)"
// 你不能在结构体类型的常量上调用 mutating 方法,因为其属性不能被改变,即使它们是可变属性
let fixedPoint = Point(x: 3.0, y: 3.0)
fixedPoint.moveBy(x: 2.0, y: 3.0)
// this will report an error
- mutating 方法可以将一个全新的实例赋值给隐式的 self 属性。
struct Point {
var x = 0.0, y = 0.0
mutating func moveBy(x deltaX: Double, y deltaY: Double) {
self = Point(x: x + deltaX, y: y + deltaY)
}
}
- 枚举的 mutating 方法可以将隐式的 self 参数设置为同一枚举的不同case:
enum TriStateSwitch {
case off, low, high
mutating func next() {
switch self {
case .off:
self = .low
case .low:
self = .high
case .high:
self = .off
}
}
}
var ovenLight = TriStateSwitch.low
ovenLight.next()
// ovenLight is now equal to .high
ovenLight.next()
// ovenLight is now equal to .off
- 你可以通过在方法的 func 关键字前添加 static 关键字来标识类型方法。 类可以使用 class 关键字代替 static ,以允许子类覆盖父类对该方法的实现。
- 在 Objective-C 中,你只能为 Objective-C 类定义类型级方法。 在 Swift 中,你可以为所有类、结构体和枚举定义类型级方法。 每个类型方法都明确作用于其对应的类型。
- 在类型方法的主体内部,隐式的 self 属性指的是类型本身,而不是该类型的实例。 这意味着你可以使用 self 来区分类型属性和类型方法形参,正如你在实例属性和实例方法形参中所做的那样。
下标
- 下标可以定义在类、结构体和枚举中,是访问集合、列表或序列中元素的快捷方式。
- 可以使用下标的索引设置和获取值,而不需要再调用对应的存取方法。举例来说,用下标访问一个 Array 实例中的元素可以写作 someArray[index],访问 Dictionary 实例中的元素可以写作 someDictionary[key]。
- 一个类型可以定义多个下标,系统会根据索引值的类型自动选择对应的下标重载。
- 下标不限于一维,你可以定义具有多个入参的下标来满足自定义类型的需求。
- 下标允许你通过在实例名称后面的方括号中传入一个或者多个索引值来对实例进行查询。
- 定义下标使用 subscript 关键字,与定义实例方法类似,都是指定一个或多个输入参数和一个返回类型。与实例方法不同的是,下标可以设定为读写或只读。这种行为由 getter 和 setter 实现,类似计算型属性
subscript(index: Int) -> Int {
get {
// 返回一个适当的 Int 类型的值
}
set(newValue) {
// 执行适当的赋值操作
}
}
- 如同只读计算型属性,对于只读下标的声明,你可以通过省略 get 关键字和对应的大括号组来进行简写:
subscript(index: Int) -> Int {
// 返回一个适当的 Int 类型的值
}
- 下面代码演示了只读下标的实现,这里定义了一个 TimesTable 结构体,用来表示对应整数的乘法表:
struct TimesTable {
let multiplier: Int
subscript(index: Int) -> Int {
return multiplier * index
}
}
let threeTimesTable = TimesTable(multiplier: 3)
print("six times three is \(threeTimesTable[6])")
// 打印“six times three is 18”
- “下标”的确切含义取决于使用场景。下标通常用作访问集合、列表或序列中的成员元素的快捷方式。你可以针对自己特定的类或结构体功能来以最恰当的方式实现下标。
- 下标可以接受任意数量的入参,并且这些入参可以是任何类型。下标的返回值也可以是任意类型。
- 与函数一样,下标可以接受不同数量的参数,并且为这些参数提供默认值
- 与函数不同的是,下标不能使用 in-out 参数。
- 一个类或结构体可以根据自身需要提供多个下标实现,使用下标时将通过入参的数量和类型进行区分,自动匹配合适的下标。它通常被称为 下标重载。
- 虽然下标采用单一入参是最常见的,但也可以根据情况定义接受多个入参的下标。下面的示例定义一个 Matrix 结构体,该结构体表示一个 Double 类型的二维矩阵。Matrix 结构体的下标接受两个整型参数:
struct Matrix {
let rows: Int, columns: Int
var grid: [Double]
init(rows: Int, columns: Int) {
self.rows = rows
self.columns = columns
grid = Array(repeating: 0.0, count: rows * columns)
}
func indexIsValid(row: Int, column: Int) -> Bool {
return row >= 0 && row < rows && column >= 0 && column < columns
}
subscript(row: Int, column: Int) -> Double {
get {
assert(indexIsValid(row: row, column: column), "Index out of range")
return grid[(row * columns) + column]
}
set {
assert(indexIsValid(row: row, column: column), "Index out of range")
grid[(row * columns) + column] = newValue
}
}
}
var matrix = Matrix(rows: 2, columns: 2)
matrix[0, 1] = 1.5
matrix[1, 0] = 3.2
let someValue = matrix[2, 2]
// 断言将会触发,因为 [2, 2] 已经超过了 matrix 的范围
- 你可以通过在 subscript 关键字之前写下 static 关键字的方式来表示一个类型下标。类型可以使用 class 关键字来代替 static,它允许子类重写父类中对那个下标的实现。
enum Planet: Int {
case mercury = 1, venus, earth, mars, jupiter, saturn, uranus, neptune
static subscript(n: Int) -> Planet {
return Planet(rawValue: n)!
}
}
let mars = Planet[4]
print(mars)
继承
- 一个类可以从另一个类继承方法、属性和其他特性。当一个类从另一个类继承时,继承的类被称为子类,而被它继承的类被称为父类。继承是 Swift 中区别类与其他类型的基本特性。
- Swift 中的类可以调用和访问属于其父类的方法、属性和下标,并且可以通过重写这些方法、属性和下标来优化或修改他们的行为。Swift 通过检查重写定义是否与父类定义相匹配来帮助确保您的覆盖是正确的。
- 类还可以为继承的属性添加属性观察器,以便在属性值发生变化时得到通知。无论最初是定义为存储属性还是计算属性,都可以为任何属性添加属性观察器。
- 如果一个类没有继承其他类,那他就是一个基类。Swift 中没有统一的基类,所有类的起源都是平等的。您不指定父类的类会自动成为基类。
class Vehicle {
var currentSpeed = 0.0
var description: String {
return "traveling at \(currentSpeed) miles per hour"
}
func makeNoise() {
// 不做任何事情 - 不是任何一辆车都会发出噪音。
}
}
- 子类化是基于现有类创建新类的行为。子类继承现有类的特性,然后您可以对其进行完善。您还可以向子类添加新的特性。要指示子类有一个父类,请在父类名前写子类名,中间用冒号分隔:
class Bicycle: Vehicle {
var hasBasket = false
}
let bicycle = Bicycle()
bicycle.hasBasket = true
bicycle.currentSpeed = 15.0
print("Bicycle: \(bicycle.description)")
// Bicycle: traveling at 15.0 miles per hour
class Tandem: Bicycle {
var currentNumberOfPassengers = 0
}
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 关键字的重写在编译代码时都会被诊断为错误。
class Train: Vehicle {
override func makeNoise() {
print("Choo Choo")
}
}
let train = Train()
train.makeNoise()
// 打印 "Choo Choo"
- 您可以重写继承的实例或类型属性,为该属性提供自己的自定义 getter 和 setter,或添加属性观察器以使重写的属性能够观察底层属性值的变化。
- 您可以为任何继承的属性提供自定义 getter(如果需要的话还有 setter),无论继承的属性在源码中是作为存储属性还是计算属性实现的。
- 您可以通过在子类属性重写中提供 getter 和 setter 来将继承的只读属性表示为可读写属性。但是您不能将继承的可读写属性声明为只读属性。
class Car: Vehicle {
var gear = 1
override var description: String {
return super.description + " in gear \(gear)"
}
}
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 中观察任何值的变化。
class AutomaticCar: Car {
override var currentSpeed: Double {
didSet {
gear = Int(currentSpeed / 10.0) + 1
}
}
}
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 class)中在 class 关键字前写 final 修饰符来将整个类标记为 final。任何尝试子类化 final 类的行为都会在编译时报错。