Swift探索(三): 属性
一. 存储属性
1. 存储属性定义
存储属性是一个作为特定类和结构体实例一部分的常量或变量。存储属性要么是变量存储属性 (由 var
关键字引入)要么是常量存储属性(由 let
关键字引入)。
var age = 18
let name = "小明"
-
var
用来声明变量,变量的值可以在将来设置为不同的值。 -
let
用来声明常量,常量的值一旦设置好便不能再被更改
在创建类或结构体的实例时,必须为所有的存储属性设置一个初始值。可以在初始化器( init
)里为存储属性设置一个初始值,可以分配一个默认的属性值作为定义的一部分。
struct PersonStruct {
var age: Int = 10
let name: String = "小明"
}
class PersonClass {
var age: Int
let name: String
init(_ age: Int, _ name: String) {
self.age = age
self.name = name
}
}
let person = PersonStruct()
let person1 = PersonClass(18, "小明")
2. 从SIL查看let和var的区别
var age = 18
let name = "小明"
生成sil
文件后可以看到如下代码
@_hasStorage @_hasInitialValue var age: Int { get set }
@_hasStorage @_hasInitialValue let name: String { get }
通过 sil
我们可以发现
-
var
修饰的属性有get
和set
方法 -
let
修饰的属性只有get
方法,所有let
修饰的属性不能修改。
二. 计算属性
1. 计算属性定义
计算属性并不存储值,他们提供 getter
和 setter
来修改和获取值。对于存储属性来说可以是常量或变量,但计算属性必须定义为变量。于此同时我们书写计算属性时候必须包含类型,因为编译器需要知道期望返回值是什么
struct square {
var width: Double
var area: Double {
get {
width * width
// return width * width
}
set {
self.width = newValue
}
// set (newArea) {
// self.width = newArea
// }
}
}
重写一个实例的 setter
和 getter
可以用 set
、get
来修饰,setter
传入新的默认值叫做 newValue
,也可以自定义。Swift
中,在拥有返回值的方法里,如果方法内部是单表达式,那么可以直接省略 return
。
2. 只读计算属性
2.1 方式一
struct square {
var width: Double = 30.0
var area: Double {
get {
width * width
}
}
let height: Double = 20.0
}
2.2 方式二
struct square {
var width: Double = 30.0
private(set) var area : Double
let height: Double = 20.0
}
2.3 两种方式的区别
我们来看一看 square
结构体在 sil
中如何声明的:
// 方式一
struct square {
@_hasStorage @_hasInitialValue var width: Double { get set }
var area: Double { get }
@_hasStorage @_hasInitialValue let height: Double { get }
init()
init(width: Double = 30.0)
}
// 方式二
struct square {
@_hasStorage @_hasInitialValue var width: Double { get set }
@_hasStorage private(set) var area: Double { get set }
@_hasStorage @_hasInitialValue let height: Double { get }
init(width: Double = 30.0, area: Double)
}
我们可以看到方式二中的 area
实际上是存储属性,只是 set
方法被私有化了。而方式一中的 area
没有任何修饰,只有get
方法。
3. 计算属性本质
我们来查看一下完整代码的sil
文件
struct square {
var width: Double
var area: Double {
get {
width
}
set {
self.width = newValue
}
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
var s = square(width: 10.0)
s.width = 20.0
s.area = 30.0
let w = s.width
let a = s.area
}
}
通过 swiftc ViewController.swift -emit-sil
将 ViewController.swift
编译成 sil
文件
// sil 文件中 square 的声明
struct square {
@_hasStorage var width: Double { get set }
var area: Double { get set }
init(width: Double)
}
我们可以看到存储属性 width
和计算属性 area
都跟有 { get set }
,那么就说明他们都是有 setter
和 getter
方法的。我们接着往下看他们具体
3.1 width 属性的 getter 和 setter 在 sil 的内部实现
// square.width.getter
sil hidden [transparent] @$s4main6squareV5widthSdvg : $@convention(method) (square) -> Double {
// %0 "self" // users: %2, %1
bb0(%0 : $square):
debug_value %0 : $square, let, name "self", argno 1 // id: %1
%2 = struct_extract %0 : $square, #square.width // user: %3
return %2 : $Double // id: %3
} // end sil function '$s4main6squareV5widthSdvg'
// square.width.setter
sil hidden [transparent] @$s4main6squareV5widthSdvs : $@convention(method) (Double, @inout square) -> () {
// %0 "value" // users: %6, %2
// %1 "self" // users: %4, %3
bb0(%0 : $Double, %1 : $*square):
debug_value %0 : $Double, let, name "value", argno 1 // id: %2
debug_value_addr %1 : $*square, var, name "self", argno 2 // id: %3
%4 = begin_access [modify] [static] %1 : $*square // users: %7, %5
%5 = struct_element_addr %4 : $*square, #square.width // user: %6
store %0 to %5 : $*Double // id: %6
end_access %4 : $*square // id: %7
%8 = tuple () // user: %9
return %8 : $() // id: %9
} // end sil function '$s4main6squareV5widthSdvs'
可以发现
- 在调用
width
的getter
方法的时候,可以发现是直接获取square
中width
的值进行返回。 - 在调用
width
的setter
方法的时候,是直接获取到square
中width
的地址,对地址所指向的内容进行修改。
3.2 area 属性的 getter 和 setter 在 sil 的内部实现
// square.area.getter
sil hidden @$s4main6squareV4areaSdvg : $@convention(method) (square) -> Double {
// %0 "self" // users: %2, %1
bb0(%0 : $square):
debug_value %0 : $square, let, name "self", argno 1 // id: %1
%2 = struct_extract %0 : $square, #square.width // user: %3
return %2 : $Double // id: %3
} // end sil function '$s4main6squareV4areaSdvg'
// square.area.setter
sil hidden @$s4main6squareV4areaSdvs : $@convention(method) (Double, @inout square) -> () {
// %0 "newValue" // users: %6, %2
// %1 "self" // users: %4, %3
bb0(%0 : $Double, %1 : $*square):
debug_value %0 : $Double, let, name "newValue", argno 1 // id: %2
debug_value_addr %1 : $*square, var, name "self", argno 2 // id: %3
%4 = begin_access [modify] [static] %1 : $*square // users: %7, %5
%5 = struct_element_addr %4 : $*square, #square.width // user: %6
store %0 to %5 : $*Double // id: %6
end_access %4 : $*square // id: %7
%8 = tuple () // user: %9
return %8 : $() // id: %9
} // end sil function '$s4main6squareV4areaSdvs'
可以发现跟 width
属性的 getter
和 setter
完全一样。但是我们发现并没有 area
相关的存储变量。所以其实,计算属性根本不会有存储在实例的成员变量,那也就意味着计算属性不占用内存。
3.3 在汇编中计算属性的实现
将上面的代码编译成汇编代码,并在 s.width = 20.0
打上断点
我们可以看到计算属性
area
的赋值和取值的本质就是 setter
和 getter
方法。而存储属性 width
则是在进行一系列的 mov
、str
操作。
三. 属性观察者
1. 属性观察者定义
属性观察者用来观察属性值的变化, willSet
当属性将被改变调用,即使这个值与原有的值相同,而 didSet
在属性已经改变之后调用。它们的语法类似于 getter
和 setter
。
class Person{
// 存储属性
var name: String = ""{
willSet {
print("name will set value \(newValue)")
}
didSet {
print("name did set value \(oldValue)")
}
}
}
let p = Person()
p.name = "小明"
// 打印结果
name will set value 小明
name did set value
我们通过 sil
来看 willSet
和 didset
是怎么被调用的
// Person.name.setter
sil hidden @$s4main6PersonC4nameSSvs : $@convention(method) (@owned String, @guaranteed Person) -> () {
// %0 "value" // users: %22, %16, %12, %11, %2
// %1 "self" // users: %20, %13, %11, %4, %3
bb0(%0 : $String, %1 : $Person):
debug_value %0 : $String, let, name "value", argno 1 // id: %2
debug_value %1 : $Person, let, name "self", argno 2 // id: %3
%4 = ref_element_addr %1 : $Person, #Person.name // 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 Person.name.willset
%10 = function_ref @$s4main6PersonC4nameSSvw : $@convention(method) (@guaranteed String, @guaranteed Person) -> () // user: %11
%11 = apply %10(%0, %1) : $@convention(method) (@guaranteed String, @guaranteed Person) -> ()
retain_value %0 : $String // id: %12
%13 = ref_element_addr %1 : $Person, #Person.name // 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 Person.name.didset
%19 = function_ref @$s4main6PersonC4nameSSvW : $@convention(method) (@guaranteed String, @guaranteed Person) -> () // user: %20
%20 = apply %19(%6, %1) : $@convention(method) (@guaranteed String, @guaranteed Person) -> ()
release_value %6 : $String // id: %21
release_value %0 : $String // id: %22
%23 = tuple () // user: %24
return %23 : $() // id: %24
} // end sil function '$s4main6PersonC4nameSSvs'
我们可以看到在 Person.name
的 setter
方法中在赋值操作之前调用了 willSet
方法,然后进行赋值操作,最后再调用 didSet
方法。
2. 初始化期间设置属性时不会调用观察者
在初始化期间设置属性时不会调用 willSet
和 didSet
观察者,只有在为完全初始化的实例分配新值时才会调用它们。
class Person{
var name: String = "unnamed"{
willSet {
print("name will set value \(newValue)")
}
didSet {
print("name did set value \(oldValue)")
}
}
init(name: String) {
self.name = name
}
}
let p = Person.init(name: "小明")
运行上述代码,可以发现没有任何打印结果
初始化方法.png可以发现在初始化方法里是拿到
Person.name
属性的内存地址,然后将字符串直接拷贝到 Person.name
属性的内存地址中,并没有调用 get
和 set
方法,是因为 Person.name
还没有初始化完成。
3. 观察者对计算属性的观察
上面的属性观察者只是对存储属性起作用,如果我们想对计算属性起作用怎么办?
class Square {
var width: Double
var area: Double {
get {
return width * width
}
set {
print("do something \(newValue)")
self.width = sqrt(newValue)
print("do something \(oldValue)")
}
}
init(width: Double) {
self.width = width
}
}
如果我们想对计算属性设置值之前对他进行操作,只需将相关代码添加到属性的 setter
方法中。对于计算属性我们没有必要给他添加 willSet
和 didSet
,只会画蛇添足。
4. 在继承中的属性观察者
class Person {
var age: Int {
willSet {
print("age will set value \(newValue)")
}
didSet {
print("age did set value \(oldValue)")
}
}
var height: Double
init(_ age: Int, _ height: Double) {
self.age = age
self.height = height
}
}
class subPerson :Person {
override var age: Int {
willSet {
print("override age will set value \(newValue)")
}
didSet {
print("override age did set value \(oldValue)")
}
}
var name: String
init(name: String) {
self.name = name
super.init(18, 185.0)
self.age = 20
}
}
let p = subPerson.init(name: "小明")
// 打印结果
override age will set value 20
age will set value 20
age did set value 18
override age did set value 18
我们可以看出对于继承关系的属性他的调用顺序是先调用子类的 willSet
,再调用父类的 willSet
,再进行赋值操作,再调用父类的 didSet
,最后调用子类的 didSet
。值得注意的是打印结果是在执行 self.age = 20
这段代码的时候打印的,因为上面提到过初始化期间设置属性时不会调用观察者,因此这里只会打印一次。
通过
sil
文件我们也可以看出 subPerson.age
的 setter
方法中的调用顺序是先调用 subPerson.age
的 willSet
方法,在调用 Person.age
的 setter
方法,在调用 subPerson.age
的 didSet
方法。
四. 延迟存储属性
1. 延迟存储属性定义
延时加载存储属性是指当第一次被调用的时候才会计算其初始值的属性。在属性声明前使用 lazy
来标示一个延时加载存储属性。
class Person {
lazy var age: Int = 18
}
var p = Person()
print(p.age)
print("end")
我们通过观察 p
的内存来观察延迟存储属性 age
这个时候,可以看到
p.age
还没有调用时在 metadata + refcount
后面的内存空间是没有值的,接着我们在 print("end")
打个断点,让 age
访问到,并打印p
的内存p的内存2.png
我们可以看到当
age
第一次被访问的时候这个内存空间有了值。
2. 延迟存储属性在sil中的实现
为了简化 sil
文件,将上述代码替换成
class Person {
lazy var age: Int = 18
}
var p = Person()
var t = p.age
class Person {
lazy var age: Int { get set }
@_hasStorage @_hasInitialValue final var $__lazy_storage_$_age: Int? { get set }
@objc deinit
init()
}
通过在 sil
文件中 age
的声明我们可以看出延迟存储属性是可选值,因此在 age
还没有加载时为空,在第一次加载后有值。
// variable initialization expression of Person.$__lazy_storage_$_age
sil hidden [transparent] @$s4main6PersonC21$__lazy_storage_$_age029_12232F587A4C5CD8B1EEDF696793G2FCLLSiSgvpfi : $@convention(thin) () -> Optional<Int> {
bb0:
%0 = enum $Optional<Int>, #Optional.none!enumelt // user: %1
return %0 : $Optional<Int> // id: %1
} // end sil function '$s4main6PersonC21$__lazy_storage_$_age029_12232F587A4C5CD8B1EEDF696793G2FCLLSiSgvpfi'
在$__lazy_storage_$_age
初始化表达式中可以看到默认的空值是 #Optional.none
,就是一个空值。
// Person.age.getter
sil hidden [lazy_getter] [noinline] @$s4main6PersonC3ageSivg : $@convention(method) (@guaranteed Person) -> Int {
// %0 "self" // users: %14, %2, %1
bb0(%0 : $Person):
debug_value %0 : $Person, let, name "self", argno 1 // id: %1
%2 = ref_element_addr %0 : $Person, #Person.$__lazy_storage_$_age // user: %3
%3 = begin_access [read] [dynamic] %2 : $*Optional<Int> // users: %4, %5
%4 = load %3 : $*Optional<Int> // user: %6
end_access %3 : $*Optional<Int> // id: %5
switch_enum %4 : $Optional<Int>, case #Optional.some!enumelt: bb1, case #Optional.none!enumelt: bb2 // id: %6
// %7 // users: %9, %8
bb1(%7 : $Int): // Preds: bb0
debug_value %7 : $Int, let, name "tmp1" // id: %8
br bb3(%7 : $Int) // id: %9
bb2: // Preds: bb0
%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
%14 = ref_element_addr %0 : $Person, #Person.$__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
// %19 // user: %20
bb3(%19 : $Int): // Preds: bb2 bb1
return %19 : $Int // id: %20
} // end sil function '$s4main6PersonC3ageSivg'
在 age
的 getter
方法中可以看到,首先执行 bb0
代码块,在 bb0
中首先获取到 #Person.$__lazy_storage_$_age
的地址,并把内存地址的值读取到寄存器 %4
,然后对 %4
进行枚举模式匹配,如果有值就走 bb1
的代码块,没有值就走 bb2
代码块。bb1
中的具体操作就是直接返回原有的值。bb2
中的具体的操作就是将 18
存储到 #Person.$__lazy_storage_$_age
的内存地址当中,并返回这个值。
注意
如果一个被标记为 lazy 的属性在没有初始化时就同时被多个线程访问,则无法保证该属性只会被初始化一次。
五. 类型属性
1. 类型属性的定义
实例属性属于一个特定类型的实例,每创建一个实例,实例都拥有属于自己的一套属性值,实例之间的属性相互独立。你也可以为类型本身定义属性,无论创建了多少个该类型的实例,这些属性都只有唯一一份。这种属性就是类型属性。
类型属性其实就是一个全局变量,并且只会被初始化一次。
class Person {
// 多线程访问时也只会被初始化一次
static var age: Int = 18
}
Person.age = 20
2. 类型属性的本质
我们通过 swiftc main.swift -emit-sil
命令将 main.swift
编译成 sil
文件来查看
class Person {
@_hasStorage @_hasInitialValue static var age: Int { get set }
@objc deinit
init()
}
// one-time initialization token for age
sil_global private @$s4main6PersonC3age_Wz : $Builtin.Word
// static Person.age
sil_global hidden @$s4main6PersonC3ageSivpZ : $Int
我们可以看到 age
属性的声明只是多了一个 staic
关键字。 在这下方我们可以看到多了一个全局变量 s4main6PersonC3age_Wz
,并且 Person.age
属性也变成了一个全局变量。
// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
%2 = metatype $@thick Person.Type
// function_ref Person.age.unsafeMutableAddressor
%3 = function_ref @$s4main6PersonC3ageSivau : $@convention(thin) () -> Builtin.RawPointer // user: %4
%4 = apply %3() : $@convention(thin) () -> Builtin.RawPointer // user: %5
%5 = pointer_to_address %4 : $Builtin.RawPointer to [strict] $*Int // user: %8
%6 = integer_literal $Builtin.Int64, 20 // user: %7
%7 = struct $Int (%6 : $Builtin.Int64) // user: %9
%8 = begin_access [modify] [dynamic] %5 : $*Int // users: %9, %10
store %7 to %8 : $*Int // id: %9
end_access %8 : $*Int // id: %10
%11 = integer_literal $Builtin.Int32, 0 // user: %12
%12 = struct $Int32 (%11 : $Builtin.Int32) // user: %13
return %12 : $Int32 // id: %13
} // end sil function 'main'
在初始化的时候我们可以看到调用了一个函数 Person.age.unsafeMutableAddressor
就是内存地址的访问,接下来我们定位到这个函数
// Person.age.unsafeMutableAddressor
sil hidden [global_init] @$s4main6PersonC3ageSivau : $@convention(thin) () -> Builtin.RawPointer {
bb0:
%0 = global_addr @$s4main6PersonC3age_Wz : $*Builtin.Word // user: %1
%1 = address_to_pointer %0 : $*Builtin.Word to $Builtin.RawPointer // user: %3
// function_ref one-time initialization function for age
%2 = function_ref @$s4main6PersonC3age_WZ : $@convention(c) () -> () // user: %3
%3 = builtin "once"(%1 : $Builtin.RawPointer, %2 : $@convention(c) () -> ()) : $()
%4 = global_addr @$s4main6PersonC3ageSivpZ : $*Int // user: %5
%5 = address_to_pointer %4 : $*Int to $Builtin.RawPointer // user: %6
return %5 : $Builtin.RawPointer // id: %6
} // end sil function '$s4main6PersonC3ageSivau'
在这个函数中,显示获取到最开始 Person
声明下方多出的全局变量 s4main6PersonC3age_Wz
的地址,然后转换成 Builtin.RawPointer
类型,然后又调用了一个函数 @$s4main6PersonC3age_WZ : $@convention(c) () -> ()
,接着执行 builtin "once"
,最后返回的是全局变量的内存地址。接着定位到 @$s4main6PersonC3age_WZ : $@convention(c) () -> ()
这个函数当中。
// one-time initialization function for age
sil private [global_init_once_fn] @$s4main6PersonC3age_WZ : $@convention(c) () -> () {
bb0:
alloc_global @$s4main6PersonC3ageSivpZ // id: %0
%1 = global_addr @$s4main6PersonC3ageSivpZ : $*Int // user: %4
%2 = integer_literal $Builtin.Int64, 18 // user: %3
%3 = struct $Int (%2 : $Builtin.Int64) // user: %4
store %3 to %1 : $*Int // id: %4
%5 = tuple () // user: %6
return %5 : $() // id: %6
} // end sil function '$s4main6PersonC3age_WZ'
这个函数当中,首先创建全局变量 s4main6PersonC3ageSivpZ
也就是我们的 age
,接着获取这个全局变量的内存地址,接着构建 18
并且将 18
存放到全局变量的内存地址中。这个函数也就是在初始化 age
变量。
在上一步中 builtin "once"
是在干什么呢?我们通过 swiftc main.swift -emit-ir
命令将 main.swift
编译成 IR
文件来查看
define hidden swiftcc i8* @"$s4main6PersonC3ageSivau"() #0 {
entry:
%0 = load i64, i64* @"$s4main6PersonC3age_Wz", align 8
%1 = icmp eq i64 %0, -1
%2 = call i1 @llvm.expect.i1(i1 %1, i1 true)
br i1 %2, label %once_done, label %once_not_done
once_done: ; preds = %once_not_done, %entry
%3 = load i64, i64* @"$s4main6PersonC3age_Wz", align 8
%4 = icmp eq i64 %3, -1
call void @llvm.assume(i1 %4)
ret i8* bitcast (%TSi* @"$s4main6PersonC3ageSivpZ" to i8*)
once_not_done: ; preds = %entry
call void @swift_once(i64* @"$s4main6PersonC3age_Wz", i8* bitcast (void ()* @"$s4main6PersonC3age_WZ" to i8*), i8* undef)
br label %once_done
}
s4main6PersonC3ageSivau.png
通过
xcrun swift-demangle s4main6PersonC3ageSivau
命令我们可以看到 s4main6PersonC3ageSivau
就是在 sil
文件中的 Person.age.unsafeMutableAddressor
函数。那么在这里面我们可以看到有一个 @swift_once
,可能就是我们在 sil
中看到的 builtin "once"
,我们去 Swift源码 中查看 @swift_once
到底干了什么。
/// Runs the given function with the given context argument exactly once.
/// The predicate argument must point to a global or static variable of static
/// extent of type 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
}
在 Once.cpp
文件中我们可以找到这个方法,我们发现这里使用的 GCD
的 dispatch_once
。所以类型属性(static
修饰的属性)只会被初始化一次。
3. 单例的写法
class Person {
static let sharedInstace = Person()
private init(){}
}
Person.sharedInstace
六. 属性在Mahco文件的位置信息
在之前的文章Swift探索(一): 类与结构体(上)中我们了解到 Swift
对象的内存结构 Metadata
和在Swift探索(二): 类与结构体(下) 方法调度探索当中了解到 typeDescriptor
的结构
struct MataData {
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
}
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
}
通过Swift源码我们了解到 Swift
对象的属性存储在 fieldDescriptor
当中,并且 fieldDescriptor
的结构体如下
struct FieldDescriptor {
var MangledTypeName: Int32 //回写之后的类型名称
var Superclass: Int32 // 父类
var Kind: uint16 // 标识
var FieldRecordSize: uint16 // 记录当前的大小
var NumFields: uint32 //有多少个属性
var FieldRecords: [FieldRecord] // 每一个属性的具体细节
}
struct FieldRecord {
var Flags: uint32 //标志位
var MangledTypeName: Int32 // 这个属性的类型信息
var FieldName: Int32 // 属性的名称
}
我们通过 MachO
来验证
1. 验证流程
class Person {
var age = 18
var name = "小明"
}
前面部分跟在在Swift探索(二): 类与结构体(下) 中查找 v-table
的流程一致,首先Section64(_TEXT,__swift5_types)
中存放的就是 Descriptor
:
因此得到typeDescriptor
的地址为:
0xFFFFFF54 + 0x3F44 = 0x100003E98
虚拟地址的首地址.png
在
Load Commands
的 LC_SEGMENT_64(__PAGEZERO)
中可以看到虚拟地址的首地址和大小,因此上一步得到的地址 0x100003E98
减去虚拟内存的首地址 0x100000000
就是当前typeDescriptor
在虚拟内存中的偏移量( offset
)。
0x100003E98 - 0x100000000 = 0x3E98
定位到 0x3E98
0x3E98
就是 TargetClassDescriptor
这个结构体类的首地址,后面存储的就是相应成员变量的内容,根据前面对源码的分析我们得到了 TargetClassDescriptor
结构体中的 fieldDescriptor
前面有 4
个 Int32
类型,也就是 4
个 4
字节,于是我们向后偏移 4
个 4
字节
我们可以得到
fieldDescriptor
在 Macho
中的偏移量,于是 fieldDescriptor
的地址为
0x3EA8 + 0x74 = 0x3F1C
定位到 0x3F1C
根据前面的分析我们知道
FieldDescriptor
结构体中的 FieldRecord
要向后偏移16个字节,并且 FieldRecord
占用 12
个字节,于是可以看出 Section64(__TEXT,__swift5_fieldmd)
内就是存储的 fieldDescriptor
,因此fieldDescriptor.png
那么
fieldRecords1
的 FieldName
的地址和fieldRecords2
的 FieldName
的地址就分别是
// fieldRecords1的fieldName
0x3F2C + 4 + 4 + 0xFFFFFFDF = 0x100003F13
// 减去虚拟内存地址
0x100003F13 - 0x100000000 = 0x3F13
// fieldRecords2的fieldName
0x3F3C + 4 + 0xFFFFFFD7 = 0x100003F17
// 减去虚拟内存地址
0x100003F17 - 0x100000000 = 0x3F17
定位到 0x3F13
和 0x3F17
可以看到后面的 Value
就是 age
和 name
通过两篇文章的 MachO
分析我们大致的了解到
-
Section64(__TEXT,__swift5_types)
: 存储的typeDescriptor
-
Section64(__TEXT,__swift5_fieldmd)
: 存储的fieldDescriptor
-
Section64(__TEXT,__swift5_reflstr)
: 存储所有的属性名称