一、 存储属性
存储属性,分两种:let修饰的常量存储属性
;var修饰的变量存储属性
。
还用之前的代码:
class Person {
var age: Int = 33
var name: String = "chen"
}
let t = Person()
特点:占用分配实例对象的内存空间,即,堆空间。
二、 计算属性
类
、结构体
和枚举
可以定义计算属性,计算属性不直接存储值,而是提供一个getter
和一个可选的 setter
,来间接获取和设置其他属性或变量的值。
必须使用 var
关键字定义计算属性,只有 getter 没有 setter
的计算属性叫只读计算属性
,不能设置新的值。
特点:不占用内存空间,本质是
set/get
方法。
以下代码是否正确?
运行会崩溃,原因是
age
的set
方法中调用age.set
导致了循环引用,即递归
。代码验证计算属性:
class Circle {
var radius: Double = 2;
var area: Double{
get{
return 3.14 * radius * radius
}
set{
radius = sqrt(newValue / 3.14)
}
}
}
print(class_getInstanceSize(Circle.self))
//打印结果:24
从结果可以看出,类Circle的内存大小是:24 = 类自带16字节 + radius(8字节)
,是没有加上area
的。也就是说, area属性没有占有内存空间
。
我们也可以通过SIL,验证一下:
swiftc -emit-sil main.swift |xcrun swift-demangle >> ./main.sil && open main.sil
SIL如下:
class Circle {
@_hasStorage @_hasInitialValue var radius: Double { get set }
var area: Double { get set }
@objc deinit
init()
}
存储属性
,有_hasStorage
的标识符。计算属性
只有get、set
方法。
三、 属性观察器
class Teacher {
var name: String = "chinese"{
//新值存储之前调用
willSet{
print("willSet newValue \(newValue)")
}
//新值存储之后调用
didSet{
print("didSet oldValue \(oldValue)")
}
}
}
let t: Teacher = Teacher();
t.name = "english"
//打印结果:
//willSet newValue english
//didSet oldValue chinese
特点:
1、在当前类的init
方法中,如果调用属性,是不会
触发属性观察者的(因为内存安全,不确定变量是否初始化结束);触发get、set
2、在子类的init
方法中,如果调用属性,会触发属性观察者。也是因为内存安全,子类调用了父类的init,已经初始化过了,而初始化流程保证了所有属性都有值(即super.init确保变量初始化完成了),所以可以观察属性了
3、对于同一个属性,子类和父类
都有属性观察者,其顺序是:先子类willset,后父类willset,在父类didset, 子类的didset,即:子父 父子
四、延迟属性
延迟属性,即,使用lazy修饰
的存储属性。特点:
- 必须有一个
默认
的初始值。- 第一次访问的时候才被赋值
- 不能保证线程安全
- 影响实例对象内存的大小
class Teacher {
lazy var age: Int = 18
}
let t = Teacher()
print("-----Teacher-------")
print(t.age)
print(class_getInstanceSize(Teacher.self))
print("-----Int-------")
print(MemoryLayout<Int>.size)
print(MemoryLayout<Optional<Int>>.size)
print(MemoryLayout<Optional<Int>>.stride)
断点调试:
查看内存,前面两个内存段,分别是Metadata、refCounted,第三个应该是age属性,但是此时内存值是0。
继续打印:
-----Teacher-------
18
32
-----Int-------
8
9
16
此时t.age的值正常打印,查看源码发现,lazy属性默认是Optional类型的
,只有set
和get
方法,也就是默认值为nil
。第一次调用get方法时,才会被初始化。
关于内存,如果age
属性没有lazy
修饰, class_getInstanceSize(Teacher.self) = 24
;有lazy修饰
时,是32
,这个通过打印可以看到,Optional<Int>
内存占9字节
,内存对齐
,所以是32
。
五、 类型属性
使用关键字static修饰
,且是一个全局变量
。特点
- 类型属性必须有一个
默认的初始值
;- 类型属性只会被
初始化一次
。
参考:Swift-属性