3、Swift屬性探索
一、儲存屬性(Stored Property)
儲存屬性是一個作為特定類和結構體例項一部分的常量或變數。儲存屬性要麼是變數儲存屬性 (由 var
關鍵字引入)要麼是常量儲存屬性(由 let
關鍵字引入)。
swift
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
swift
var age = 18
let x = 20
編譯個sil康康:
```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
來修改和獲取值。計算屬性必須定義為變數。我們寫計算屬性時必須包含型別,因為編譯器需要知道返回值是什麼型別。
```swift struct square{
var width: Double
var area: Double{
get{
return width * width
}
set{
self.width = newValue
}
}
}
``
- 這裡面的
width,是
儲存屬性,佔用8個位元組的空間;
area`是計算屬性,不佔用記憶體空間。
set()
裡的newValue
是編譯器自動生成的
newValue
可以自己起個名字,不一定非要叫newValue
swift
var area: Double{
get{
return width * width
}
set(newArea){
self.width = newArea
}
}
再看這段程式碼
swift
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方法),但本質不同。
還有一種寫法:
```swift 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()
```swift 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:
```sil // SubjectName.subjectName.setter // setter方法 sil hidden @$s4main11SubjectNameC07subjectC0SSvs : [email protected](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 : [email protected](method) (@guaranteed String, @guaranteed SubjectName) -> () // user: %11 %11 = apply %10(%0, %1) : [email protected](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 : [email protected](method) (@guaranteed String, @guaranteed SubjectName) -> () // user: %20 %20 = apply %19(%6, %1) : [email protected](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() 呼叫情況
來一段程式碼:
```swift 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")
```
列印結果:
```sil 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 : [email protected](method) (Int, @guaranteed CTParTimeTeacher) -> () // user: %13 %13 = apply %12(%0, %1) : [email protected](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 : [email protected](method) (Int, @guaranteed CTTeacher) -> () // user: %17 %17 = apply %16(%0, %15) : [email protected](method) (Int, @guaranteed CTTeacher) -> () strong_release %15 : $CTTeacher // id: %18
// function_ref CTParTimeTeacher.age.didset // 呼叫CTParTimeTeacher的didset %19 = function_ref @$s4main16CTParTimeTeacherC3ageSivW : [email protected](method) (Int, @guaranteed CTParTimeTeacher) -> () // user: %20 %20 = apply %19(%8, %1) : [email protected](method) (Int, @guaranteed CTParTimeTeacher) -> () %21 = tuple () // user: %22 return %21 : $() // id: %22 } ```
三、延遲儲存屬性
- 延遲儲存屬性的初始值在其第一次使用時才進行計算。
- 用關鍵字
lazy
來標識一個延遲儲存屬性 ```swift class Subject{ lazy var age:Int = 18 }
var s = Subject()
print(s.age)
```
age
是如何做到在首次訪問才初始化的?看一下sil:
age
的後面有一個問號,也就是說它是一個可選型別
在看一下它初始化時的處理:初始化為一個Optional.none!
,可以理解為oc裡面的nil
,也就是0x0000000000000000
- 延遲儲存屬性的本質是一個可選型別
再看一下getter方法的sil
```sil // Subject.age.getter sil hidden [lazy_getter] [noinline] @$s4main7SubjectC3ageSivg : [email protected](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
//把記憶體地址的值,放到%4
%4 = load %3 : $Optional
//switch匹配,如果是Optional.some,走bb1程式碼;如果是Optional.none,走bb2程式碼
switch_enum %4 : $Optional
```sil 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
//把構建好的值,給到age
%14 = ref_element_addr %0 : $Subject, #Subject.$__lazy_storage_$_age // user: %15
%15 = begin_access [modify] [dynamic] %14 : $Optional
sil
bb1(%7 : $Int): // Preds: bb0
//把原有的int值,返回回去
debug_value %7 : $Int, let, name "tmp1" // id: %8
br bb3(%7 : $Int) // id: %9
還有一種情況:
swift
class Subject{
lazy var age:Int{
let s = MyClass()
}()
}
後面跟一個閉包表示式,只有在第一次使用的時候才初始化。
- 注意點:延遲儲存屬性 不能保證執行緒安全
四、static 型別屬性
- 型別屬性其實就是一個全域性變數
- 型別屬性只會被初始化一次
來段程式碼:
swift class CCTeacher{ static var age:Int = 18 } CCTeacher.age = 30
看一下sil:
sil
// static CCTeacher.age
sil_global hidden @$s4main9CCTeacherC3ageSivpZ : $Int
在sil中,age被宣告為一個全域性變數,所以static
本質上就是一個全域性變數。
再看看如何訪問age這個變數的:
```sil // CCTeacher.age.unsafeMutableAddressor sil hidden [global_init] @$s4main9CCTeacherC3ageSivau : [email protected](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 : [email protected](c) () -> () // user: %3
//"once" %1(token的地址)和%2(函式的記憶體地址)作為入參 %3 = builtin "once"(%1 : $Builtin.RawPointer, %2 : [email protected](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裡面找到:
```sil // one-time initialization function for age sil private [global_init_once_fn] @$s4main9CCTeacherC3age_WZ : [email protected](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程式碼裡可以找到:
ll
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):
```c++ 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
組合起來,便成了單利:
```swift class CCTeacher{ static let shareInstance = CTTeacher()
//把初始化器私有起來,外部就只能通過shareInstance訪問,不能調init()
private init(){}
} ``` 如果是型別函式
```swift class CCTeacher{
static func test(){
var tmp = 100
print(tmp)
}
}
CCTeacher.test() ``` 直接靜態派發,不走vtable
六、屬性在macho中的位置
回顧一下Metadata
,typeDescriptor
存在於TEXT,swift5_types
這個section裡面
swift
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
這個類
swift
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
中,其結構:
swift
class FieldDescriptor {
MangledTypeName int32
Superclass int32
Kind uint16
FieldRecordSize uint16
NumFields uint32
FieldRecords [FieldRecord]
}
其中numFields
代表當前有多少個屬性,FieldRecords
記錄了每個屬性的資訊,FieldRecords
的結構體:
swift
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