一、存储属性
存储属性 是一个类或者值类型(结构体,enum等)实例一部分的常量或变量,其存储属性一般有两种引入方式:
- 由
var
关键字定义的,叫变量存储属性 - 由
let
关键字定义的,叫常量存储属性
那么这两种有什么区别呢?在汇编层面,他们其实没有什么区别,但是在编译时他们主要的区别是:
-
let
修饰的常量,其值一旦设置就无法再修改,否则编译不通过 -
var
修饰的常量,其值可以随处设置不同的值
eg:
sil分析:
class PSYModel {
@_hasStorage var age: Int { get set }
@hasStorage final let name: String { get }
init( age: Int, _ name: String)
@objc deinit
}
// PSYModel.age.getter //age的getter方法
// PSYModel.age.setter //age的setter方法
// PSYModel.name.getter //name的getter方法
通过上面可以看出var
修饰的变量,具有 存储属性,并且具有getter
和setter
方法,所以其设置值之后还可被设置新值。而let
修饰的变量,被添加了final
标识,并且只有getter
方法,没有setter
方法,所以在被设置初值之后就不能再被设置其值。但是为啥在指定初始化器中怎么能够给它设置值呢?因为在init
方法中,是直接操作内存,存储到对应的内存的,并不需要调用setter
方法
二、计算属性
在swift中存储属性很常见,当然除了存储属性,类、结构体和枚举也能够定义计算属性。计算属性并不存储值,他们提供getter
和setter
方法来获取和修改值,并且不同于存储属性可以使用var
和let
关键字修饰,计算属性必须用var
修饰,并且定义变量的类型。如下:
sil文件struct square {
var width: Double
var area: Double{
get{
return width * width
}
set(newValue){
self.width = newValue
}
}
}
var s = square(width: 10.0)
s.area = 20
sil文件下area
具有setter
和getter
方法,但是并没有 hasStoreage
标识,也就是没有存储属性。直接打印其结构体的大小也可以发现area并不占用内存。
如果private (set)
修饰一个变量,说明其实具有存储属性的私有变量,只能在类或结构体代码块内可以访问,并且有setter
和getter
方法,只是将setter
方法设置为私有,外部只能调用getter
方法。
三、属性观察者
属性观察者会观察用来观察属性值的变化,当属性值即将改变时调用willSet
,当属性值改变之后调用didSet
。其方法类似于getter
和setter
方法,在初始化器内修改其值并不会触发调用willSet
和didSet
方法,因为初始化器是直接内存的操作。(计算属性不用添加观察者)
初始化器内不会触发 | 非初始化器赋值触发 |
---|---|
@_hasStorage var subName: String { get set }
也具有存储属性,其赋值的时候是由setter方法触发的,sil文件如下:
如果类存在继承,观察者属性调用又是啥样呢?
class PSYModel{
var age: Int
let name: String
init(_ age: Int, _ name: String, _ subName: String) {
self.age = age
self.name = name
self.subName = subName
}
var subName: String {
willSet{
print("PSY subName value willSet")
}
didSet{
print("PSY subName value didSet")
}
}
}
class LQRModel: PSYModel {
var sex: Int
override var subName: String{
willSet{
print("LQR subName value willSet")
}
didSet{
print("LQR subName value willSet")
}
}
init(_ sex: Int) {
self.sex = sex
super.init(18, "P", "SY")
}
}
var psy = LQRModel.init(1)
psy.subName = "spy"
print("end")
输出结果:****************************
LQR subName value willSet
PSY subName value willSet
PSY subName value didSet
LQR subName value willSet
end
Program ended with exit code: 0
image.png
四、延迟存储属性
延迟存储属性,其初始值是在第一次使用的时候才计算,使用关键字lazy
来标识一个延迟存储属性。延迟存储属性只能用let修饰,不能用let,且必须有值。
class PSYModel{
lazy var age: Int = 1
let name: String
init(_ name: String) {
self.name = name
}
}
var psy = PSYModel.init("PSY")
print("psy.age = \(psy.age)")
print("end")
lldb打印内存看一下其实例对象在初始化的时候并没有值,在使用的时候才有值:
那么它具体是怎么做到,使用的时候才有值呢?它实际是被编译成了一个
_age: Int?
可选类型的存储属性,并且生成了一个_age
初始化表达式,返回了一个枚举。sil文件 初始化表达式返回了一个枚举 age的getter方法 bb9是初始化值为6
可知,延迟存储属性延迟了内存的分配,相当于懒加载一样的效果,但是其并不是线程安全的,因为其并不能保证属性只被访问一次。
五、类型属性
类型属性其实是一个全局变量,只会被初始化一次,使用static
修饰,eg :
class PSYModel{
static var age: Int = 6
}
PSYModel.age = 18
将上面的代码生成.sil
代码看一下,可以发现其仍具有存储属性,比一般存储属性多了个static
修饰符,并且生成了一个全局变量age
:
main函数主要意思:
- 引用函数PSYModel.age.unsafeMutableAddressor
- 调用函数PSYModel.age.unsafeMutableAddressor并转成RawPointer原生指针
- 将原生指针指向Int数据类型
- 创建数值为18的数据Int64数据
- 将数据存入内存
PSYModel.age.unsafeMutableAddressor
到底做了啥?内存是如何分配的?继续解读sil文件的PSYModel.age.unsafeMutableAddressor
,可以发现其主要做了:
- 申请了一个全局的token0(
@globalinit_029_12232F587A4C5CD8B1EEDF696793B2FC_token0
) - 拿到
globalinit_029_12232F587A4C5CD8B1EEDF696793B2FC_func0
申请内存函数指针 - 调用了一个“once”函数,参数是上面得到的
token0
和globalinit
函数指针
@globalinit_029_12232F587A4C5CD8B1EEDF696793B2FC_func0
真正初始化的函数,这个就很简单了,创建全局属性age
,拿到内存地址,创建初始值6,将6存储到内存当中并返回。
但是那个“once”是有什么用吗?为什么还有一个token0
?为了得到更接近底层汇编的中间代码,在终端使用命令生成ir文件
: swiftc main.swift -emit-ir -o main1.c
(这里为了方便生成的文件查看我制定后缀为.c文件)
找到相同的函数@s4main8PSYModelC3ageSivau
--> PSYModel.age.unsafeMutableAddressor
,可以发现“once”函数的调用译成了根据标记,如果没有内存分配(once_ont_done
)就调用源码swift_once
方法,如果已经分配了(once_done
)就直接读取内存(load
),保证了其只会分配一次内存,只初始化一次。
结合Swift源码 看一下swift_once
做了什么?全局搜索swift_once
可以找到其,并根据注释可以知道其调用了GCD(dispatch_once_f
)保证函数只被执行一次。
/// 使用给定的上下文参数运行给定函数一次。
///predicate参数必须指向一个全局变量或静态变量,其静态范围类型为swift_once_t。
void swift::swift_once(swift_once_t *predicate, void (*fn)(void *),
void *context) {
#ifdef SWIFT_STDLIB_SINGLE_THREADED_RUNTIME
if (! *predicate) {
*predicate = true;
fn(context);
}
#elif defined(__APPLE__)
dispatch_once_f(predicate, context, fn);
#elif defined(__CYGWIN__)
_swift_once_f(predicate, context, fn);
#else
std::call_once(*predicate, [fn, context]() { fn(context); });
#endif
}
拓展(单例):
// 单例
class PSYModel{
static let shareInstance = PSYModel()
private init() {}
}
如果不希望单例类被继承,class
前面加上final
六、属性在MachO文件中的位置
首先我们要有一个共识,根据前两篇文章【Swift语言的类与结构体--2,Swift语言的类与结构体--1】我们得到了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
的结构信息:
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
}
以及在typeDescriptor
的结构信息中,fieldDescriptor
记录了属性的信息,其结构体如下:
struct FieldDescriptor{
var MangledTypeName: UInt32
var Superclass: UInt32
var Kind: Int16
var FieldRecordSize: Int16
var NumFields: Int32 // 属性个数
var FieldRecords // [FieldRecords]
}
FieldRecords
中记录每个属性的信息,其结构体如下:
struct FieldRecord{
Flags : uint32
MangledTypeName: int32
FieldName: int32
}
新建代码,编译成.app文件,使用MachO View打开,手动计算验证是否能找到属性名:
class PSYModel{
var age: Int = 18
let name: String = "psy"
}
首先找到Section段__TEXT,__swift5_types
,将pFile + Data - baseAddress(基地址) (0xFFFFFF5C + 0x1EE0
- 0x100000000
= 0x1E3C
)得到typeDescriptor
的偏移
点击Section64段__TEXT,__const
,找到0x1E3C
的pFile,向后偏移4个4字节,得到fieldDescriptor
,此时也是得到fieldDescriptor
的offset信息,需要pFile + Data(0x1E4C
+ 0x6C
= 0x1EB8
)才能得到真正的FieldDescriptor
点击Section64段__TEXT,__switf5_fieldmd
,找到0x1EB8
,向后偏移3个4字节,得到连续的内存空间即是FiledRecords
,再根据FiledRecords
结构,第1个4字节是Flags,第2个4字节是MangledTypeName,接下来的4字节是FiledName的offset(0xffffffdf
)
pFile + FiledName的offset ( 0x1ED0
+ 0xffffffdf
= 0x100001EAF
),然后在Section64段__TEXT,__switf5_reflstr
就可找到属性名字如下:同理,0x1EDC
+ 0xffffffd7
= 0x100001EB3
得到另一个属性名name
网友评论