一. 存儲屬性
1. 存儲屬性定義
存儲屬性是一個(gè)作為特定類和結(jié)構(gòu)體實(shí)例一部分的常量或變量。存儲屬性要么是變量存儲屬性 (由 var
關(guān)鍵字引入)要么是常量存儲屬性(由 let
關(guān)鍵字引入)委刘。
var age = 18
let name = "小明"
-
var
用來聲明變量丧没,變量的值可以在將來設(shè)置為不同的值。 -
let
用來聲明常量钱雷,常量的值一旦設(shè)置好便不能再被更改
在創(chuàng)建類或結(jié)構(gòu)體的實(shí)例時(shí)骂铁,必須為所有的存儲屬性設(shè)置一個(gè)初始值≌挚梗可以在初始化器( init
)里為存儲屬性設(shè)置一個(gè)初始值拉庵,可以分配一個(gè)默認(rèn)的屬性值作為定義的一部分。
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的區(qū)別
var age = 18
let name = "小明"
生成sil
文件后可以看到如下代碼
@_hasStorage @_hasInitialValue var age: Int { get set }
@_hasStorage @_hasInitialValue let name: String { get }
通過 sil
我們可以發(fā)現(xiàn)
-
var
修飾的屬性有get
和set
方法 -
let
修飾的屬性只有get
方法套蒂,所有let
修飾的屬性不能修改钞支。
二. 計(jì)算屬性
1. 計(jì)算屬性定義
計(jì)算屬性并不存儲值茫蛹,他們提供 getter
和 setter
來修改和獲取值。對于存儲屬性來說可以是常量或變量烁挟,但計(jì)算屬性必須定義為變量婴洼。于此同時(shí)我們書寫計(jì)算屬性時(shí)候必須包含類型,因?yàn)榫幾g器需要知道期望返回值是什么
struct square {
var width: Double
var area: Double {
get {
width * width
// return width * width
}
set {
self.width = newValue
}
// set (newArea) {
// self.width = newArea
// }
}
}
重寫一個(gè)實(shí)例的 setter
和 getter
可以用 set
撼嗓、get
來修飾柬采,setter
傳入新的默認(rèn)值叫做 newValue
,也可以自定義且警。Swift
中粉捻,在擁有返回值的方法里,如果方法內(nèi)部是單表達(dá)式斑芜,那么可以直接省略 return
肩刃。
2. 只讀計(jì)算屬性
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 兩種方式的區(qū)別
我們來看一看 square
結(jié)構(gòu)體在 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
實(shí)際上是存儲屬性,只是 set
方法被私有化了杏头。而方式一中的 area
沒有任何修飾盈包,只有get
方法。
3. 計(jì)算屬性本質(zhì)
我們來查看一下完整代碼的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
和計(jì)算屬性 area
都跟有 { get set }
醇王,那么就說明他們都是有 setter
和 getter
方法的呢燥。我們接著往下看他們具體
3.1 width 屬性的 getter 和 setter 在 sil 的內(nèi)部實(shí)現(xiàn)
// 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'
可以發(fā)現(xiàn)
- 在調(diào)用
width
的getter
方法的時(shí)候,可以發(fā)現(xiàn)是直接獲取square
中width
的值進(jìn)行返回厦画。 - 在調(diào)用
width
的setter
方法的時(shí)候疮茄,是直接獲取到square
中width
的地址,對地址所指向的內(nèi)容進(jìn)行修改根暑。
3.2 area 屬性的 getter 和 setter 在 sil 的內(nèi)部實(shí)現(xiàn)
// 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'
可以發(fā)現(xiàn)跟 width
屬性的 getter
和 setter
完全一樣力试。但是我們發(fā)現(xiàn)并沒有 area
相關(guān)的存儲變量。所以其實(shí)排嫌,計(jì)算屬性根本不會有存儲在實(shí)例的成員變量畸裳,那也就意味著計(jì)算屬性不占用內(nèi)存。
3.3 在匯編中計(jì)算屬性的實(shí)現(xiàn)
將上面的代碼編譯成匯編代碼淳地,并在 s.width = 20.0
打上斷點(diǎn)
我們可以看到計(jì)算屬性
area
的賦值和取值的本質(zhì)就是 setter
和 getter
方法怖糊。而存儲屬性 width
則是在進(jìn)行一系列的 mov
、str
操作颇象。
三. 屬性觀察者
1. 屬性觀察者定義
屬性觀察者用來觀察屬性值的變化伍伤, willSet
當(dāng)屬性將被改變調(diào)用,即使這個(gè)值與原有的值相同遣钳,而 didSet
在屬性已經(jīng)改變之后調(diào)用扰魂。它們的語法類似于 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 = "小明"
// 打印結(jié)果
name will set value 小明
name did set value
我們通過 sil
來看 willSet
和 didset
是怎么被調(diào)用的
// 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
方法中在賦值操作之前調(diào)用了 willSet
方法,然后進(jìn)行賦值操作劝评,最后再調(diào)用 didSet
方法姐直。
2. 初始化期間設(shè)置屬性時(shí)不會調(diào)用觀察者
在初始化期間設(shè)置屬性時(shí)不會調(diào)用 willSet
和 didSet
觀察者,只有在為完全初始化的實(shí)例分配新值時(shí)才會調(diào)用它們蒋畜。
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: "小明")
運(yùn)行上述代碼声畏,可以發(fā)現(xiàn)沒有任何打印結(jié)果
可以發(fā)現(xiàn)在初始化方法里是拿到
Person.name
屬性的內(nèi)存地址,然后將字符串直接拷貝到 Person.name
屬性的內(nèi)存地址中姻成,并沒有調(diào)用 get
和 set
方法插龄,是因?yàn)?Person.name
還沒有初始化完成。
3. 觀察者對計(jì)算屬性的觀察
上面的屬性觀察者只是對存儲屬性起作用佣渴,如果我們想對計(jì)算屬性起作用怎么辦?
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
}
}
如果我們想對計(jì)算屬性設(shè)置值之前對他進(jìn)行操作辫狼,只需將相關(guān)代碼添加到屬性的 setter
方法中初斑。對于計(jì)算屬性我們沒有必要給他添加 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: "小明")
// 打印結(jié)果
override age will set value 20
age will set value 20
age did set value 18
override age did set value 18
我們可以看出對于繼承關(guān)系的屬性他的調(diào)用順序是先調(diào)用子類的 willSet
见秤,再調(diào)用父類的 willSet
砂竖,再進(jìn)行賦值操作,再調(diào)用父類的 didSet
鹃答,最后調(diào)用子類的 didSet
乎澄。值得注意的是打印結(jié)果是在執(zhí)行 self.age = 20
這段代碼的時(shí)候打印的,因?yàn)樯厦嫣岬竭^初始化期間設(shè)置屬性時(shí)不會調(diào)用觀察者测摔,因此這里只會打印一次置济。
通過
sil
文件我們也可以看出 subPerson.age
的 setter
方法中的調(diào)用順序是先調(diào)用 subPerson.age
的 willSet
方法,在調(diào)用 Person.age
的 setter
方法锋八,在調(diào)用 subPerson.age
的 didSet
方法浙于。
四. 延遲存儲屬性
1. 延遲存儲屬性定義
延時(shí)加載存儲屬性是指當(dāng)?shù)谝淮伪徽{(diào)用的時(shí)候才會計(jì)算其初始值的屬性。在屬性聲明前使用 lazy
來標(biāo)示一個(gè)延時(shí)加載存儲屬性挟纱。
class Person {
lazy var age: Int = 18
}
var p = Person()
print(p.age)
print("end")
我們通過觀察 p
的內(nèi)存來觀察延遲存儲屬性 age
這個(gè)時(shí)候羞酗,可以看到
p.age
還沒有調(diào)用時(shí)在 metadata + refcount
后面的內(nèi)存空間是沒有值的,接著我們在 print("end")
打個(gè)斷點(diǎn)紊服,讓 age
訪問到檀轨,并打印p
的內(nèi)存我們可以看到當(dāng)
age
第一次被訪問的時(shí)候這個(gè)內(nèi)存空間有了值。
2. 延遲存儲屬性在sil中的實(shí)現(xiàn)
為了簡化 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
還沒有加載時(shí)為空,在第一次加載后有值煎饼。
// 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
初始化表達(dá)式中可以看到默認(rèn)的空值是 #Optional.none
讹挎,就是一個(gè)空值。
// 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
方法中可以看到,首先執(zhí)行 bb0
代碼塊淤袜,在 bb0
中首先獲取到 #Person.$__lazy_storage_$_age
的地址痒谴,并把內(nèi)存地址的值讀取到寄存器 %4
,然后對 %4
進(jìn)行枚舉模式匹配铡羡,如果有值就走 bb1
的代碼塊积蔚,沒有值就走 bb2
代碼塊。bb1
中的具體操作就是直接返回原有的值烦周。bb2
中的具體的操作就是將 18
存儲到 #Person.$__lazy_storage_$_age
的內(nèi)存地址當(dāng)中尽爆,并返回這個(gè)值。
注意
如果一個(gè)被標(biāo)記為 lazy 的屬性在沒有初始化時(shí)就同時(shí)被多個(gè)線程訪問读慎,則無法保證該屬性只會被初始化一次漱贱。
五. 類型屬性
1. 類型屬性的定義
實(shí)例屬性屬于一個(gè)特定類型的實(shí)例,每創(chuàng)建一個(gè)實(shí)例夭委,實(shí)例都擁有屬于自己的一套屬性值幅狮,實(shí)例之間的屬性相互獨(dú)立。你也可以為類型本身定義屬性株灸,無論創(chuàng)建了多少個(gè)該類型的實(shí)例崇摄,這些屬性都只有唯一一份。這種屬性就是類型屬性慌烧。
類型屬性其實(shí)就是一個(gè)全局變量逐抑,并且只會被初始化一次。
class Person {
// 多線程訪問時(shí)也只會被初始化一次
static var age: Int = 18
}
Person.age = 20
2. 類型屬性的本質(zhì)
我們通過 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
屬性的聲明只是多了一個(gè) staic
關(guān)鍵字屹蚊。 在這下方我們可以看到多了一個(gè)全局變量 s4main6PersonC3age_Wz
厕氨,并且 Person.age
屬性也變成了一個(gè)全局變量。
// 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'
在初始化的時(shí)候我們可以看到調(diào)用了一個(gè)函數(shù) Person.age.unsafeMutableAddressor
就是內(nèi)存地址的訪問汹粤,接下來我們定位到這個(gè)函數(shù)
// 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'
在這個(gè)函數(shù)中命斧,顯示獲取到最開始 Person
聲明下方多出的全局變量 s4main6PersonC3age_Wz
的地址,然后轉(zhuǎn)換成 Builtin.RawPointer
類型玄括,然后又調(diào)用了一個(gè)函數(shù) @$s4main6PersonC3age_WZ : $@convention(c) () -> ()
冯丙,接著執(zhí)行 builtin "once"
,最后返回的是全局變量的內(nèi)存地址遭京。接著定位到 @$s4main6PersonC3age_WZ : $@convention(c) () -> ()
這個(gè)函數(shù)當(dāng)中胃惜。
// 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'
這個(gè)函數(shù)當(dāng)中,首先創(chuàng)建全局變量 s4main6PersonC3ageSivpZ
也就是我們的 age
哪雕,接著獲取這個(gè)全局變量的內(nèi)存地址船殉,接著構(gòu)建 18
并且將 18
存放到全局變量的內(nèi)存地址中。這個(gè)函數(shù)也就是在初始化 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
}
通過
xcrun swift-demangle s4main6PersonC3ageSivau
命令我們可以看到 s4main6PersonC3ageSivau
就是在 sil
文件中的 Person.age.unsafeMutableAddressor
函數(shù)挨厚。那么在這里面我們可以看到有一個(gè) @swift_once
,可能就是我們在 sil
中看到的 builtin "once"
糠惫,我們?nèi)?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
文件中我們可以找到這個(gè)方法,我們發(fā)現(xiàn)這里使用的 GCD
的 dispatch_once
硼讽。所以類型屬性(static
修飾的屬性)只會被初始化一次巢价。
3. 單例的寫法
class Person {
static let sharedInstace = Person()
private init(){}
}
Person.sharedInstace
六. 屬性在Mahco文件的位置信息
在之前的文章Swift探索(一): 類與結(jié)構(gòu)體(上)中我們了解到 Swift
對象的內(nèi)存結(jié)構(gòu) Metadata
和在Swift探索(二): 類與結(jié)構(gòu)體(下) 方法調(diào)度探索當(dāng)中了解到 typeDescriptor
的結(jié)構(gòu)
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
當(dāng)中,并且 fieldDescriptor
的結(jié)構(gòu)體如下
struct FieldDescriptor {
var MangledTypeName: Int32 //回寫之后的類型名稱
var Superclass: Int32 // 父類
var Kind: uint16 // 標(biāo)識
var FieldRecordSize: uint16 // 記錄當(dāng)前的大小
var NumFields: uint32 //有多少個(gè)屬性
var FieldRecords: [FieldRecord] // 每一個(gè)屬性的具體細(xì)節(jié)
}
struct FieldRecord {
var Flags: uint32 //標(biāo)志位
var MangledTypeName: Int32 // 這個(gè)屬性的類型信息
var FieldName: Int32 // 屬性的名稱
}
我們通過 MachO
來驗(yàn)證
1. 驗(yàn)證流程
class Person {
var age = 18
var name = "小明"
}
前面部分跟在在Swift探索(二): 類與結(jié)構(gòu)體(下) 中查找 v-table
的流程一致固阁,首先Section64(_TEXT,__swift5_types)
中存放的就是 Descriptor
:
因此得到typeDescriptor
的地址為:
0xFFFFFF54 + 0x3F44 = 0x100003E98
在
Load Commands
的 LC_SEGMENT_64(__PAGEZERO)
中可以看到虛擬地址的首地址和大小壤躲,因此上一步得到的地址 0x100003E98
減去虛擬內(nèi)存的首地址 0x100000000
就是當(dāng)前typeDescriptor
在虛擬內(nèi)存中的偏移量( offset
)。
0x100003E98 - 0x100000000 = 0x3E98
定位到 0x3E98
0x3E98
就是 TargetClassDescriptor
這個(gè)結(jié)構(gòu)體類的首地址备燃,后面存儲的就是相應(yīng)成員變量的內(nèi)容碉克,根據(jù)前面對源碼的分析我們得到了 TargetClassDescriptor
結(jié)構(gòu)體中的 fieldDescriptor
前面有 4
個(gè) Int32
類型,也就是 4
個(gè) 4
字節(jié)并齐,于是我們向后偏移 4
個(gè) 4
字節(jié)
我們可以得到
fieldDescriptor
在 Macho
中的偏移量漏麦,于是 fieldDescriptor
的地址為
0x3EA8 + 0x74 = 0x3F1C
定位到 0x3F1C
根據(jù)前面的分析我們知道
FieldDescriptor
結(jié)構(gòu)體中的 FieldRecord
要向后偏移16個(gè)字節(jié),并且 FieldRecord
占用 12
個(gè)字節(jié)冀膝,于是可以看出 Section64(__TEXT,__swift5_fieldmd)
內(nèi)就是存儲的 fieldDescriptor
唁奢,因此那么
fieldRecords1
的 FieldName
的地址和fieldRecords2
的 FieldName
的地址就分別是
// fieldRecords1的fieldName
0x3F2C + 4 + 4 + 0xFFFFFFDF = 0x100003F13
// 減去虛擬內(nèi)存地址
0x100003F13 - 0x100000000 = 0x3F13
// fieldRecords2的fieldName
0x3F3C + 4 + 0xFFFFFFD7 = 0x100003F17
// 減去虛擬內(nèi)存地址
0x100003F17 - 0x100000000 = 0x3F17
定位到 0x3F13
和 0x3F17
可以看到后面的 Value
就是 age
和 name
通過兩篇文章的 MachO
分析我們大致的了解到
-
Section64(__TEXT,__swift5_types)
: 存儲的typeDescriptor
-
Section64(__TEXT,__swift5_fieldmd)
: 存儲的fieldDescriptor
-
Section64(__TEXT,__swift5_reflstr)
: 存儲所有的屬性名稱