美文网首页
Swift -- 3.属性

Swift -- 3.属性

作者: MissStitch丶 | 来源:发表于2022-01-10 23:45 被阅读0次

    一.存储属性

    存储属性是一个作为特定类和结构体实例一部分的常量或变量。存储属性要么是变量存储属性(由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标志结束

    相关文章

      网友评论

          本文标题:Swift -- 3.属性

          本文链接:https://www.haomeiwen.com/subject/yekdcrtx.html