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标志结束
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353

推荐阅读更多精彩内容