一、存储属性
存储属性是一个作为特定类和结构体实例一部分的常量或变量。存储属性要么是变量存储属性(由 var 关键字引入)要么是常量存储属性(由 let 关键字引入)。
二、计算属性
存储的属性是最常见的,除了存储属性,类、结构体和枚举也能够定义计算属性,计算属性并不存储值,他们提供getter
和setter
来修改和获取值。对于存储属性来说可以是常量或变量,但计算属性必须定义为变量。与此同时我们书写计算属性时候必须包含类型,因为编译器需要知道期望返回值是什么。
计算属性的本质就是 get和set方法。
struct square{
//实例当中占据内存
var width: Double
//不占据内存空间
var area: Double{
get{
return width * hegith
}
set{
self.width = newValue
}
}
}
三、属性观察者
属性观察者会观察用来观察属性值的变化,一个 willSet
当属性将被改变调用,即使这个值与 原有的值相同,而 didSet
在属性已经改变之后调用。它们的语法类似于 getter
和 setter
。
这里我们在使用属性观察器的时候,需要注意的一点是在初始化期间设置属性时不会调用 willSet 和 didSet 观察者;只有在为完全初始化的实例分配新值时才会调用它们。运行下面这段代码,你会发现当前并不会有任何的输出。
属性观察者只是对存储属性起作用,如下所示:
class SubjectName{
var subjectName: String = "[unnamed]"{
willSet{
print("subjectName will set value \(newValue)")
}
didSet{
print("subjectName has been changed \(oldValue)")
}
}
init(subjectName: String) {
self.subjectName = subjectName
}
}
let s = SubjectName(subjectName: "Swift进阶")
在继承中willset 和 didset的调用顺序,代码如下所示:
class Person{
var age: Int{
willSet{
print("age will set value \(newValue)")
}
didSet{
print("age has been changed \(oldValue)")
}
}
var height:Double
init(_ age: Int, _ height: Double) {
self.age = age
self.height = height
}
}
class Teacher:Person {
override var age: Int{
willSet{
print("override age will set value \(newValue)")
}
didSet{
print("override age has been changed \(oldValue)")
}
}
init(_ age:Int){
super.init(10,10)
self.age = age
}
}
运行打印:
override age will set value 20
age will set value 20
age has been changed 10
override age has been changed 10
以上的结果,也可以通过sil文件分析,分析过程在此省略。
四、延迟存储属性
延迟存储属性的初始值在其第一次使用时才进行计算。 用关键字 lazy 来标识一个延迟存储属性
五、类型属性
类型属性其实就是一个全局变量。
类型属性只会被初始化一次。
内存独占的概念
class Teacher{
static let sharedInstance = Teacher()
private init(){}
}
六、属性在MachO文件的位置信息
先看一下Metadata的元数据结构,
struct Metadata{
var kind: Int
var superClass: Any.Type
var cacheData: (Int, Int)
var data: Int
var classFlags: Int32
var instanceAddressPoint: UInt32
var instanceSize: UInt32
var instanceAlignmentMask: UInt16
var reserved: UInt16
var classSize: UInt32
var classAddressPoint: UInt32
var typeDescriptor: UnsafeMutableRawPointer
var iVarDestroyer: UnsafeRawPointer
}
方法调度里面认识了 typeDescriptor ,这里面记录了 V-Table 的相关信息,接下来在看一下 typeDescriptor 中的 fieldDescriptor
struct TargetClassDescriptor{
var flags: UInt32
var parent: UInt32
var name: Int32
var accessFunctionPointer: Int32
var fieldDescriptor: Int32 //存储属性信息
var superClassType: Int32
var metadataNegativeSizeInWords: UInt32
var metadataPositiveSizeInWords: UInt32
var numImmediateMembers: UInt32
var numFields: UInt32
var fieldOffsetVectorOffset: UInt32
var Offset: UInt32
var size: UInt32
//V-Table
}
fieldDescriptor 记录了当前的属性信息,其中fieldDescriptor 在源码中的结构如下:
struct FieldDescriptor {
MangledTypeName int32
Superclass int32
Kind uint16
FieldRecordSize uint16
NumFields uint32
FieldRecords [FieldRecord]
}
其中 NumFields 代表当前有多少个属性, FieldRecords 记录了每个属性的信息, FieldRecords 的机构体如下:
struct FieldRecord{
Flags uint32
MangledTypeName int32
FieldName int32
}
查看MachO文件
swift5_types 里面存放的是 TargetClassDescriptor
属性信息存储在 fieldDescriptor 中
找到typeDescriptor的地址:0xFFFFFF24 + 0xF60 = 0x100000E84(typeDescriptor在内存或者说是MachO文件中的位置,MachO中的位置需要减去虚拟内存地址0x100000000)
找到__const,进行定位 0xE84
000000A4里面存储的是偏移地址,也就是 0xE94 + 0xA4 = 0xF38, 就是 fieldDescriptor
结合上面结构体 FieldRecord 可以得到如下结果:
0xF48 + 0x8 + 0xFFFFFFDF - 0x100000000 = 0xF2F