一、存储属性-常量(let)VS 变量(var)
存储属性是一个作为特定类和结构体实例一部分的常量或变量。存储属性要么是变量存储属性(由var关键字引入)要么是常量存储属性(由let关键字引入)。存储属性这里没有什么特别要强调的,因为随处可见
let和var
class OSLetvarPorperty {
let x: Double = 0.0
var y: Double = 0.0
}
SIL比较
结论
常量和变量在内存方面没有区别,let常量不能被修改的原因是编译器没有为其自动生成set方法。
二、计算属性
存储的属性是最常⻅的,除了存储属性,类、结构体和枚举也能够定义计算属性,计算属性并不存储值,他们提供getter和setter来修改和获取值。对于存储属性来说可以是常量或变量,但计算属性必须定义为变量。于此同时我们书写计算属性时候必须包含类型,因为编译器需要知道期望返回值是什么。 计算属性的根本就是set、get方法.
class OSSquera {
//存储属性
var width: CGFloat = 0.0
//计算属性
var area: CGFloat {
get {
return width * width
}
set {
width = newValue
}
}
//只读计算属性
var area1: CGFloat {
get {
// return 可以省略
width * width
}
}
//set方法只有在该类内访问。根本还是存储属性
private(set) var area2: CGFloat = 0.0
}
观察其SIL
会发现area和area1没有@_hasStorage @_hasInitialValue
关键字,说明并没有内存存储情况。
三、观察属性
属性观察者会观察属性值的变化,一个willSet当属性将被改变调用,即使这个值与原有的值相同,而didSet在属性已经改变之后调用。它们的语法类似于getter和setter。
class OSObserveProperty {
var id : String = "" {
willSet {
print("id will be set!")
}
didSet {
print("id has been set!")
}
}
}
需要注意的是,初始化期间不会调用willSet 和 didSet,在初始化的过程中,是直接拿到属性的内存地址进行赋值,没有通过set方法。在初始化以下类的时候并没有出现打印。
class OSObserveProperty {
var id : String {
willSet {
print("id will be set!")
}
didSet {
print("id has been set!")
}
}
init(_ id: String) {
self.id = id
}
}
观察属性值能观察存储属性,对计算属性不起作用,计算属性也用不着,可以直接在set或者get方法中监听即可
继承情况下,同一属性的willSet和didSet在子类和父类的调用顺序是先调用子类的willSet,再调用父类的willSet,再调用父类的didSet,最后调用子类的didSet。
class OSObservePropertySub: OSObserveProperty {
override var id: String {
willSet {
print("subClasss id will be set!")
}
didSet {
print("subClasss id has been set!")
}
}
override init(_ id: String) {
super.init(id)
}
}
let subO = OSObservePropertySub("002")
subO.id = "003"
打印:
四、延迟存储属性
延迟存储属性的初始值在其第一次使用时才进行计算。用关键字lazy来标识一个延迟存储属性。
代码如下:
class OSLazyProperty {
lazy var height: Int = 20
}
在执行过程中使用断点调试观察其内存
在没有调用height属性之前,lazy对象应该存储height的内存是清零的。而在调用完之后,这块内存存入了0x14(20)值。
注意:延迟加载属性必须有初始值。这一点是为了确保swift的安全性。
五、类型属性
类型属性用关键字static
修饰
class OSStaticProperty {
static var userId = "000001"
}
观察其SIL
多了个全局变量,对其改变并生成SIL观察
class OSStaticProperty {
static var userId = "000001"
func setUserId() {
OSStaticProperty.userId = "11111"
}
}
再全局搜索该方法名。
可以看到once关键词,这个是sil中的一次性函数
可以在swift源码中搜索
swift_once
从这里我们可以衍生出swift中单例的写法。
class OSStaticProperty {
//单例的写法
static let manager = OSStaticProperty()
}
进一步优化,我们可以把置顶初始化器设置为私有属性,这样外部只能访问manager
来获取对象。
class OSStaticProperty {
//单例的写法
static let manager = OSStaticProperty()
private init(){}
注意:类型变量是可以被继承的。
六、属性Moch-O文件中的位置
在之前的文章我们在Mach-o找到了方法存储的地址。这次我们用类似的方法来寻找属性的虚拟地址。先来回顾一下Metadate的数据结构。
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
记录了每个属性的信息,FieldRecord
的结构如下:
struct FieldRecord{
Flags uint32
MangledTypeName int32
FieldName int32
}