一、 存储属性
存储属性,分两种: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-属性
