3、Swift屬性探索

語言: CN / TW / HK

一、儲存屬性(Stored Property)

儲存屬性是一個作為特定類和結構體例項一部分的常量或變數。儲存屬性要麼是變數儲存屬性 (由 var 關鍵字引入)要麼是常量儲存屬性(由 let 關鍵字引入)。

swift class CTTeacher{ var age:Int var name:String } 這裡的agename就是儲存屬性

let 用來宣告常量,常量的值一旦設定好便不能再被更改;

var 用來宣告變數,變數的值可以在將來設定為不同的值。

1.1 從彙編的角度來看varlet

iShot2022-02-08 17.26.17.png

iShot2022-02-08 17.35.52.png

可以看出他倆都存在__common這個section裡,是在同一個記憶體區域,並且相差8個位元組。所以本質上let和var它就是一個記憶體地址,沒什麼區別。

1.2 從SIL的角度來看varlet

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()

計算屬性並不儲存值,他們提供 gettersetter 來修改和獲取值。計算屬性必須定義為變數。我們寫計算屬性時必須包含型別,因為編譯器需要知道返回值是什麼型別。

```swift struct square{

var width: Double

var area: Double{
    get{
        return width * width
    }
    set{
        self.width = newValue
    }
}

} `` - 這裡面的width,是儲存屬性,佔用8個位元組的空間;area`是計算屬性,不佔用記憶體空間。

  • set()裡的newValue是編譯器自動生成的

iShot2022-02-09 17.30.59.png

  • newValue可以自己起個名字,不一定非要叫newValue

swift var area: Double{ get{ return width * width } set(newArea){ self.width = newArea } } iShot2022-02-09 17.34.05.png

再看這段程式碼

swift struct square{ var width: Double var area: Double{ get{ return width * width } // set(newArea){ // self.width = newArea // } } let height:Double = 20 } areaheight有啥區別?

iShot2022-02-10 10.11.36.png - 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屬性,意味著areaset`方法只能在結構體內部呼叫

iShot2022-02-10 10.37.14.png iShot2022-02-10 10.35.44.png

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()` iShot2022-02-10 11.45.32.png

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") ``` 列印結果: iShot2022-02-10 13.50.33.png

```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) ``` iShot2022-02-10 14.15.08.png

iShot2022-02-10 14.30.28.png

age是如何做到在首次訪問才初始化的?看一下sil: iShot2022-02-10 14.37.31.png

age的後面有一個問號,也就是說它是一個可選型別

在看一下它初始化時的處理:初始化為一個Optional.none!,可以理解為oc裡面的nil,也就是0x0000000000000000 iShot2022-02-10 14.51.57.png

  • 延遲儲存屬性的本質是一個可選型別

再看一下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 // users: %4, %5

//把記憶體地址的值,放到%4 %4 = load %3 : $Optional // user: %6 end_access %3 : $Optional // id: %5

//switch匹配,如果是Optional.some,走bb1程式碼;如果是Optional.none,走bb2程式碼 switch_enum %4 : $Optional, case #Optional.some!enumelt: bb1, case #Optional.none!enumelt: bb2 // id: %6 ``` 那看一下bb2程式碼塊:(初始化賦值的程式碼)

```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, #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 // users: %16, %17 store %13 to %15 : $Optional // id: %16 end_access %15 : $*Optional // id: %17 br bb3(%11 : $Int) // id: %18 ``` bb1程式碼塊:(非第一次呼叫時走的程式碼)

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`

五、記憶體獨佔的概念

那麼staticlet組合起來,便成了單利:

```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中的位置

回顧一下MetadatatypeDescriptor存在於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

iShot2022-02-10 17.23.58.png

得到3E7C,再定位到TEXT,__const這個section,找到3E7C,再向後偏移4個4位元組

iShot2022-02-10 17.39.22.png 這裡的98就是fieldDescriptor的偏移量,所以3E88 + 98 就可以得到FieldDescriptormach-o中的資訊

3E88 + 98 = 3F24

再定位到 TEXT,__swift5_fieldmd 這個section,這裡就能看到fieldDescriptor的內容,偏移4個位元組,便是FieldRecords iShot2022-02-10 18.03.27.png

前兩個對應的就是 FlagsMangledTypeName,從第三個開始就是 FieldName 也就是 ageage1 iShot2022-02-11 09.43.22.png

計算age的偏移量:3F34 + 兩個4位元組 + FFFFFFFD

3F34 + 8 + FFFFFFFD = 0x100003F39

0x100003F39 - 0x100000000 = 3F39

mach-o中的TEXT,__swift5_reflstr 這個section定位到3F39

QQ20220211-095830@2x.png

成功定位到ageage1,其中的61 67 65就是age61 67 67 31 就是age1