Swift中跟实例相关的属性可以分为2大类,计算属性和存储属性

  1. struct Circle {
  2. // 存储属性
  3. var radius: Double
  4. // 计算属性
  5. var diameter: Double {
  6. set {
  7. radius = newValue / 2
  8. }
  9. get {
  10. radius * 2
  11. }
  12. }
  13. }

存储属性(Stored Property)
类似于成员变量的概念
存储在实例的内存中
结构体和类可以定义存储属性
枚举不可以定义存储属性

计算属性(Computed Property)
本质就是方法(函数)
不占用实例的内存
枚举、结构体、类都可以定义计算属性

存储属性

关于存储属性,Swift有个明确的规定
在创建类 或 结构体的实例时,必须为所有的存储属性设置一个合适的初始值

  • 可以在初始化器里为存储属性设置一个初始值 ```swift struct Point { var x: Int var y: Int }

var p = Point(x: 10, y: 20)

  1. - 可以分配一个默认的属性值作为属性定义的一部分
  2. ```swift
  3. struct Point {
  4. var x: Int = 10
  5. var y: Int = 20
  6. }
  7. var p = Point()

计算属性

set传入的新值默认叫做newValue,也可以自定义:

  1. struct Point {
  2. var x: Int = 10
  3. var y: Int {
  4. set(myNewValue) {
  5. x = myNewValue * 2
  6. }
  7. get {
  8. return x/2
  9. }
  10. }
  11. }

只读计算属性:只有get,没有set

  1. struct Point {
  2. var x: Int = 10
  3. var y: Int {
  4. get {
  5. 20
  6. }
  7. }
  8. }

计算属性只能用var,不能用let
(因为存储的内容是会变的)

枚举rawValue原理

  1. enum Season: Int {
  2. case spring = 1, summer, automn, winter
  3. var rawValue: Int {
  4. switch self {
  5. case .spring:
  6. return 11
  7. case .summer:
  8. return 22
  9. case .automn:
  10. return 33
  11. case .winter:
  12. return 44
  13. }
  14. }
  15. }
  16. var s = Season.summer
  17. print(s.rawValue) // 22

rawValue本质是只读的计算属性
可以通过汇编查看:

  1. 0x1000039dd <+93>: callq 0x100003af0 ; SwiftTest.Season.rawValue.getter : Swift.Int at <compiler-generated>

延迟存储属性(Lazy Stored Property)

使用lazy可以定义一个延迟存储属性,在第一次用到属性的时候才会进行初始化

  1. class Car {
  2. init() {
  3. print("Car init!")
  4. }
  5. func run() {
  6. print("Car is running!")
  7. }
  8. }
  9. class Person {
  10. /// 定义延迟存储属性
  11. lazy var car = Car()
  12. init() {
  13. print("Person init!")
  14. }
  15. func goOut() {
  16. car.run()
  17. }
  18. }
  19. var p = Person()
  20. print("-----------")
  21. p.goOut()

打印结果:

  1. Person init!
  2. -----------
  3. Car init!
  4. Car is running!

创建Person的过程中,没有创建car属性,在用到car属性时才会调用car的初始化器,进行创建。

  1. lazy var imageView: UIImageView = {
  2. let imageView = UIImageView()
  3. imageView.image = UIImage(named: "123.png")
  4. return imageView
  5. }()

imageView是一个存储属性,只不过是一个带有返回值的闭包表达式,用lazy修饰后,在使用时,就会执行闭包表达式里的内容。

延迟存储属性特点

lazy属性必须是var,不能是let,因为let必须在实例的初始化方法完成之前拥有值。
如果多条线程同时第一次访问lazy属性,无法保证属性只被初始化一次。

延迟存储属性注意点

当结构体包含一个延迟存储属性是,只有var才能访问延迟存储属性,因为延迟存储属性初始化时需要改变结构体的内存
image.png

属性观察器(Property Observer)

可以为非lazy的var存储属性设置属性观察器

  1. struct Point {
  2. var x: Int = 10
  3. var y: Int = 20 {
  4. willSet {
  5. print("willset - newValue = \(newValue), y = \(y)")
  6. }
  7. didSet {
  8. print("didset - oldValue = \(oldValue), y = \(y)")
  9. }
  10. }
  11. }
  12. var p = Point()
  13. p.y = 21

打印结果:

  1. willset - newValue = 21, y = 20
  2. didset - oldValue = 20, y = 21

willSet会传递新值,默认叫newValue
didSet会传递旧值,默认叫oldValue
在初始化器中设置属性值不会出发willset和didSet
在属性定义时设置初始值也不会出发willSet和didSet

全局变量、局部变量

属性观察器、计算属性的功能,同样可以应用在全局变量、局部变量身上

  1. /// 全局变量
  2. var num: Int {
  3. set {
  4. print("setNum", newValue)
  5. }
  6. get {
  7. 10
  8. }
  9. }
  1. /// 局部变量
  2. func test() {
  3. var age: Int = 10 {
  4. willSet {
  5. print("willSet", newValue)
  6. }
  7. didSet {
  8. print("didSet", oldValue, age)
  9. }
  10. }
  11. age = 11
  12. print("age = \(age)")
  13. }
  14. test()

inout的再次研究

  1. struct Point {
  2. /// 存储属性
  3. var x: Int
  4. /// 计算属性
  5. var y: Int {
  6. set {
  7. x = newValue / 2
  8. }
  9. get {
  10. x * 2
  11. }
  12. }
  13. func log() {
  14. print("point = (\(x), \(y))")
  15. }
  16. }
  17. /// inout参数
  18. func test(_ num: inout Int) {
  19. num = 20
  20. }
  21. // 创建point实例
  22. var p = Point(x: 1)
  23. p.log()
  24. // 传入p的计算属性作为inout参数
  25. test(&p.y)
  26. p.log()

打印结果:

  1. point = (1, 2)
  2. point = (10, 20)

证明计算属性也可以作为inout参数,原理是调用test方法前,会先调用y的get方法拿到y的值并创建一个局部变量,再调用test方法把这个局部变量的地址值进行赋值,最后调用y的set方法将局部变量赋值给y。可以通过汇编验证:

  1. 0x100003c49 <+57>: callq 0x100003cc0 ; SwiftTest.Point.y.getter : Swift.Int at main.swift:440
  2. 0x100003c4e <+62>: movq %rax, -0x28(%rbp)
  3. 0x100003c52 <+66>: leaq -0x28(%rbp), %rdi
  4. 0x100003c56 <+70>: callq 0x100004170 ; SwiftTest.test(inout Swift.Int) -> () at main.swift:451
  5. 0x100003c5b <+75>: movq -0x28(%rbp), %rdi
  6. 0x100003c5f <+79>: leaq 0x8562(%rip), %r13 ; SwiftTest.p : SwiftTest.Point
  7. 0x100003c66 <+86>: callq 0x100003d60 ; SwiftTest.Point.y.setter : Swift.Int at main.swift:437

inout的本质总结

如果实参有物理地址,且没有设置属性观察器:

  • 直接将实参的内存地址传入函数(实参进行引用传递)

如果实参是计算属性或者设置了属性观察器,采取了Copy In Copy Out的做法:

  • 调用该函数时,先复制实参的值,产生副本【get】
  • 将副本的内存地址传入函数(副本进行引用传递),在函数内部可以修改副本的值
  • 函数返回后,再将副本的值覆盖实参的值【set】

    类型属性(Type Property)

    严格来说属性可分为:
    实例属性(Instance Property):只能通过实例去访问

  • 存储实例属性(Store Instance Property):存储在实例的内存中,每个实例都有1份

  • 计算实例属性(Computed Instance Property)

类型属性(Type Property):只能通过类型去访问

  • 存储类型属性(Store Type Property):整个程序运行中,就只用1份内存(类似于全局变量)
  • 计算类型属性(Computed Type Property)

    1. struct Point {
    2. // 计算类型属性 x
    3. static var x: Int {
    4. get {
    5. 10
    6. }
    7. }
    8. // 存储类型属性 y
    9. static var y: Int = 20
    10. }

    可以通过static定一个类型属性
    如果是类,也可以用关键词class修饰计算属性
    类型属性举例: ```swift struct Car { static var count: Int = 0 init() {

    1. Car.count += 1

    } }

var c1 = Car() var c2 = Car() var c3 = Car() print(“(Car.count)”) // 3

  1. <a name="g49fC"></a>
  2. ## 类型属性的细节
  3. 不同于存储实例属性,你必须给存储类型属性设定初始值,因为类型没有实例那样的init初始化器来初始化存储属性。
  4. 类型存储类型属性默认就是lazy,会在第一次使用的时候才初始化,就算被多个线程同时访问,保证只会初始化一次。<br />类型存储属性可以是let
  5. 枚举类型也可以定义类型属性(存储类型属性、计算类型属性)
  6. <a name="Cs59c"></a>
  7. ## 类型属性和全局变量
  8. ```swift
  9. var num1 = 10
  10. class Car {
  11. static var count = 10
  12. }
  13. Car.count = 20
  14. var num2 = 12

num1、count、num2都是全局变量,只是count在访问时有限制(需要通过Car去访问),通过查看给count赋值的汇编代码:

  1. SwiftTest`Car.count.unsafeMutableAddressor:
  2. -> 0x100003f40 <+0>: pushq %rbp
  3. 0x100003f41 <+1>: movq %rsp, %rbp
  4. 0x100003f44 <+4>: cmpq $-0x1, 0x83c4(%rip) ; SwiftTest.num3 : Swift.Int + 7
  5. 0x100003f4c <+12>: sete %al
  6. 0x100003f4f <+15>: testb $0x1, %al
  7. 0x100003f51 <+17>: jne 0x100003f55 ; <+21> at main.swift:514:16
  8. 0x100003f53 <+19>: jmp 0x100003f5e ; <+30> at main.swift
  9. 0x100003f55 <+21>: leaq 0x83a4(%rip), %rax ; static SwiftTest.Car.count : Swift.Int
  10. 0x100003f5c <+28>: popq %rbp
  11. 0x100003f5d <+29>: retq
  12. 0x100003f5e <+30>: leaq -0x45(%rip), %rsi ; one-time initialization function for count at main.swift
  13. 0x100003f65 <+37>: leaq 0x83a4(%rip), %rdi ; one-time initialization token for count
  14. 0x100003f6c <+44>: callq 0x1000076e2 ; symbol stub for: swift_once
  15. 0x100003f71 <+49>: jmp 0x100003f55 ; <+21> at main.swift:514:16

由于类型存储类型属性默认就是lazy,所以调用了swift_once方法,内部其实是调用了dispatch_once方法,相当于:

  1. dispatch_once {
  2. count = 10
  3. }

当类型属性第一次被访问时是通过dispatch_once去初始化,也保证了线程安全。

单例模式

  1. class FileManager {
  2. // 类型存储属性
  3. public static let shared = FileManager()
  4. private init() {
  5. // do something
  6. }
  7. }
  8. // 访问
  9. let manager = FileManager.shared

static保证内存中只有一份,let保证不会在其他地方修改这个内存,这样就实现了单例功能。