一.存儲屬性
存儲屬性是一個作為特定類和結構體實例一部分的常量或變量。存儲屬性要么是變量存儲屬性(由var
關鍵字引入)要么是常量存儲屬性(由let
關鍵字引入)。
class LGTeacher {
var age = 10
let name = ""
}
這里的age
和name
就是我們所說的存儲屬性,我們這里需要加以區別的是let
和var
的區別。
從定義上:let
用來聲明常量,常量的值一旦設置好便不能再被更改。var
用來聲明變量,變量的值可以在將來設置為不同的值。
1.匯編角度分析let和var
在main.swift中添加代碼,使用真機測試
var age = 18
let x = 16
projectTest`main:
0x1049c8d90 <+0>: adrp x9, 10
0x1049c8d94 <+4>: mov w8, #0x12 //將18復制到w8寄存器上
0x1049c8d98 <+8>: str x8, [x9, #0x160] //將x8存到x9+0x160所對應的內存地址上
0x1049c8d9c <+12>: adrp x9, 10
0x1049c8da0 <+16>: mov w8, #0x10 //將16復制到w8寄存器上
-> 0x1049c8da4 <+20>: str x8, [x9, #0x168] //將x8存到x9+0x168所對應的內存地址上(剛好與age相差8字節)
0x1049c8da8 <+24>: mov w0, #0x0
0x1049c8dac <+28>: ret
- 因此從匯編角度上看,
let
與var
并沒有什么區別,都是將值存入到內存中去
2.SIL角度分析let和var
import Foundation
//都是存儲屬性, var有set方法,let沒有生成set方法
//本質上let和var其實本質上也是一種語法,只是let沒有set方法。所以不能被修改
@_hasStorage @_hasInitialValue var age: Int { get set }
@_hasStorage @_hasInitialValue let x: Int { get }
二.計算屬性
存儲屬性是最常見的,除了存儲屬性,類、結構體和枚舉也能定義計算屬性,計算屬性并不存儲值,他們提供getter
和setter
來獲取和修改值。對于存儲屬性來說可以是常量或變量,但計算屬性必須定義為變量
。與此同時我們書寫計算屬性時候必須包含類型
,因為編譯器需要知道期望返回值是什么。
struct square{
//實例占據內存,8字節
var width: Double
//本質就是方法
var area: Double{
get{
//get中,也可以省略return。編譯器會自動推導
width * width
}
set{
self.width = newValue
}
//newValue,編譯器幫我們生成的。如果想要修改,使用set(xxxx){}
//SIL中對newValue的介紹
//debug_value %0 : $Double, let, name "newValue", argno 1 // id: %2
}
//將set方法私有化,只能在當前square使用set方法
private(set) var height: Double
}
三.屬性觀察者
屬性觀察者用來觀察屬性值的變化,一個willSet
當屬性將被改變調用,即使這個值與原有值相同,而didSet
在屬性已經改變之后調用。它們的語法類似于getter
和setter
class SubjectName{
var subjectName = ""{
willSet{
print("subjectName will set value \(newValue)")
}
didSet{
print("subjectName has been changed \(oldValue)")
}
}
}
let s = SubjectName()
s.subjectName = "Swift"
執行
subjectName will set value Swift
subjectName has been changed
SIL中willSet
// SubjectName.subjectName.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
%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
%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
%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'
- 在subjectName的
set
方法中,賦值前會觸發willSet
,賦值后會觸發didSet
1.初始化過程中不會執行willSet
class SubjectName{
var subjectName = ""{
willSet{
print("subjectName will set value \(newValue)")
}
didSet{
print("subjectName has been changed \(oldValue)")
}
}
init(subjectName: String) {
self.subjectName = subjectName
}
}
let s = SubjectName(subjectName: "Swift")
SIL中的init(subjectName: String)
// SubjectName.init(subjectName:)
sil hidden @$s4main11SubjectNameC07subjectC0ACSS_tcfc : $@convention(method) (@owned String, @owned SubjectName) -> @owned SubjectName {
// %0 "subjectName" // users: %19, %16, %12, %2
// %1 "self" // users: %13, %4, %20, %3
bb0(%0 : $String, %1 : $SubjectName):
debug_value %0 : $String, let, name "subjectName", argno 1 // id: %2
debug_value %1 : $SubjectName, let, name "self", argno 2 // id: %3
%4 = ref_element_addr %1 : $SubjectName, #SubjectName.subjectName // user: %11
%5 = string_literal utf8 "" // user: %10
%6 = integer_literal $Builtin.Word, 0 // user: %10
%7 = integer_literal $Builtin.Int1, -1 // user: %10
%8 = metatype $@thin String.Type // user: %10
// function_ref String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
%9 = function_ref @$sSS21_builtinStringLiteral17utf8CodeUnitCount7isASCIISSBp_BwBi1_tcfC : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %10
%10 = apply %9(%5, %6, %7, %8) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %11
store %10 to %4 : $*String // id: %11
retain_value %0 : $String // id: %12
%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
release_value %0 : $String // id: %19
return %1 : $SubjectName // id: %20
} // end sil function '$s4main11SubjectNameC07subjectC0ACSS_tcfc'
-
init
是作為初始化操作,本質是不會調用get
或set
方法 - 在SIL中直接是執行
store %0 to %14 : $*String
將%0存入%14 - 換句話說,如果在初始化的過程中能夠訪問
willSet
或didSet
,有些屬性沒有被初始化完成,也會操作內存泄露
2.繼承屬性下的觀察者
class Teacher {
var age: Int {
willSet {
print("age will set value \(newValue)")
}
didSet {
print("age has been changed \(oldValue)")
}
}
init(age: Int) {
self.age = age
}
}
class PartTeacher: Teacher {
override var age: Int {
willSet {
print("override age will set value \(newValue)")
}
didSet {
print("override age has been changed \(oldValue)")
}
}
override init(age: Int) {
super.init(age: age)
}
}
let t = PartTeacher(age: 18)
t.age = 20
執行結果
override age will set value 20
age will set value 20
age has been changed 18
override age has been changed 18
- 執行順序
子類willSet
->父類willSet
->父類didSet
->子類didSet
通過SIL來理解,找到age
的set
方法
// PartTeacher.age.setter
sil hidden @$s4main11PartTeacherC3ageSivs : $@convention(method) (Int, @guaranteed PartTeacher) -> () {
// %0 "value" // users: %17, %13, %2
// %1 "self" // users: %15, %14, %5, %4, %20, %13, %3
bb0(%0 : $Int, %1 : $PartTeacher):
debug_value %0 : $Int, let, name "value", argno 1 // id: %2
debug_value %1 : $PartTeacher, let, name "self", argno 2 // id: %3
strong_retain %1 : $PartTeacher // id: %4
%5 = upcast %1 : $PartTeacher to $Teacher // users: %11, %6
%6 = ref_element_addr %5 : $Teacher, #Teacher.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 : $Teacher // id: %11
// function_ref PartTeacher.age.willset
%12 = function_ref @$s4main11PartTeacherC3ageSivw : $@convention(method) (Int, @guaranteed PartTeacher) -> () // user: %13
%13 = apply %12(%0, %1) : $@convention(method) (Int, @guaranteed PartTeacher) -> ()
strong_retain %1 : $PartTeacher // id: %14
%15 = upcast %1 : $PartTeacher to $Teacher // users: %18, %17
// function_ref Teacher.age.setter
%16 = function_ref @$s4main7TeacherC3ageSivs : $@convention(method) (Int, @guaranteed Teacher) -> () // user: %17
%17 = apply %16(%0, %15) : $@convention(method) (Int, @guaranteed Teacher) -> ()
strong_release %15 : $Teacher // id: %18
// function_ref PartTeacher.age.didset
%19 = function_ref @$s4main11PartTeacherC3ageSivW : $@convention(method) (Int, @guaranteed PartTeacher) -> () // user: %20
%20 = apply %19(%8, %1) : $@convention(method) (Int, @guaranteed PartTeacher) -> ()
%21 = tuple () // user: %22
return %21 : $() // id: %22
} // end sil function '$s4main11PartTeacherC3ageSivs'
- 1.
PartTeacher.age.willset
執行子類的willSet
- 2.
Teacher.age.setter
執行父類的setter
,在我們之前的講解中setter
會觸發willSet
和didSet
。因此會,調用父類的willSet
和父類的didSet
- 3.
PartTeacher.age.didset
執行子類的didSet
四.延遲存儲屬性
延遲存儲實現的初始值在其第一次使用時才進行計算(懶加載),使用關鍵字lazy
class Subject {
lazy var age: Int = 18
}
var s = Subject()
print(s.age)
print("end")
1.使用LLDB調試分析
在print(s.age)和print("end")分別打一個斷點
執行到print(s.age)斷點時
(lldb) po s
<Subject: 0x10b410270>
(lldb) x/8g 0x10b410270
0x10b410270: 0x0000000100008160 0x0000000200000003
0x10b410280: 0x0000000000000000 0x000000010b410401
0x10b410290: 0x0000000000000000 0x0000000000000000
0x10b4102a0: 0x000000010b410006 0x0000000100000001
(lldb)
除去16字節的metadata,下一個8字節就是age,此時為0。此時并沒有存儲值
放開斷點進入print("end")斷點
(lldb) x/8g 0x10b410270
0x10b410270: 0x0000000100008160 0x0000000200000003
0x10b410280: 0x0000000000000012 0x000000010b410400
0x10b410290: 0x0000000000000000 0x0000000000000000
0x10b4102a0: 0x000000010b410006 0x0000000100000001
(lldb)
此時已經存上age的值了
2.SIL文件分析
//此時age是一個Int?,可選值。因此懶加載的本質就是一個可選值
class Subject {
lazy var age: Int { get set }
@_hasStorage @_hasInitialValue final var $__lazy_storage_$_age: Int? { get set }
@objc deinit
init()
}
// Subject.init()
sil hidden @$s4main7SubjectCACycfc : $@convention(method) (@owned Subject) -> @owned Subject {
// %0 "self" // users: %2, %5, %1
bb0(%0 : $Subject):
debug_value %0 : $Subject, let, name "self", argno 1 // id: %1
//初始化了一個lazy_age,返回了addr存入寄存器%2
%2 = ref_element_addr %0 : $Subject, #Subject.$__lazy_storage_$_age // user: %4
//聲明了一個為Optinal.none的枚舉類型存入寄存器%3
%3 = enum $Optional<Int>, #Optional.none!enumelt // user: %4
//將%3寄存器的值存入寄存器%2,意思也就是將age置為Optional.none。也就是nil
store %3 to %2 : $*Optional<Int> // id: %4
return %0 : $Subject // id: %5
} // end sil function '$s4main7SubjectCACycfc'
// 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
%2 = ref_element_addr %0 : $Subject, #Subject.$__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 : $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
// %19 // user: %20
bb3(%19 : $Int): // Preds: bb2 bb1
return %19 : $Int // id: %20
}
分析Subject.age.getter
1. %2 = ref_element_addr %0 : $Subject, #Subject.$__lazy_storage_$_age // user: %3
讀取我們的lay_storage_age到%2
2.%4 = load %3 : $*Optional<Int>
將Optional<Int> 值給到%4
3.switch_enum %4 : $Optional<Int>, case #Optional.some!enumelt: bb1, case #Optional.none!enumelt: bb2 // id: %6
枚舉的判斷,如果有值(Optional.some)就走bb1的代碼塊,如果沒值(Optional.none)就走bb2的代碼塊
4.bb2,把當前Int類型的值(18)構建出來給到我們的枚舉(Optional<Int>),然后執行存儲操作(store %13 to %15 : $*Optional<Int> )
5.bb1,把原有的Int值直接返回回去
-
begin_access
和end_access
叫做內存獨占
問題引入:延遲存儲屬性是否能保證bb2只被訪問一次?
- 當然是不能,因為當多線程時,多條線程同時執行到某個延遲存儲屬性,此時都會執行bbl2代碼塊,因此會多次進行賦值操作。因此,
延遲存儲屬性并不是線程安全的
五.類型屬性
類型屬性
其實就是一個只會被初始化一次的全局變量
class LGTeacher {
static var age = 18
}
LGTeacher.age = 30
SIL分析
class LGTeacher {
@_hasStorage @_hasInitialValue static var age: Int { get set }
@objc deinit
init()
}
// one-time initialization token for age
sil_global private @$s4main9LGTeacherC3age_Wz : $Builtin.Word
// static LGTeacher.age
sil_global hidden @$s4main9LGTeacherC3ageSivpZ : $Int
//age變成了一個全局變量
//statc本質上就是全局變量
探究如何被初始化的
在@main中,有這么一個方法。age的可變地址,可能是對地址的訪問
// function_ref LGTeacher.age.unsafeMutableAddressor
%3 = function_ref @$s4main9LGTeacherC3ageSivau : $@convention(thin) () -> Builtin.RawPointer // user: %4
查找關于s4main9LGTeacherC3ageSivau
的實現
// LGTeacher.age.unsafeMutableAddressor
sil hidden [global_init] @$s4main9LGTeacherC3ageSivau : $@convention(thin) () -> Builtin.RawPointer {
bb0:
//拿到內存地址,其實這里就是token的內存地址(對應的sil_global private @$s4main9LGTeacherC3age_Wz : $Builtin.Word)
%0 = global_addr @$s4main9LGTeacherC3age_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
//%2為token函數的內存地址
%2 = function_ref @$s4main9LGTeacherC3age_WZ : $@convention(c) () -> () // user: %3
//執行builtin "once"(應該是執行一次的意思),一個參數為token的內存地址,一個是token函數的內存地址
%3 = builtin "once"(%1 : $Builtin.RawPointer, %2 : $@convention(c) () -> ()) : $()
//拿到全局變量的內存地址
%4 = global_addr @$s4main9LGTeacherC3ageSivpZ : $*Int // user: %5
//將全局變量的指針轉化為RawPointer
%5 = address_to_pointer %4 : $*Int to $Builtin.RawPointer // user: %6
//返回RawPointer,實際上把全局變量返回回去了
return %5 : $Builtin.RawPointer // id: %6
} // end sil function '$s4main9LGTeacherC3ageSivau'
- 這里的大概意思就是,根據token創建一次全局變量,并把這個全局變量地址返回回去
探究token相關函數s4main9LGTeacherC3age_WZ
//注釋的意思也相當明確,age的一次性初始化方法
// one-time initialization function for age
sil private [global_init_once_fn] @$s4main9LGTeacherC3age_WZ : $@convention(c) () -> () {
bb0:
//創建一個全局變量給到%0(s4main9LGTeacherC3ageSivpZ -> 對應的就是static LGTeacher.age(sil_global hidden @$s4main9LGTeacherC3ageSivpZ : $Int,第一塊代碼里可以找到))
alloc_global @$s4main9LGTeacherC3ageSivpZ // id: %0
//拿到age全局變量的內存地址
%1 = global_addr @$s4main9LGTeacherC3ageSivpZ : $*Int // user: %4
//構建Int,并賦值18(Int在當前是結構體)
%2 = integer_literal $Builtin.Int64, 18 // user: %3
%3 = struct $Int (%2 : $Builtin.Int64) // user: %4
//將這個Int結構體存入到%1(全局變量age的內存地址),初始化age變量
store %3 to %1 : $*Int // id: %4
%5 = tuple () // user: %6
return %5 : $() // id: %6
} // end sil function '$s4main9LGTeacherC3age_WZ'
探究builtin "once"
將代碼降級到IR
//$s4main9LGTeacherC3age_Wz對應的就是全局變量的初始化方法
//此時調用了一個@swift_once
once_not_done: ; preds = %entry
call void @swift_once(i64* @"$s4main9LGTeacherC3age_Wz", i8* bitcast (void ()* @"$s4main9LGTeacherC3age_WZ" to i8*), i8* undef)
br label %once_done
}
源碼探究@swift_once
進入Once.cpp
執行了dispatch_once_f,GCD單例寫法,保證只會執行一次
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
}
- 執行了
dispatch_once_f
,本質上還是通過GCD單例寫法來保證全局變量只會被初始化一次
至此也就證明了類型屬性就是全局變量并且只會被初始化一次,并且線程也是安全的
拓展:如何使用類型屬性寫一個Swift單例
class LGTeacher {
static let sharedInstance = LGTeacher()
private init() {}
}
六.屬性在MachO文件的位置信息
1.源碼分析
回顧之前總結的TargetClassDescriptor
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
//對應上面添加的size =》B.addInt32(VTableEntries.size());
var size: UInt32
//V-Table
}
- 屬性存放的位置就是
fieldDescriptor
1.在源碼中找到fieldDescriptor
/// A pointer to the field descriptor for the type, if any.
TargetRelativeDirectPointer<Runtime, const reflection::FieldDescriptor,
/*nullable*/ true> Fields;
2.進入reflection
namespace reflection {
class FieldDescriptor;
}
- 發現是一個
FieldDescriptor
3.進入FieldDescriptor
class FieldDescriptor {
const FieldRecord *getFieldRecordBuffer() const {
return reinterpret_cast<const FieldRecord *>(this + 1);
}
public:
const RelativeDirectPointer<const char> MangledTypeName;
const RelativeDirectPointer<const char> Superclass;
FieldDescriptor() = delete;
const FieldDescriptorKind Kind;
const uint16_t FieldRecordSize;
const uint32_t NumFields;
...
using const_iterator = FieldRecordIterator;
const_iterator begin() const {
auto Begin = getFieldRecordBuffer();
auto End = Begin + NumFields;
return const_iterator { Begin, End };
}
const_iterator end() const {
auto Begin = getFieldRecordBuffer();
auto End = Begin + NumFields;
return const_iterator { End, End };
}
llvm::ArrayRef<FieldRecord> getFields() const {
return {getFieldRecordBuffer(), NumFields};
}
...
};
4.通過上述源碼總結出FieldDescriptor
struct FieldDescriptor {
MangledTypeName int32
Superclass int32
Kind uint16
FieldRecordSize uint16
//當前有多少個屬性
NumFields uint32
//記錄每個屬性的信息
FieldRecords [FieldRecord]
}
5.進入FileRecord
class FieldRecord {
const FieldRecordFlags Flags;
public:
const RelativeDirectPointer<const char> MangledTypeName;
const RelativeDirectPointer<const char> FieldName;
...
};
通過源碼得出FieldRecords
struct FieldRecord{
Flags uint32
MangledTypeName int32
FieldName int32
}
-
MangledTypeName
混寫屬性類型名稱 -
FieldName
屬性名稱
2.Mach-o分析
Swift代碼
class LGTeacher{
var age = 18
var name = "Kody"
}
Mach-o文件
1.得出TargetClassDescriptor
0xFFFFFF54 + 0x3F48 - 0x100000000(虛擬內存地址) = 0x3E9C
2.在_TEXT_const
找到0x3E9C
- 標注部分就是
TargetClassDescriptor
起止位置,偏移4個4字節就是FiledDescriptor
- 此時此刻的
0x74
其實是存放的偏移信息
0x3EAC + 0x74 = 0x3F20
3.去_TEXT__swift5_fieldmd
找到0x3F20
- 這里就是
FieldDescriptor
的信息
4.找到FieldRecord
,偏移4個字節
- 后面的連續內存空間就是結構體
[FieldRecord]
信息 -
0x2
表示Flags
-
0xFFFFFFDC
表示MangledTypeName
-
0xFFFFFFDF
表示FieldName
,這里也是偏移信息
0x3F38 + 0xFFFFFFDF - 0x100000000 = 0x3F17
5.進入TEXT__swift5_refstr
,找到0x3F17
-
0x61
的ASCII碼對應a
-
0x67
的ASCII碼對應g
-
0x65
的ASCII碼對應e
-
00
標志結束