站在汇编角度深入了解 Swift(七)

982 阅读5分钟

属性

  • 存储属性
    • 类似于成员变量这个概念
    • 存储在实例的内存中
    • 结构体、类可以定义存储属性
    • 枚举不可以定义存储属性
    • 创建类或者结构体时必须为所有的存储属性设置一个合适的初始值
  • 计算属性
    • 本质就是方法(函数)
    • 不占用实例的内存(就和平常的方法一样也不会占用的)
    • 结构体、类、枚举可以定义存储属性
    • 只读计算属性,只有 get,没有 set
    • 定义计算属性只能用 var,不能用 let,因为就算是只读属性,值也是可能会发生变化的
  • 延迟存储属性
    • 使用 lazy 可以定义一个延迟存储属性,在第一次用到属性的时候才会进行初始化
    • 当结构体包含一个延迟存储属性时,只有 var 才能访问延迟存储属性,因为延迟属性初始化时需要改变结构体的内存
struct Circle {
    // 存储属性
    var radius: Double 
    // 计算属性
    var diameter: Double {
        set {
            radius = newValue / 2
        }
        get {
            radius * 2 // 这里的 return 可以省略
        }
    }
}

从这里可以看出来,这个和平常的是不一样的,还有一个字节来存储 lazy 属性是否初始化了,如果没有这个 lazy 属性,打印的结果应该是24、24、8了

struct Point {
    var x = 10
    var y = 11
    lazy var z = 12
}

var point = Point()
printMemory(t1: point)
point.z = 10
printMemory(t1: point)
----------------------执行结果----------------------
(lldb) p point
(swiftstudy.Point) $R0 = {
  x = 10
  y = 11
  $__lazy_storage_$_z = nil
}
(lldb) x/5wg 0x7ffeefbff410
0x7ffeefbff410: 0x000000000000000a 0x000000000000000b
0x7ffeefbff420: 0x0000000000000000 0x0000000000000001
0x7ffeefbff430: 0x0000000000000000
(lldb) c
Process 26808 resuming
25
32
8
(lldb) x/5wg 0x7ffeefbff410
0x7ffeefbff410: 0x000000000000000a 0x000000000000000b
0x7ffeefbff420: 0x000000000000000a 0x0000000000000000
0x7ffeefbff430: 0x0000000000000000

思考:如果是类和平常是不是一样的?

其实和有 lazy 的结构体是相似的,都会把 lazy 包装成相应的 $__lazy_storage_$_z 这种形式。
class Point {
    var x = 10
    var y = 11
    lazy var z = 12
}

let point = Point()
printHeap(p: point)
point.z = 10
printHeap(p: point)

--------------------------执行结果-----------------------
(lldb) p point
(swiftstudy.Point) $R0 = 0x000000010121ede0 {
  x = 10
  y = 11
  $__lazy_storage_$_z = nil
}
(lldb) x/10wg 0x000000010121ede0
0x10121ede0: 0x000000010000ba28 0x0000000200000002
0x10121edf0: 0x000000000000000a 0x000000000000000b
0x10121ee00: 0x0000000000000000 0x0000000000000001
0x10121ee10: 0x0000000000000000 0x0000000000000000
0x10121ee20: 0x00007fff77fd3080 0x00000000e3aa30a7
48
48
(lldb) x/10wg 0x000000010121ede0
0x10121ede0: 0x000000010000ba28 0x0000000200000002
0x10121edf0: 0x000000000000000a 0x000000000000000b
0x10121ee00: 0x000000000000000a 0x0000000000000000

属性观察器

  • 可以为非 lazy 的 var 存储属性设置属性观察器
  • 类似于观察者模式
  • 可以用于局部变量和全局变量
struct Circle {
    var radius: Double = 10 {
        willSet {
            print("willSet", newValue)
        }
        didSet {
            print("didSet", oldValue, radius)
        }
    }
}

let c = Circle()
c.radius = 10

inout的本质总结

  • 如果实参有物理内存地址,且没有设置属性观察器
    • 直接将实参的内存地址传入函数(实参进行引用传递)
  • 如果实参是计算属性 或者 设置了属性观察器
    • 采取了 Copy In Copy Out 的策略
      • 调用该函数时,先复制实参的值,产生副本【get】
      • 将副本的内存地址传入函数(副本进行引用传递),在函数内部可以修改副本的值
      • 函数返回后,再将副本的值覆盖实参的值【set】
  • 总结:inout 的本质就是引用传递(地址传递)

类型属性

  • 实例属性:只能通过实例去访问
    • 存储实例属性:存储在实例的内存中,每个实例都有1份
    • 计算实例属性:
  • 类型属性:只能通过类型去访问
    • 存储类型属性:整个程序运行过程中,就只有1份内存(类似于全局变量),可以看成是全局变量(因为他的内存和全局变量是挨在一起的),只是加了一个限定的范围,就是定义他的结构体、类之类的
    • 计算类型属性
    • 不同于存储实例属性,你必须给存储类型属性设定初始值
      • 因为类型没有像实例那样的 init 初始化器来初始化存储属性
    • 存储类型属性默认就是 lazy,会在第一次使用的时候才初始化,就算被多个线程同时访问,也能保证只会初始化一次,并且可以是let,所以 swift 中可以直接使用 static let shared = Class() 来定义单例
      • 为什么保证了线程安全?因为底层调用了 swift_once,也就是调用了 GCD 中的 dispatch_once
      • static 修饰的指针指向的内存会不会释放,需要看他指向的内存在什么位置,static 只是将这个指针全局化,而不是将指针指向的内存全局化,不过如果是单例,那么内容也是全局化的
  • 可以通过 static 定义类型属性
    • 如果是类,也可以用关键字 class,如果在 class 中用 static 定义类型属性,那么这个类型属性,子类是不可以继承的