从零学习Swift 08: 继承体系
一: 方法
方法的定义:
func 方法名(参数列表) -> 返回值类型{
方法体
}
同 OC 一样, Swift 也有实例方法和对象方法:
class Person{
//实例方法
func run(){
}
//类型方法
static func eat(){
}
}
var person = Person()
//通过实例调用
person.run()
//通过类调用
Person.eat()
在 Swift 中,类,结构体,枚举都可以定义方法.需要注意的是:swift 语法规定:结构体,枚举的实例方法默认不能修改自身的属性,如果想要修改自身属性,需要在方法前加上
mutating`关键字:
如果要在值类型的实例方法内部修改值类型的属性,需要在实例方法前加上mutating
关键字:
如果调用一个方法,它的返回值没有被使用,编译器会报出警告:
返回值未被使用警告
可以使用@discardableResult
关键字消除警告:
另外Swift 语法规定:类型方法中不能访问实例属性:
类型方法中不能访问实例属性二: 下标
如果我们想像访问数组一样array[0]
,通过下标访问自己写的类的属性.可以使用subscript
关键字给任意类型增加下标功能.
//下标
class Person{
var age = 18
var height = 175
subscript(index: Int) -> Int{
get{
if index == 0{
return age
}else if index == 1{
return height
}
return 0
}
set{
if index == 0{
age = newValue
}else if index == 1{
height = newValue
}
}
}
}
var p1 = Person()
print(p1.age, p1.height)
p1[0] = 22
p1[1] = 180
print(p1.age, p1.height)
//打印结果
18 175
22 180
可以看到下标和计算属性很像,都有get , set
方法.其实下标的本质就是方法,只不过下标不是用func
定义,而是使用subscript
定义.
同计算属性一样,下标可以没有set
方法,只有get
方法.如果只有get
方法,get
可以省略.
使用下标时需要注意一个细节,只有当下标的返回值是class
类型时,外部才可以修改,如图:
如果下标的返回值是struct
类型,默认外部是不可以修改此返回值的,如图:
如果要想修改,必须加上set
方法,如图:
set
方法中的newValue
是Point
类型,为什么我们我们赋值的实收11 , 22
可以直接赋值成功呢?其实p[0].x = 11 , p[0].y = 22
的本质是这样的:
// p[0].x = 11
Point(x: 11, y: p[0].y)
// p[0].y = 22
Point(x: p[0].x, y: 22)
总结:
下标返回的对象外面是不是可以直接修改?
-
class
类型可以直接修改 -
struct
类型必须加上set
方法才可以修改
其实通过下标访问属性的本质就是调用get
,set
方法,可以通过汇编证实这一点:
三: 继承
继承是面向对象语言的三大特性之一.在 Swift 语言中,只有类支持继承,结构体和枚举不支持继承.
- 继承的内存结构
//继承的内存结构
class Person{
var age = 10
}
class Father: Person {
var height = 180
}
class Son: Father{
var firstName = "王"
}
var person = Person()
var laowang = Father()
var xiaoWang = Son()
如上所示的代码,person , laowang , xiaoWang
堆空间的内存布局如下:
从上图可以看出来,子类会用继承父类的属性,并且会有额外的空间存储从父类继承过来的属性.
- 重写
override
子类可以重写父类的下标 , 方法 , 属性
,必须加上override
关键字.
2.1 重新写实例方法 , 下标
//重写实例方法下标
class Father {
var height = 180
func eat(){
print("Father eat")
}
subscript(index: Int) -> Int{
print("Father index")
return height
}
}
class Son: Father{
var firstName = "王"
//重写父类实例方法
override func eat() {
print("Son eat")
}
//重写父类下标
override subscript(index: Int) -> Int {
print("Son index")
return super.height
}
}
2.2 重写类型方法 , 下标
//重写类型方法下标
class Father {
var height = 180
class func eat(){
print("Father eat")
}
class subscript(index: Int) -> Int{
print("Father index")
return index
}
}
class Son: Father{
var firstName = "王"
//重写父类类型方法
override class func eat() {
print("Son eat")
}
//重写父类类型下标
override class subscript(index: Int) -> Int {
print("Son index")
return super[index]
}
}
之前我们讲过,类型方法可以通过class
,static
定义.需要注意的是,通过class
定义的方法,下标允许被子类重写;通过static
定义的方法 , 下标不允许被子类重写.
如图:
static 定义的方法下标不允许被重写2.3 重写实例属性
子类可以将父类的存储属性,计算属性重写为计算属性
.注意:只能重写为计算属性.
//重写实例属性
class Father{
var age = 30
var money: Int{
get{
print("Father getMoney")
return age * 10
}
set{
print("Father setMoney")
age = newValue / 10
}
}
}
var person : Father
person = Father()
class Son: Father {
override var age: Int{
set{
print("Son setAge")
super.age = newValue > 0 ? newValue : 0
}
get{
print("Son getAge")
return super.age
}
}
override var money: Int{
set{
print("Son setMoney")
super.money = newValue > 0 ? newValue : 0
}
get{
print("Son getMone")
return super.money
}
}
}
print("---------------------------------------------------------")
person = Son()
//Son setAge
person.age = 10
print("---------------------")
//Son getMone
//Father getMone
//Son getAge
//100
print(person.money)
print("---------------------")
//Son setMoney
//Father setMoney
//Son setAge
person.money = 20
print("---------------------")
//Son getAge
//2
print(person.age)
以上代码就是子类把父类的存储属性和计算属性都重写成了计算属性.并且把每条打印语句的执行步骤都注释出来.
需要单独解释一下print(person.money)
这条语句的执行步骤:
首先person.money
此时person
是Son
对象的实例,执行步骤如下:
1: 所以先执行Son.money
的get
方法,打印Son getMone
;
2: 然后调用super.money
执行父类的getMoney
方法打印Father getMoney
;
3: 在Father getMoney
中又访问了age
(return age * 10),而子类有把age
属性重写成了计算属性(set , get
),所以会执行子类的Son getAge
4: 最后输出 100
2.4 重写类型属性
通过static
修饰的属性不可以被子类重写,通过class
修饰的属性才可以被子类重写.
子类重写父类类型属性,及调用步骤:
//重写类型属性
class Father{
static var age = 30
class var money: Int{
get{
print("Father getMoney")
return age * 10
}
set{
print("Father setMoney")
age = newValue / 10
}
}
}
var person : Father
person = Father()
class Son: Father {
override static var money: Int{
set{
print("Son setMoney")
super.money = newValue > 0 ? newValue : 0
}
get{
print("Son getMone")
return super.money
}
}
}
Father.age = 10
//Father getMoney
//100
print(Father.money)
print("---------------------")
//Father setMoney
Father.money = 20
print("---------------------")
//2
print(Father.age)
print("---------------------")
Son.age = 10
//Son getMone
//Father getMoney
//100
print(Son.money)
print("---------------------")
//Son setMoney
//Father setMoney
Son.money = 20
print("---------------------")
2.5 通过重写为父类属性添加属性观察器
我们还可以通过override
为父类属性添加属性观察器.但是不能为let属性 , 或者只读的计算属性
添加.因为
let属性和只读计算属性都不允许更改值,所以根本没必要添加属性观察器.
下面代码就实现了为父类的存储属性 , 计算属性
设置属性观察器:
//子类给父类添加属性观察器
class Father{
//存储属性
var age: Int = 20
//计算属性
var money: Int{
get{
print("Father getMoney")
return age * 10
}
set{
print("Father setMoney")
age = newValue / 10
}
}
}
class Son: Father {
//重写父类的实例存储属性,并设置属性观察器
override var age: Int{
willSet{
print("Son willSetAge :",newValue)
}
didSet{
print("Son didSetAge : ",age)
}
}
//重写父类的计算属性,并设置属性观察器
override var money: Int{
willSet{
print("Son willSetMoney : ",newValue)
}
didSet{
print("Son didSetMoney : ",money)
}
}
}
var son = Son()
son.money = 16
以上代码的打印顺序是这样的:
打印顺序我们对上面的打印顺序做一下解释:
-
首先第一行打印的
Father getMoney
,我们在为有属性观察器的的属性赋值的时候,会首先调用他的get
方法,拿到原来的值. -
第二行
Son willSetMoney
.这个不用解释,都明白. -
第三行
Father setMoney
.在给子类Son.mone
赋值的时候会先给它的父类Father.money
赋值,所以打印这一句. -
第四行
Son willSetAge
.在执行第三行的Father setMoney
中有一句age = newValue / 10
.给age
赋值,而子类为父类添加了属性观察器,所以会走子类的Son willSetAge
-
第五行
Son didSetAge
.第四行执行完,不解释 -
第六行
Father getMoney
.这一行是因为在didSetMoney
中访问了money
,如图:
- 第七行
Son didSetMoney
.设置完毕,不解释.
final
如果我们不想让我们的类被外界继承;或者不想让我们写的方法,属性,下标被重写,可以加上final
关键字.
结尾在补充一下static , class
两个关键字的区别,虽然这两个关键字都是用来修饰类型级别的方法或者属性的,但是还是些区别: