一、存储属性(Stored Property)
存储属性是一个作为特定类和结构体实例一部分的常量或变量。存储属性要么是变量存储属性 (由 var 关键字引入)要么是常量存储属性(由 let 关键字引入)。
class CTTeacher{
var age:Int
var name:String
}
这里的age和name就是存储属性
let 用来声明常量,常量的值一旦设置好便不能再被更改;
var 用来声明变量,变量的值可以在将来设置为不同的值。
1.1 从汇编的角度来看var和let
可以看出他俩都存在__common这个section里,是在同一个内存区域,并且相差8个字节。所以本质上let和var它就是一个内存地址,没什么区别。
1.2 从SIL的角度来看var和let
var age = 18
let x = 20
编译个sil康康:
@_hasStorage @_hasInitialValue var age: Int { get set }
@_hasStorage @_hasInitialValue let x: Int { get }
它俩都有@_hasStorage前缀,都是存储属性,重点是一个有set方法,一个没有set方法,也就是说let属性并没有set方法
二、计算属性
2.1 get() & set()
计算属性并不存储值,他们提供 getter 和 setter 来修改和获取值。计算属性必须定义为变量。我们写计算属性时必须包含类型,因为编译器需要知道返回值是什么类型。
struct square{
var width: Double
var area: Double{
get{
return width * width
}
set{
self.width = newValue
}
}
}
-
这里面的
width,是存储属性,占用8个字节的空间;area是计算属性,不占用内存空间。 -
set()里的newValue是编译器自动生成的
newValue可以自己起个名字,不一定非要叫newValue
var area: Double{
get{
return width * width
}
set(newArea){
self.width = newArea
}
}
再看这段代码
struct square{
var width: Double
var area: Double{
get{
return width * width
}
// set(newArea){
// self.width = newArea
// }
}
let height:Double = 20
}
area和height有啥区别?
height有@_hasStorage修饰,是个存储属性,area是计算属性- 他俩都没有set方法 所以虽然这俩属性似乎用法上一样,都只能取值(get方法),但本质不同。
还有一种写法:
struct square{
var width: Double = 10
private(set) var area:Double = 20
let height:Double = 30
}
private(set)修饰area属性,意味着area的set方法只能在结构体内部调用
2.2 willSet() & didSet()
class SubjectName{
var subjectName:String = ""{
willSet{
print("subjectName will set value \(newValue)")
}
didSet{
print("subjectName has been changed \(oldValue)")
}
}
}
let s = SubjectName()
s.subjectName = "NewName"
看一下sil:
// SubjectName.subjectName.setter
// setter方法
sil hidden @$s4main11SubjectNameC07subjectC0SSvs : $@convention(method) (@owned String, @guaranteed SubjectName) -> () {
// %0 "value" // users: %22, %16, %12, %11, %2
// %1 "self" // users: %20, %13, %11, %4, %3
bb0(%0 : $String, %1 : $SubjectName):
debug_value %0 : $String, let, name "value", argno 1 // id: %2
debug_value %1 : $SubjectName, let, name "self", argno 2 // id: %3
%4 = ref_element_addr %1 : $SubjectName, #SubjectName.subjectName // user: %5
%5 = begin_access [read] [dynamic] %4 : $*String // users: %6, %8
%6 = load %5 : $*String // users: %21, %9, %20, %7
retain_value %6 : $String // id: %7
end_access %5 : $*String // id: %8
debug_value %6 : $String, let, name "tmp" // id: %9
// function_ref SubjectName.subjectName.willset
// 调用willset方法
%10 = function_ref @$s4main11SubjectNameC07subjectC0SSvw : $@convention(method) (@guaranteed String, @guaranteed SubjectName) -> () // user: %11
%11 = apply %10(%0, %1) : $@convention(method) (@guaranteed String, @guaranteed SubjectName) -> ()
retain_value %0 : $String // id: %12
//给subjectName赋值
%13 = ref_element_addr %1 : $SubjectName, #SubjectName.subjectName // user: %14
%14 = begin_access [modify] [dynamic] %13 : $*String // users: %16, %15, %18
%15 = load %14 : $*String // user: %17
store %0 to %14 : $*String // id: %16
release_value %15 : $String // id: %17
end_access %14 : $*String // id: %18
// function_ref SubjectName.subjectName.didset
// 调用didset方法
%19 = function_ref @$s4main11SubjectNameC07subjectC0SSvW : $@convention(method) (@guaranteed String, @guaranteed SubjectName) -> () // user: %20
%20 = apply %19(%6, %1) : $@convention(method) (@guaranteed String, @guaranteed SubjectName) -> ()
release_value %6 : $String // id: %21
release_value %0 : $String // id: %22
%23 = tuple () // user: %24
return %23 : $() // id: %24
} // end sil function '$s4main11SubjectNameC07subjectC0SSvs'
- 计算属性不能添加
willSet()和didSet()
2.3 有继承时 willSet() & didSet() 调用情况
来一段代码:
class CTTeacher{
var age: Int{
willSet{
print("CTTeacher's age will set value \(newValue)")
}
didSet{
print("CTTeacher's age has been changed \(oldValue)")
}
}
var height:Double
init(_ age:Int, _ height:Double) {
self.age = age
self.height = height
}
}
class CTParTimeTeacher:CTTeacher{
override var age: Int{
willSet{
print("CTParTimeTeacher's age will set value \(newValue)")
}
didSet{
print("CTParTimeTeacher's age has been changed \(oldValue)")
}
}
var subjectName:String
init(_ subjectName:String) {
self.subjectName = subjectName
super.init(18, 109.0)
self.age = 20
}
}
let t = CTParTimeTeacher("CCT")
打印结果:
bb0(%0 : $Int, %1 : $CTParTimeTeacher):
debug_value %0 : $Int, let, name "value", argno 1 // id: %2
debug_value %1 : $CTParTimeTeacher, let, name "self", argno 2 // id: %3
strong_retain %1 : $CTParTimeTeacher // id: %4
%5 = upcast %1 : $CTParTimeTeacher to $CTTeacher // users: %11, %6
%6 = ref_element_addr %5 : $CTTeacher, #CTTeacher.age // user: %7
%7 = begin_access [read] [dynamic] %6 : $*Int // users: %8, %9
%8 = load %7 : $*Int // users: %10, %20
end_access %7 : $*Int // id: %9
debug_value %8 : $Int, let, name "tmp" // id: %10
strong_release %5 : $CTTeacher // id: %11
// function_ref CTParTimeTeacher.age.willset
// 调用CTParTimeTeacher的willset
%12 = function_ref @$s4main16CTParTimeTeacherC3ageSivw : $@convention(method) (Int, @guaranteed CTParTimeTeacher) -> () // user: %13
%13 = apply %12(%0, %1) : $@convention(method) (Int, @guaranteed CTParTimeTeacher) -> ()
strong_retain %1 : $CTParTimeTeacher // id: %14
%15 = upcast %1 : $CTParTimeTeacher to $CTTeacher // users: %18, %17
// function_ref CTTeacher.age.setter
// 调用CTTeacher的set方法 由于CTParTimeTeacher继承自CTTeacher 所以set方法走父类合理
%16 = function_ref @$s4main9CTTeacherC3ageSivs : $@convention(method) (Int, @guaranteed CTTeacher) -> () // user: %17
%17 = apply %16(%0, %15) : $@convention(method) (Int, @guaranteed CTTeacher) -> ()
strong_release %15 : $CTTeacher // id: %18
// function_ref CTParTimeTeacher.age.didset
// 调用CTParTimeTeacher的didset
%19 = function_ref @$s4main16CTParTimeTeacherC3ageSivW : $@convention(method) (Int, @guaranteed CTParTimeTeacher) -> () // user: %20
%20 = apply %19(%8, %1) : $@convention(method) (Int, @guaranteed CTParTimeTeacher) -> ()
%21 = tuple () // user: %22
return %21 : $() // id: %22
}
三、延迟存储属性
- 延迟存储属性的初始值在其第一次使用时才进行计算。
- 用关键字
lazy来标识一个延迟存储属性
class Subject{
lazy var age:Int = 18
}
var s = Subject()
print(s.age)
age是如何做到在首次访问才初始化的?看一下sil:
age的后面有一个问号,也就是说它是一个可选类型
在看一下它初始化时的处理:初始化为一个Optional.none!,可以理解为oc里面的nil,也就是0x0000000000000000
- 延迟存储属性的本质是一个可选类型
再看一下getter方法的sil
// Subject.age.getter
sil hidden [lazy_getter] [noinline] @$s4main7SubjectC3ageSivg : $@convention(method) (@guaranteed Subject) -> Int {
// %0 "self" // users: %14, %2, %1
bb0(%0 : $Subject):
debug_value %0 : $Subject, let, name "self", argno 1 // id: %1
//访问__lazy_storage_$_age的地址
%2 = ref_element_addr %0 : $Subject, #Subject.$__lazy_storage_$_age // user: %3
%3 = begin_access [read] [dynamic] %2 : $*Optional<Int> // users: %4, %5
//把内存地址的值,放到%4
%4 = load %3 : $*Optional<Int> // user: %6
end_access %3 : $*Optional<Int> // id: %5
//switch匹配,如果是Optional.some,走bb1代码;如果是Optional.none,走bb2代码
switch_enum %4 : $Optional<Int>, case #Optional.some!enumelt: bb1, case #Optional.none!enumelt: bb2 // id: %6
那看一下bb2代码块:(初始化赋值的代码)
bb2: // Preds: bb0
//构建值 int类型
%10 = integer_literal $Builtin.Int64, 18 // user: %11
%11 = struct $Int (%10 : $Builtin.Int64) // users: %18, %13, %12
debug_value %11 : $Int, let, name "tmp2" // id: %12
%13 = enum $Optional<Int>, #Optional.some!enumelt, %11 : $Int // user: %16
//把构建好的值,给到age
%14 = ref_element_addr %0 : $Subject, #Subject.$__lazy_storage_$_age // user: %15
%15 = begin_access [modify] [dynamic] %14 : $*Optional<Int> // users: %16, %17
store %13 to %15 : $*Optional<Int> // id: %16
end_access %15 : $*Optional<Int> // id: %17
br bb3(%11 : $Int) // id: %18
bb1代码块:(非第一次调用时走的代码)
bb1(%7 : $Int): // Preds: bb0
//把原有的int值,返回回去
debug_value %7 : $Int, let, name "tmp1" // id: %8
br bb3(%7 : $Int) // id: %9
还有一种情况:
class Subject{
lazy var age:Int{
let s = MyClass()
}()
}
后面跟一个闭包表达式,只有在第一次使用的时候才初始化。
- 注意点:延迟存储属性 不能保证线程安全
四、static 类型属性
- 类型属性其实就是一个全局变量
- 类型属性只会被初始化一次 来段代码:
class CCTeacher{
static var age:Int = 18
}
CCTeacher.age = 30
看一下sil:
// static CCTeacher.age
sil_global hidden @$s4main9CCTeacherC3ageSivpZ : $Int
在sil中,age被声明为一个全局变量,所以static本质上就是一个全局变量。
再看看如何访问age这个变量的:
// CCTeacher.age.unsafeMutableAddressor
sil hidden [global_init] @$s4main9CCTeacherC3ageSivau : $@convention(thin) () -> Builtin.RawPointer {
bb0:
//拿到age的token的地址
%0 = global_addr @$s4main9CCTeacherC3age_Wz : $*Builtin.Word // user: %1
//一个指针类型的转换:把%0转换成一个RawPointer
%1 = address_to_pointer %0 : $*Builtin.Word to $Builtin.RawPointer // user: %3
// function_ref one-time initialization function for age
// 调用age的初始化函数init
%2 = function_ref @$s4main9CCTeacherC3age_WZ : $@convention(c) () -> () // user: %3
//"once" %1(token的地址)和%2(函数的内存地址)作为入参
%3 = builtin "once"(%1 : $Builtin.RawPointer, %2 : $@convention(c) () -> ()) : $()
%4 = global_addr @$s4main9CCTeacherC3ageSivpZ : $*Int // user: %5
//返回全局变量的内存地址
%5 = address_to_pointer %4 : $*Int to $Builtin.RawPointer // user: %6
return %5 : $Builtin.RawPointer // id: %6
} // end sil function '$s4main9CCTeacherC3ageSivau'
%2那行,调用了一个函数 s4main9CCTeacherC3age_WZ,在sil里面找到:
// one-time initialization function for age
sil private [global_init_once_fn] @$s4main9CCTeacherC3age_WZ : $@convention(c) () -> () {
bb0:
//初始化一个全局变量s4main9CCTeacherC3ageSivpZ,这个玩意儿通过还原
//得到:static main.CCTeacher.age : Swift.Int
//没错,就是在创建age
//(控制台:xcrun swift-demangle s4main9CCTeacherC3ageSivpZ)
alloc_global @$s4main9CCTeacherC3ageSivpZ // id: %0
//拿到全局变量内存地址
%1 = global_addr @$s4main9CCTeacherC3ageSivpZ : $*Int // user: %4
//构建int类型18
%2 = integer_literal $Builtin.Int64, 18 // user: %3
%3 = struct $Int (%2 : $Builtin.Int64) // user: %4
//把值存到age
store %3 to %1 : $*Int // id: %4
%5 = tuple () // user: %6
return %5 : $() // id: %6
} // end sil function '$s4main9CCTeacherC3age_WZ'
代码里边儿“once”那里,意思是只调用一次,在ir代码里可以找到:
once_not_done: ; preds = %entry
call void @swift_once(i64* @"$s4main9CCTeacherC3age_Wz", i8* bitcast (void ()* @"$s4main9CCTeacherC3age_WZ" to i8*), i8* undef)
br label %once_done
}
调用了swift_once这个方法,我们在swift源码里可以找到(Once.cpp):
void swift::swift_once(swift_once_t *predicate, void (*fn)(void *),
void *context) {
#if 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
}
里面是dispatch_once_f,熟悉的GCD
五、内存独占的概念
那么static和let组合起来,便成了单利:
class CCTeacher{
static let shareInstance = CTTeacher()
//把初始化器私有起来,外部就只能通过shareInstance访问,不能调init()
private init(){}
}
如果是类型函数
class CCTeacher{
static func test(){
var tmp = 100
print(tmp)
}
}
CCTeacher.test()
直接静态派发,不走vtable
六、属性在macho中的位置
回顾一下Metadata,typeDescriptor存在于TEXT,swift5_types这个section里面
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是属于TargetClassDescriptor这个类
class 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
}
Swift 类的属性存放在 fieldDescriptor 中,其结构:
class FieldDescriptor {
MangledTypeName int32
Superclass int32
Kind uint16
FieldRecordSize uint16
NumFields uint32
FieldRecords [FieldRecord]
}
其中numFields代表当前有多少个属性,FieldRecords记录了每个属性的信息,FieldRecords的结构体:
struct FieldRecord {
Flags uint32 //标志位
MangledTypeName int32 //属性的类型信息
FieldName int32 //属性名称
}
- 计算出
typeDescriptor在 Mach-O 中的地址
FFFFFF30 + 3F4C = 0x100003E7C
0x100003E7C - 0x100000000 = 3E7C
得到3E7C,再定位到TEXT,__const这个section,找到3E7C,再向后偏移4个4字节
这里的
98就是fieldDescriptor的偏移量,所以3E88 + 98 就可以得到FieldDescriptor在mach-o中的信息
3E88 + 98 = 3F24
再定位到 TEXT,__swift5_fieldmd 这个section,这里就能看到fieldDescriptor的内容,偏移4个字节,便是FieldRecords
前两个对应的就是 Flags 和 MangledTypeName,从第三个开始就是 FieldName 也就是 age 和 age1
计算age的偏移量:3F34 + 两个4字节 + FFFFFFFD
3F34 + 8 + FFFFFFFD = 0x100003F39
0x100003F39 - 0x100000000 = 3F39
在mach-o中的TEXT,__swift5_reflstr 这个section定位到3F39
成功定位到age和age1,其中的61 67 65就是age,61 67 67 31 就是age1