经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 移动开发 » iOS » 查看文章
Swift 进阶(七)方法、下标
来源:cnblogs  作者:FunkyRay  时间:2021/4/12 9:51:13  对本文有异议

方法(Method)

基本概念

枚举、结构体、类都可以定义实例方法、类型方法

  • 实例方法(Instance Method):通过实例对象调用
  • 类型方法(Type Method):通过类型调用

实例方法调用

  1. class Car {
  2. var count = 0
  3. func getCount() -> Int {
  4. count
  5. }
  6. }
  7. let car = Car()
  8. car.getCount()

类型方法用static或者class关键字定义

  1. class Car {
  2. static var count = 0
  3. static func getCount() -> Int {
  4. count
  5. }
  6. }
  7. Car.getCount()

类型方法中不能调用实例属性,反之实例方法中也不能调用类型属性

-w645
-w644

不管是类型方法还是实例方法,都会含有隐藏参数self

self在实例方法中代表实例对象

self在类型方法中代表类型

  1. // count等于self.count、Car.self.count、Car.count
  2. static func getCount() -> Int {
  3. self.count
  4. }

mutating

结构体和枚举是值类型,默认情况下,值类型的属性不能被自身的实例方法修改

func关键字前面加上mutating可以允许这种修改行为

  1. struct Point {
  2. var x = 0.0, y = 0.0
  3. mutating func moveBy(deltaX: Double, deltaY: Double) {
  4. x += deltaX
  5. y += deltaY
  6. }
  7. }
  1. enum StateSwitch {
  2. case low, middle, high
  3. mutating func next() {
  4. switch self {
  5. case .low:
  6. self = .middle
  7. case .middle:
  8. self = .high
  9. case .high:
  10. self = .low
  11. }
  12. }
  13. }

@discardableResult

func前面加上@discardableResult,可以消除函数调用后返回值未被使用的警告

  1. struct Point {
  2. var x = 0.0, y = 0.0
  3. @discardableResult mutating func moveX(deltaX: Double) -> Double {
  4. x += deltaX
  5. return x
  6. }
  7. }
  8. var p = Point()
  9. p.moveX(deltaX: 10)

下标(subscript)

基本概念

使用subscript可以给任意类型(枚举、结构体、类)增加下标功能

有些地方也翻译成:下标脚本

subscript的语法类似于实例方法、计算属性,本质就是方法(函数)

  1. class Point {
  2. var x = 0.0, y = 0.0
  3. subscript(index: Int) -> Double {
  4. set {
  5. if index == 0 {
  6. x = newValue
  7. } else if index == 1 {
  8. y = newValue
  9. }
  10. }
  11. get {
  12. if index == 0 {
  13. return x
  14. } else if index == 1 {
  15. return y
  16. }
  17. return 0
  18. }
  19. }
  20. }
  21. var p = Point()
  22. p[0] = 11.1
  23. p[1] = 22.2
  24. print(p.x) // 11.1
  25. print(p.y) // 22.2
  26. print(p[0]) // 11.1
  27. print(p[1]) // 22.2

subscript中定义的返回值类型决定了getter中返回值类型和setternewValue的类型

subscript可以接收多个参数,并且类型任意

  1. class Grid {
  2. var data = [
  3. [0, 1 ,2],
  4. [3, 4, 5],
  5. [6, 7, 8]
  6. ]
  7. subscript(row: Int, column: Int) -> Int {
  8. set {
  9. guard row >= 0 && row < 3 && column >= 0 && column < 3 else { return }
  10. data[row][column] = newValue
  11. }
  12. get {
  13. guard row >= 0 && row < 3 && column >= 0 && column < 3 else { return 0 }
  14. return data[row][column]
  15. }
  16. }
  17. }
  18. var grid = Grid()
  19. grid[0, 1] = 77
  20. grid[1, 2] = 88
  21. grid[2, 0] = 99

subscript可以没有setter,但必须要有getter,同计算属性

  1. class Point {
  2. var x = 0.0, y = 0.0
  3. subscript(index: Int) -> Double {
  4. get {
  5. if index == 0 {
  6. return x
  7. } else if index == 1 {
  8. return y
  9. }
  10. return 0
  11. }
  12. }
  13. }

subscript如果只有getter,可以省略getter

  1. class Point {
  2. var x = 0.0, y = 0.0
  3. subscript(index: Int) -> Double {
  4. if index == 0 {
  5. return x
  6. } else if index == 1 {
  7. return y
  8. }
  9. return 0
  10. }
  11. }

subscript可以设置参数标签

只有设置了自定义标签的调用才需要写上参数标签

  1. class Point {
  2. var x = 0.0, y = 0.0
  3. subscript(index i: Int) -> Double {
  4. if i == 0 {
  5. return x
  6. } else if i == 1 {
  7. return y
  8. }
  9. return 0
  10. }
  11. }
  12. var p = Point()
  13. p.y = 22.2
  14. print(p[index: 1]) // 22.2

subscript可以是类型方法

  1. class Sum {
  2. static subscript(v1: Int, v2: Int) -> Int {
  3. v1 + v2
  4. }
  5. }
  6. print(Sum[10, 20]) // 30

通过反汇编来分析

看下面的示例代码,我们将断点打到图上的位置,然后观察反汇编

-w710

看到其内部是会调用setter来进行计算

-w708
-w714

然后再将断点打到这里来看

-w552

看到其内部是会调用getter来进行计算
-w712
-w716

经上述分析就可以证明subscript本质就是方法调用

结构体和类作为返回值对比

看下面的示例代码

  1. struct Point {
  2. var x = 0, y = 0
  3. }
  4. class PointManager {
  5. var point = Point()
  6. subscript(index: Int) -> Point {
  7. set { point = newValue }
  8. get { point }
  9. }
  10. }
  11. var pm = PointManager()
  12. pm[0].x = 11 // 等价于pm[0] = Point(x: 11, y: pm[0].y)
  13. pm[0].y = 22 // 等价于pm[0] = Point(x: pm[0].x, y: 22)

如果我们注释掉setter,那么调用会报错

-w644

但是我们将结构体换成类,就不会报错了

-w624

原因还是在于结构体是值类型,通过getter得到的Point结构体只是临时的值(可以想成计算属性),并不是真正的存储属性point,所以会报错

通过打印也可以看出来要修改的并不是同一个地址值的point

-w716

但换成了类,那么通过getter得到的Point类是一个指针变量,而修改的是指向堆空间中的Point的属性,所以不会报错

继承(Inheritance)

基本概念

值类型(结构体、枚举)不支持继承,只有类支持继承

没有父类的类,叫做基类

Swift并没有像OC、Java那样的规定,任何类最终都要继承自某个基类

子类可以重新父类的下标、方法、属性,重写必须加上override

  1. class Car {
  2. func run() {
  3. print("run")
  4. }
  5. }
  6. class Truck: Car {
  7. override func run() {
  8. }
  9. }

内存结构

看下面几个类的内存占用是多少

  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. }
  10. let a = Animal()
  11. a.age = 10
  12. print(Mems.size(ofRef: a)) // 32
  13. print(Mems.memStr(ofRef: a))
  14. //0x000000010000c3c8
  15. //0x0000000000000003
  16. //0x000000000000000a
  17. //0x000000000000005f
  18. let d = Dog()
  19. d.age = 10
  20. d.weight = 20
  21. print(Mems.size(ofRef: d)) // 32
  22. print(Mems.memStr(ofRef: d))
  23. //0x000000010000c478
  24. //0x0000000000000003
  25. //0x000000000000000a
  26. //0x0000000000000014
  27. let e = ErHa()
  28. e.age = 10
  29. e.weight = 20
  30. e.iq = 30
  31. print(Mems.size(ofRef: e)) // 48
  32. print(Mems.memStr(ofRef: e))
  33. //0x000000010000c548
  34. //0x0000000000000003
  35. //0x000000000000000a
  36. //0x0000000000000014
  37. //0x000000000000001e
  38. //0x0000000000000000

首先类内部会有16个字节存储类信息和引用计数,然后才是属性,又由于堆空间分配内存的原则是16的倍数,所以内存空间占用分别为32、32、48

子类会继承自父类的属性,所以内存会算上父类的属性存储空间

重写实例方法、下标

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

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

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

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

-w571
-w646

但是被class修饰的类型方法、下标,子类重写时允许使用static修饰

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

但再后面的子类就不被允许了

-w634

重写属性

子类可以将父类的属性(存储、计算)重写为计算属性

  1. class Animal {
  2. var age = 0
  3. }
  4. class Dog: Animal {
  5. override var age: Int {
  6. set {
  7. }
  8. get {
  9. 10
  10. }
  11. }
  12. var weight = 0
  13. }

但子类不可以将父类的属性重写为存储属性

-w644
-w638

只能重写var属性,不能重写let属性

-w642

重写时,属性名、类型要一致

-w639

子类重写后的属性权限不能小于父类的属性权限

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

重写实例属性

  1. class Circle {
  2. // 存储属性
  3. var radius: Int = 0
  4. // 计算属性
  5. var diameter: Int {
  6. set(newDiameter) {
  7. print("Circle setDiameter")
  8. radius = newDiameter / 2
  9. }
  10. get {
  11. print("Circle getDiameter")
  12. return radius * 2
  13. }
  14. }
  15. }
  16. class SubCircle: Circle {
  17. override var radius: Int {
  18. set {
  19. print("SubCircle setRadius")
  20. super.radius = newValue > 0 ? newValue : 0
  21. }
  22. get {
  23. print("SubCircle getRadius")
  24. return super.radius
  25. }
  26. }
  27. override var diameter: Int {
  28. set {
  29. print("SubCircle setDiameter")
  30. super.diameter = newValue > 0 ? newValue : 0
  31. }
  32. get {
  33. print("SubCircle getDiameter")
  34. return super.diameter
  35. }
  36. }
  37. }
  38. var c = SubCircle()
  39. c.radius = 6
  40. print(c.diameter)
  41. c.diameter = 20
  42. print(c.radius)
  43. //SubCircle setRadius
  44. //SubCircle getDiameter
  45. //Circle getDiameter
  46. //SubCircle getRadius
  47. //12
  48. //SubCircle setDiameter
  49. //Circle setDiameter
  50. //SubCircle setRadius
  51. //SubCircle getRadius
  52. //10

从父类继承过来的存储属性,都会分配内存空间,不管之后会不会被重写为计算属性

如果重写的方法里的settergetter不写super,那么就会死循环

  1. class SubCircle: Circle {
  2. override var radius: Int {
  3. set {
  4. print("SubCircle setRadius")
  5. radius = newValue > 0 ? newValue : 0
  6. }
  7. get {
  8. print("SubCircle getRadius")
  9. return radius
  10. }
  11. }
  12. }

重写类型属性

class修饰的计算类型属性,可以被子类重写

  1. class Circle {
  2. // 存储属性
  3. static var radius: Int = 0
  4. // 计算属性
  5. class var diameter: Int {
  6. set(newDiameter) {
  7. print("Circle setDiameter")
  8. radius = newDiameter / 2
  9. }
  10. get {
  11. print("Circle getDiameter")
  12. return radius * 2
  13. }
  14. }
  15. }
  16. class SubCircle: Circle {
  17. override static var diameter: Int {
  18. set {
  19. print("SubCircle setDiameter")
  20. super.diameter = newValue > 0 ? newValue : 0
  21. }
  22. get {
  23. print("SubCircle getDiameter")
  24. return super.diameter
  25. }
  26. }
  27. }
  28. Circle.radius = 6
  29. print(Circle.diameter)
  30. Circle.diameter = 20
  31. print(Circle.radius)
  32. SubCircle.radius = 6
  33. print(SubCircle.diameter)
  34. SubCircle.diameter = 20
  35. print(SubCircle.radius)
  36. //Circle getDiameter
  37. //12
  38. //Circle setDiameter
  39. //10
  40. //SubCircle getDiameter
  41. //Circle getDiameter
  42. //12
  43. //SubCircle setDiameter
  44. //Circle setDiameter
  45. //10

static修饰的类型属性(计算、存储),不可以被子类重写

-w861

属性观察器

可以在子类中为父类属性(除了只读计算属性、let属性)增加属性观察器

重写后还是存储属性,不是变成了计算属性

  1. class Circle {
  2. var radius: Int = 1
  3. }
  4. class SubCircle: Circle {
  5. override var radius: Int {
  6. willSet {
  7. print("SubCircle willSetRadius", newValue)
  8. }
  9. didSet {
  10. print("SubCircle didSetRadius", oldValue, radius)
  11. }
  12. }
  13. }
  14. var circle = SubCircle()
  15. circle.radius = 10
  16. //SubCircle willSetRadius 10
  17. //SubCircle didSetRadius 1 10

如果父类里也有属性观察器,那么子类赋值时,会先调用自己的属性观察器willSet,然后调用父类的属性观察器willSet;并且在父类里面才是真正的进行赋值,然后先父类的didSet,最后再调用自己的didSet

  1. class Circle {
  2. var radius: Int = 1 {
  3. willSet {
  4. print("Circle willSetRadius", newValue)
  5. }
  6. didSet {
  7. print("Circle didSetRadius", oldValue, radius)
  8. }
  9. }
  10. }
  11. class SubCircle: Circle {
  12. override var radius: Int {
  13. willSet {
  14. print("SubCircle willSetRadius", newValue)
  15. }
  16. didSet {
  17. print("SubCircle didSetRadius", oldValue, radius)
  18. }
  19. }
  20. }
  21. var circle = SubCircle()
  22. circle.radius = 10
  23. //SubCircle willSetRadius 10
  24. //Circle willSetRadius 10
  25. //Circle didSetRadius 1 10
  26. //SubCircle didSetRadius 1 10

可以给父类的计算属性增加属性观察器

  1. class Circle {
  2. var radius: Int {
  3. set {
  4. print("Circle setRadius", newValue)
  5. }
  6. get {
  7. print("Circle getRadius")
  8. return 20
  9. }
  10. }
  11. }
  12. class SubCircle: Circle {
  13. override var radius: Int {
  14. willSet {
  15. print("SubCircle willSetRadius", newValue)
  16. }
  17. didSet {
  18. print("SubCircle didSetRadius", oldValue, radius)
  19. }
  20. }
  21. }
  22. var circle = SubCircle()
  23. circle.radius = 10
  24. //Circle getRadius
  25. //SubCircle willSetRadius 10
  26. //Circle setRadius 10
  27. //Circle getRadius
  28. //SubCircle didSetRadius 20 20

上面打印会先调用一次Circle getRadius是因为在设置值之前会先拿到它的oldValue,所以需要调用getter一次

为了测试,我们将oldValue的获取去掉后,再打印发现就没有第一次的getter的调用了

-w717

final

final修饰的方法、下标、属性,禁止被重写

-w643
-w644
-w640

final修饰的类,禁止被继承

-w642

方法调用的本质

我们先看下面的示例代码,分析结构体和类的调用方法区别是什么

  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 ani = Animal()
  13. ani.speak()
  14. ani.eat()
  15. ani.sleep()

反汇编之后,发现结构体的方法调用就是直接找到方法所在地址直接调用

结构体的方法地址都是固定的

-w715

下面我们在看换成类之后反汇编的实现是怎样的

  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 ani = Animal()
  13. ani.speak()
  14. ani.eat()
  15. ani.sleep()

反汇编之后,会发现需要调用的方法地址是不确定的,所以凡是调用固定地址的都不会是类的方法的调用

-w1189
-w1192
-w1190

-w1186
-w1185

-w1187
-w1189

而且上述的几个调用的方法地址都是从rcx往高地址偏移8个字节来调用的,也就说明几个方法地址都是连续的

我们再来分析下方法调用前做了什么

通过反汇编我们可以看到,会从全局变量的指针找到其指向的堆内存中的类的存储空间,然后再根据类的前8个字节里的类信息知道需要调用的方法地址,从类信息的地址进行偏移找到方法地址,然后调用

-w1140

然后我们将示例代码修改一下,再观察其本质是什么

  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 ani = Animal()
  24. ani.speak()
  25. ani.eat()
  26. ani.sleep()
  27. ani = Dog()
  28. ani.speak()
  29. ani.eat()
  30. ani.sleep()

增加了子类后,Dog的类信息里的方法列表会存有重写后的父类方法,以及自己新增的方法

  1. class Dog: Animal {
  2. func run() {
  3. print("Dog run")
  4. }
  5. }

如果子类里没有重写父类方法,那么类信息里的方法列表会有父类的方法,以及自己新增的方法

原文链接:http://www.cnblogs.com/funkyRay/p/swift-jin-jie-qi-fang-fa-xia-biao.html

 友情链接:直通硅谷  点职佳

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号