Swift之继承、构造过程
继承
在Swift
中 ,类可以调用和访问超类的方法,属性和下标脚本,并且可以重写这些方法,属性和下标脚本来优化或修改它们的行为。Swift
会检查你的重写定义在超类中是否有匹配的定义,保证重写的正确性。可以为类继承来的任何属性添加属性观察器,属性改变会被通知。
基类
不继承于其他类的类,称之为基类。
//定义一个基类
class Vehicle {
//存储属性
var currentSpeed = 0.0
//只读属性
var description : String {
return "traveling at \(currentSpeed) miles per hour"
}
//方法
func makeNoise() {
//什么也不做
}
}
let someVehicle = Vehicle()
print(someVehicle.description)
注意:Swift
中的类并不是从一个通用的基类继承,如果不指定超类,这个类自动成为基类。
子类
一个已有类的基础上创建一个新类,称之为子类生成。子类继承超类的特性,并且可以优化或者改变它,也可以添加新特性。
//继承Vehicle
class Bicycle: Vehicle {
var hasBasket = false
}
let bicycle = Bicycle()
bicycle.hasBasket = true
bicycle.currentSpeed = 15.0
print("Bicycle: \(bicycle.description)")
重写
子类可以为继承来的实例方法(instance method),类方法(class method),实例属性(instance property),或下标脚本(subscript)提供自己定制的实现。
注意:重写某个特性,需要添加关键字override。告诉编译器是重写,并不是相同的定义。
访问超类方法,属性及下标脚本
- 在方法
someMethod
重写中,使用super.someMethod
来调用父类的someMethod
方法。 - 在属性
someProperty
重写实现中,使用super.someProperty
来调用父类属性。 - 在下标脚本的重写中,可以通过
super[someIndex]
来访问超类版本中的相同下标脚本。
防止重写
只要在声明关键字前加上final
特性即可。
构造过程
构造过程是使用类、结构体或枚举类型一个实例的准备过程。在新实例可用前必须执行这个过程,具体操作包括设置实例中每个存储属性的初始值和执行其他必须的设置或初始化工作。
注意:我们可以使用构造器来实现构造过程。与Objective-C
不同,Swift
构造器无需返回值,主要任务是保证新实例在第一次使用前完成正确的初始化。
存储属性的初始赋值
类和结构体在创建实例时,必须为所有存储属性设置合适的初始值。存储属性可以在定义时赋值,也可以在构造器中赋值。
注意:当给存储属性设置默认值或者在构造器中为其赋值时,他们的值是被直接设置的,不会触发任何属性观察器。
自定义构造过程
struct Color {
let red, green, blue: Double
init(red: Double, green: Double, blue: Double) {
self.red = red
self.green = green
self.blue = blue
}
init(white: Double) {
red = white
green = white
blue = white
}
}
let magenta = Color(red: 1.0, green: 0.0, blue: 1.0)
let halfGray = Color(white: 0.5)
//注意:
let veryGreen = Color(0.0, 1.0, 0.0) //报错
注意:添加(_)
可以隐藏外部名,也可以自定义外部名。
值类型的构造器代理
//构造器可以通过调用其它构造器来完成实例的部分构造过程,称之为构造代理。
struct Size {
var width = 0.0, height = 0.0
}
struct Point {
var x = 0.0, y = 0.0
}
struct Rect {
var origin = Point()
var size = Size()
init(){}
init(origin: Point, size: Size) {
self.origin = origin
self.size = size
}
init(center: Point, size: Size) {
let originX = center.x - (size.width / 2)
let originY = center.y - (size.height / 2)
self.init(origin: Point(x: originX, y: originY), size: size)
}
}
//1、第一个构造器是空构造器, 调用返回一个Rect实例
let basicRect = Rect() //原点(0,0),尺寸(0,0)
//2、 第二个构造器
let originRect = Rect(center: Point(x: 2.0, y: 2.0), size: Size(width: 5.0, height: 5.0)) //原点(2,2),尺寸(5, 5)
//3、 第三个构造器
let centerRect = Rect(center: Point(x: 4.0, y: 4.0), size: Size(width: 3.0, height: 3.0)) //原点(2.5, 2.5) 尺寸(3.0, 3.0)
//: 先通过center和size的值计算出origin坐标。然后在调用给init(origin:size:)构造器来将新的origin和size值赋值到对应的属性中。
类的继承和构造过程
类中所有存储型属性,包括所有继承自父类的属性,都必须在构造器中设置初始值。Swift中两种类构造器:指定构造器和遍历构造器。
指定构造器和便利构造器
指定构造器是类中最主要的构造器。一个指定构造器将初始化类中提供的所有属性,并根据父类链往上调用父类的构造器来实现父类的初始化。每一个类都必须拥有至少一个指定构造器。许多类通过继承父类的指定构造器满足这个条件。
便利构造器是类中比较次要的、辅助型的构造器。可以定义便利构造器来调用同一个类中的指定构造器,并为其参数提供默认值。也可以通过便利构造器来创建一个特殊用途或特定输入的实例。
语法
类的指定构造器的写法同值类型简单构造器:
init(parameters) {
statements
}
便利构造器, 在init
之前添加convenience
关键字。
convenience init() {
statements
}
类构造器代理原则
为了简化指定构造器和便利构造器之间的调用关系,Swift采用三条限制构造器之间的代理调用:
- 指定构造器必须调用其直接父类的指定构造器。
- 便利构造器必须调用同一类中定义的其它构造器。
- 便利构造器必须最终以调用一个指定构造器结束。
记忆方法: - 指定构造器必须总是向上代理。
-
便利构造器必须总是横向代理。
构造器代理图.png
两段式构造过程
Swift中类的构造过程包括两段:
- 每个存储型属性通过引入它们类的构造器来设置初始值。
- 当每一个存储型属性值被确定后,第二阶段开始,它给每个类一次机会在新实例准备使用之前进一步定制它们的存储型属性。
两段式构造器作用:
两段式构造过程的使用让构造过程更安全,同时在整个类层级结构中给予了每个类完全的灵活性。两段式构造过程可以防止存储值在初始化之前被访问;也可以防止属性被另一个构造器意外地赋予不用的值。
两段式构造过程中基于安全检查的构造流程展示:
阶段1
- 某个指定构造器或便利构造器被调用;
- 完成新实例内存的分配,但此时内存还没有被初始化;
- 指定构造器确保其所在类引入的所有存储型属性都已赋初值。存储型属性所属的内存完成初始化;
- 指定构造器将调用父类的构造器,完成父类属性的初始化;
- 这个调用父类构造器的过程沿着构造器链一直往上执行,直到到达构造器链的最顶部;
- 当到达构造器链的最顶部,且已确保所有实例包含的存储型属性都已经赋值,这个实例的内存被认为已经完全初始化。此时阶段1完成。
阶段2
- 从顶部构造器链一直往下,每个构造器链中类的指定构造器都有机会进一步定制实例。构造器此时可以访问
self
、修改它的属性并调用实例方法等等。 - 最终,任意构造器链中的便利构造器可以有机会定制实例和使用
self
。
构造器的继承和重写
跟Objective-C中的子类不同,Swift中的子类不会默认继承父类的构造器。Swift的这种机制可以防止一个父类的简单构造器被一个更专业的子类继承,并被错误的用来创建子类的实例。
重写父类指定构造器时,需要添加override修饰符;子类便利构造器不能直接调用父类便利构造器,所以子类不必提供一个父类的构造器的重写。
例子:
class Vehicle {
var numberOfWheels = 0
var description: String {
return "\(numberOfWheels) wheels"
}
}
//为存储型属性提供默认值,未提供自定义构造器。自动生成一个默认构造器,默认构造器在类中是指定构造器,它可以用于创建属性叫numberOfWheels值为0的Vehicle属性。
let vehicle = Vehicle()
print("Vehicle: \(vehicle.description)")
class Bicycle: Vehicle {
override init() {
super.init()
numberOfWheels = 2
}
}
//自定义指定构造器init()。这个构造器和父类的指定构造器相匹配。所以需要加上override修饰符。
// supe.init() 可以保证在修改属性之前,能被父类初始化。
// 另外,只能修改继承来的变量属性,不能修改常量属性。
自动构造器的继承
子类默认不会继承父类的构造器。特定条件下,父类构造器是可以被自动继承的。
- 子类中没有定义任何指定构造器,它将自动继承所有父类的指定构造器。
- 子类提供所有的父类指定构造器的实现,可以是通过继承来的,也可以是自定义实现的。子类将自动继承所有父类的便利构造器。
指定构造器和便利构造器实例
例子:
class Food {
var name: String
init(name:String) {
self.name = name
}
convenience init() {
self.init(name:"[Unnamed]")
}
}
Food构造器链.png
let nameMeat = Food(name: "Bacon")
nameMeat.name // Bacon
Food
类中的构造器init(name: String)
被定义为一个指定构造器,因为它能够确保所有新Food
实例中存储型属性都被初始化。
Food
类同样提供一个没有参数的便利构造器init()
。这个构造器提供了一个默认的占位名字,通过代理调用同一类中定义的指定构造器,并给参数name传值来实现。
class RecipeIngredient: Food {
var quantity: Int
init(name: String, quantity: Int) {
self.quantity = quantity
super.init(name: name)
}
override convenience init(name: String) {
self.init(name: name, quantity: 1);
}
}
let oneMysteryItem = RecipeIngredient()
let oneBacon = RecipeIngredient(name: "Bacon")
let sixEggs = RecipeIngredient(name: "Eggs", quantity: 6)
RecipeIngredient构造器.png
-
RecipeIngredient
类拥有一个指定构造器init(name: String, quantity: Int)
, 它可以用来产生新实例的所有属性值。这个构造器先将传入的quantity
参数赋值新引入的属性quantity
。然后,将任务向上代理给父类的init(name: String)
。这个过程符合两段式构造过程。 -
便利构造器
init(name: String)
, 通过名字创建实例。这个便利构造器只是将任务代理给同一类里提供的指定构造器。注意:此便利构造器跟Food
中指定构造器init(name: String)
相同参数。重写类父类的指定构造器,必须要使用override
修饰符。 -
另外,子类继承父类便利构造器
init()
。
class ShoppingListItem: RecipeIngredient {
var purchased = false
var description: String {
var output = "\(quantity) x \(name)"
output += purchased ? " ?" : " ?"
return output
}
}
var breakfastList = [
ShoppingListItem(),
ShoppingListItem(name: "Bacon"),
ShoppingListItem(name: "Eggs", quantity: 6)
]
breakfastList[0].name = "Orange juice"
breakfastList[0].purchased = true
for item in breakfastList {
print(item.description)
}
//1 x Orange juice ?
//1 x Bacon ?
//6 x Eggs ?
由于自己引入的所有属性都提供了默认值,并没有定义任何构造器,自动继承所有父类中指定构造器和便利构造器。
三类构造器图.png