属性
- 存储属性
- 类似于成员变量这个概念
- 存储在实例的内存中
- 结构体、类可以定义存储属性
- 枚举不可以定义存储属性
- 创建类或者结构体时必须为所有的存储属性设置一个合适的初始值
- 计算属性
- 本质就是方法(函数)
- 不占用实例的内存(就和平常的方法一样也不会占用的)
- 结构体、类、枚举可以定义存储属性
- 只读计算属性,只有 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】
- 采取了 Copy In Copy Out 的策略
- 总结:inout 的本质就是引用传递(地址传递)
类型属性
- 实例属性:只能通过实例去访问
- 存储实例属性:存储在实例的内存中,每个实例都有1份
- 计算实例属性:
- 类型属性:只能通过类型去访问
- 存储类型属性:整个程序运行过程中,就只有1份内存(类似于全局变量),可以看成是全局变量(因为他的内存和全局变量是挨在一起的),只是加了一个限定的范围,就是定义他的结构体、类之类的
- 计算类型属性
- 不同于存储实例属性,你必须给存储类型属性设定初始值
- 因为类型没有像实例那样的 init 初始化器来初始化存储属性
- 存储类型属性默认就是 lazy,会在第一次使用的时候才初始化,就算被多个线程同时访问,也能保证只会初始化一次,并且可以是let,所以 swift 中可以直接使用 static let shared = Class() 来定义单例
- 为什么保证了线程安全?因为底层调用了 swift_once,也就是调用了 GCD 中的 dispatch_once
- static 修饰的指针指向的内存会不会释放,需要看他指向的内存在什么位置,static 只是将这个指针全局化,而不是将指针指向的内存全局化,不过如果是单例,那么内容也是全局化的
- 可以通过 static 定义类型属性
- 如果是类,也可以用关键字 class,如果在 class 中用 static 定义类型属性,那么这个类型属性,子类是不可以继承的