继承(Inheritance)

值类型(枚举、结构体)不支持继承,只有类支持继承
没有父类的类,称为:基类,Swift并没有像OC、Java那样的规定:任何类最终都要继承自某个基类。
子类可以重写父类的下标、方法、属性,重写必须加上override关键字
创建三个类,继承关系: ErHa 继承于 Dog 继承于 Animal

  1. class Animal {
  2. var age = 0
  3. }
  4. class Dog: Animal {
  5. var weight = 0
  6. }
  7. class ErHa: Dog {
  8. var iq = 0
  9. }

分别创建Animal、Dog、ErHa实例对象,观察内存信息:

  1. let a = Animal()
  2. a.age = 10
  3. print(Mems.size(ofRef: a)) // 32
  4. print(Mems.memStr(ofRef: a))
  5. // 一个Animal的实例对象占用32个字节
  6. /*
  7. 0x000000010000c3d0 8个字节:指向类型信息的地址
  8. 0x0000000000000003 8个字节:引用计数相关
  9. 0x000000000000000a 8个字节:保存age属性
  10. 0x0000000105a35830 8个字节:内存对齐
  11. */
  12. let d = Dog()
  13. d.age = 10
  14. d.weight = 20
  15. print(Mems.size(ofRef: d)) // 32
  16. print(Mems.memStr(ofRef: d))
  17. // 一个Dog的实例对象占用32个字节
  18. /*
  19. 0x000000010000c480 8个字节:指向类型信息的地址
  20. 0x0000000000000003 8个字节:引用计数相关
  21. 0x000000000000000a 8个字节:保存age属性
  22. 0x0000000000000014 8个字节:保存weight属性
  23. */
  24. let e = ErHa()
  25. e.age = 10
  26. e.weight = 20
  27. e.iq = 30
  28. print(Mems.size(ofRef: e)) // 48
  29. print(Mems.memStr(ofRef: e))
  30. // 一个ErHa的实例对象占用48个字节
  31. /*
  32. 0x000000010000c550 8个字节:指向类型信息的地址
  33. 0x0000000000000003 8个字节:引用计数相关
  34. 0x000000000000000a 8个字节:保存age属性
  35. 0x0000000000000014 8个字节:保存weight属性
  36. 0x000000000000001e 8个字节:保存iq属性
  37. 0x0003000000000078 8个字节:内存对齐
  38. */

重写实例方法、下标

  1. class Animal {
  2. func speak() {
  3. print("Animal speak")
  4. }
  5. subscript(index: Int) -> Int {
  6. return index
  7. }
  8. }
  9. var a = Animal()
  10. a.speak()
  11. print(a[6])
  12. class Dog: Animal {
  13. /// 重写实例方法
  14. override func speak() {
  15. super.speak()
  16. print("Dog speak")
  17. }
  18. /// 重写下标
  19. override subscript(index: Int) -> Int {
  20. return super[index] + 1
  21. }
  22. }
  23. var d = Dog()
  24. d.speak()
  25. print(d[6])

打印结果:

  1. Animal speak
  2. 6
  3. Animal speak
  4. Dog speak
  5. 7

重写类型方法、下标

被class修饰的类型方法、下标,允许被子类重写

  1. class Animal {
  2. class func speak() {
  3. print("Animal speak")
  4. }
  5. class subscript(index: Int) -> Int {
  6. return index
  7. }
  8. }
  9. Animal.speak()
  10. print(Animal[6])
  11. class Dog: Animal {
  12. override class func speak() {
  13. super.speak()
  14. print("Dog speak")
  15. }
  16. override class subscript(index: Int) -> Int {
  17. return super[index] + 1
  18. }
  19. }
  20. Dog.speak()
  21. print(Dog[6])

打印结果:

  1. Animal speak
  2. 6
  3. Animal speak
  4. Dog speak
  5. 7

被static修饰的类型方法、下标、不允许被子类重写
image.png

重写属性

1、子类可以将父类的属性(存储、计算)重写为计算属性
2、子类不可以将父类属性重写为存储属性
3、只能重写var属性,不能重写let属性
4、重写时,属性名、类型要一致
5、子类重写后的属性权限,不能小于父类的属性权限

  • 如果如类属性是只读的,那么子类重写后的属性可以是只读的、也可以是可读写的
  • 如果父类属性是可读写的,那么子类重写后的属性也必须是可读写的

    重写实例属性

    ```swift class Point { /// 存储属性 var x: Int = 0

    /// 计算属性 var y: Int {

    1. set {
    2. print("Point set y")
    3. x = newValue / 2
    4. }
    5. get {
    6. print("Point get y")
    7. return x * 2
    8. }

    } }

class SubPoint: Point {

  1. /// 重写x为计算属性
  2. override var x: Int {
  3. set {
  4. print("SubPoint set x")
  5. super.x = newValue
  6. }
  7. get {
  8. print("SubPoint get x")
  9. return super.x
  10. }
  11. }
  12. /// 重写y为计算属性
  13. override var y: Int {
  14. set {
  15. print("SubPoint set y")
  16. super.y = newValue
  17. }
  18. get {
  19. print("SubPoint get y")
  20. return super.y
  21. }
  22. }

}

let sp = SubPoint() print(sp.y)

  1. 打印结果:

SubPoint get y Point get y SubPoint get x 0

  1. 分析:<br />sp.y调用的是子类yget方法,所以打印SubPoint get y<br />子类yget方法中调用了super.y,所以打印了Point get y<br />父类yget方法调用了x * 2,但是当前调用对象是SubPoint,会走到子类xget方法,所以打印了SubPoint get x<br />从父类继承过来的存储属性,都会分配存储空间,无论是否被重写为计算属性。
  2. <a name="UoYV3"></a>
  3. ## 重写类型属性
  4. class修饰的计算类型属性,可以被子类重写<br />被static修饰的类型属性(存储、计算),不可以被子类重写<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/742903/1642669285692-398ceba7-88ae-4aec-8201-e8c40199188e.png#clientId=u41871de7-1af0-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=313&id=u85e380aa&margin=%5Bobject%20Object%5D&name=image.png&originHeight=313&originWidth=728&originalType=binary&ratio=1&rotation=0&showTitle=false&size=40787&status=done&style=none&taskId=u3e026015-23c6-4270-aed7-820aa364974&title=&width=728)
  5. <a name="o3pJj"></a>
  6. # 属性观察器
  7. 可以在子类中为父类属性(除了只读计算属性、let属性)增加属性观察器
  8. ```swift
  9. class Point {
  10. var x: Int = 0
  11. }
  12. class SubPoint: Point {
  13. override var x: Int {
  14. willSet {
  15. print("SubPoint willSet x", newValue)
  16. }
  17. didSet {
  18. print("SubPoint didSet x", x, oldValue)
  19. }
  20. }
  21. }
  22. var p = SubPoint()
  23. p.x = 10

打印结果:

  1. SubPoint willSet x 10
  2. SubPoint didSet x 10 0

如果父类有自己的属性观察器:

  1. class Point {
  2. var x: Int = 0 {
  3. willSet {
  4. print("Point willSet x", newValue)
  5. }
  6. didSet {
  7. print("Point didSet x", x, oldValue)
  8. }
  9. }
  10. }
  11. class SubPoint: Point {
  12. override var x: Int {
  13. willSet {
  14. print("SubPoint willSet x", newValue)
  15. }
  16. didSet {
  17. print("SubPoint didSet x", x, oldValue)
  18. }
  19. }
  20. }
  21. var p = SubPoint()
  22. p.x = 10

打印结果:

  1. SubPoint willSet x 10
  2. Point willSet x 10
  3. Point didSet x 10 0
  4. SubPoint didSet x 10 0

如果父类有一个计算属性

  1. class Point {
  2. var x: Int {
  3. set {
  4. print("Point set x", newValue)
  5. }
  6. get {
  7. print("Point get x")
  8. return 0
  9. }
  10. }
  11. }
  12. class SubPoint: Point {
  13. override var x: Int {
  14. willSet {
  15. print("SubPoint willSet x", newValue)
  16. }
  17. didSet {
  18. print("SubPoint didSet x", x, oldValue)
  19. }
  20. }
  21. }
  22. var p = SubPoint()
  23. p.x = 10

打印结果:

  1. Point get x
  2. SubPoint willSet x 10
  3. Point set x 10
  4. Point get x
  5. SubPoint didSet x 0 0

第一行打印是获取的oldValue

final

被final修饰的方法、下标、属性,禁止被重写
被final修饰的类,禁止被继承
image.png

多态

结构体

  1. struct Animal {
  2. func speak() {
  3. print("Animal speak")
  4. }
  5. func eat() {
  6. print("Animal eat")
  7. }
  8. func sleep() {
  9. print("Animal sleep")
  10. }
  11. }
  12. var animal = Animal()
  13. animal.speak()
  14. animal.eat()
  15. animal.sleep()

定一个结构体,执行三个方法,观察汇编代码
image.png
调用方法时,直接调用了函数地址,原因是struct不存在继承,所以编译完函数的地址就是确定的。

  1. class Animal {
  2. func speak() {
  3. print("Animal speak")
  4. }
  5. func eat() {
  6. print("Animal eat")
  7. }
  8. func sleep() {
  9. print("Animal sleep")
  10. }
  11. }
  12. var animal = Animal()
  13. animal.speak()
  14. animal.eat()
  15. animal.sleep()

观察汇编代码:
image.png
函数的地址是不确定的,是通过计算得出的,在编译时是不确定的。

实现原理探究

  1. class Animal {
  2. func speak() {
  3. print("Animal speak")
  4. }
  5. func eat() {
  6. print("Animal eat")
  7. }
  8. func sleep() {
  9. print("Animal sleep")
  10. }
  11. }
  12. class Dog: Animal {
  13. override func speak() {
  14. print("Dog speak")
  15. }
  16. override func eat() {
  17. print("Dog eat")
  18. }
  19. func run() {
  20. print("Dog run")
  21. }
  22. }
  23. var animal = Animal()
  24. animal.speak()
  25. animal.eat()
  26. animal.sleep()
  27. // 父类类型指针,指向子类类型
  28. animal = Dog()
  29. animal.speak()
  30. animal.eat()
  31. animal.sleep()

打印结果:

  1. Animal speak
  2. Animal eat
  3. Animal sleep
  4. Dog speak
  5. Dog eat
  6. Animal sleep

以dog变量调用sleep方法为例,流程是通过dog指针找到堆空间的dog对象,找到dog对象中的前8个字节
这8个字节是一个指针,指向一块内存区域,这块内存前面保存着dog的类型信息,后面存放了Dog和父类Animal的方法信息。
通过打印这块内存的地址,可以发现是保存在内存的全局区。