Swift -- 3.屬性

一.存儲屬性

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

class LGTeacher {
    var age = 10
    let name = ""
}

這里的agename就是我們所說的存儲屬性,我們這里需要加以區別的是letvar的區別。
從定義上: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    
  • 因此從匯編角度上看,letvar并沒有什么區別,都是將值存入到內存中去

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 }

二.計算屬性

存儲屬性是最常見的,除了存儲屬性,類、結構體和枚舉也能定義計算屬性,計算屬性并不存儲值,他們提供gettersetter來獲取和修改值。對于存儲屬性來說可以是常量或變量,但計算屬性必須定義為變量。與此同時我們書寫計算屬性時候必須包含類型,因為編譯器需要知道期望返回值是什么。

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在屬性已經改變之后調用。它們的語法類似于gettersetter

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是作為初始化操作,本質是不會調用getset方法
  • 在SIL中直接是執行store %0 to %14 : $*String將%0存入%14
  • 換句話說,如果在初始化的過程中能夠訪問willSetdidSet,有些屬性沒有被初始化完成,也會操作內存泄露

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來理解,找到ageset方法

// 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會觸發willSetdidSet。因此會,調用父類的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_accessend_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文件


mach-o

1.得出TargetClassDescriptor

0xFFFFFF54 + 0x3F48 - 0x100000000(虛擬內存地址) = 0x3E9C

2.在_TEXT_const找到0x3E9C

0x3E9C
  • 標注部分就是TargetClassDescriptor起止位置,偏移4個4字節就是FiledDescriptor
FieldDescriptor的偏移信息0x74
  • 此時此刻的0x74其實是存放的偏移信息
0x3EAC + 0x74  = 0x3F20

3.去_TEXT__swift5_fieldmd找到0x3F20

0x3F20
  • 這里就是FieldDescriptor的信息

4.找到FieldRecord,偏移4個字節

FieldRecord
  • 后面的連續內存空間就是結構體[FieldRecord]信息
  • 0x2表示Flags
  • 0xFFFFFFDC表示MangledTypeName
  • 0xFFFFFFDF表示FieldName,這里也是偏移信息
0x3F38 + 0xFFFFFFDF - 0x100000000 = 0x3F17

5.進入TEXT__swift5_refstr,找到0x3F17

0x3F17
  • 0x61的ASCII碼對應a
  • 0x67的ASCII碼對應g
  • 0x65的ASCII碼對應e
  • 00標志結束
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容