Swift属性

375 阅读4分钟

1、存储属性

  • 存储属性是一个作为特定类和结构体实例一部分的常量或变量。存储属性要么是变量存储属性(由 var 关键字引入)要么是常量存储属性(由 let 关键字引入)。

var和let的区别

  • 在汇编角度看两者看不出区别,但在 SIL 文件中可以看到, let 属性比 var 缺少 set 方法
    @_hasStorage @_hasInitialValue var age: Int { get set } 
    @_hasStorage @_hasInitialValue let x: Int { get } ...
    

private()

  • private(set)修饰后,依然是存储属性,只不过set方法是私有的
    struct Square { 
        // 实例中占据内存 
        var width: Double
        let height: Double 
        private(set) var area: Double = 40 
    }
    
  • 拿到 SIL
    struct Square { @_hasStorage var width: Double { get set } 
    @_hasStorage let height: Double { get } @_hasStorage 
    @_hasInitialValue private(set) var area: Double { get set } }
    

2、计算属性

  • 除了存储属性, 类、结构体和枚举 也能够定义计算属性,计算属性并不存储值,他们提供gettersetter来修改和获取值。对于存储属性来说可以是常量或变量,但计算属性必须定义为变量。于此同时我们书写 计算属性必须包含类型 ,因为编译器需要知道期望返回值是什么。

3、属性观察者

3.1、存储属性观察者

  • 属性观察者会观察属性值的变化,willSet当属性将被改变时调用,即使 这个值与原有的值相同 ,而didSet在属性已经改变之后调用。它们的语法类似于 gettersetter
    • 需要注意的一点是 在初始化期间设置属性时不会调用 willSet 和 didSet 观察者;只有在为完全初始化的实例分配新值时才会调用它们。

3.2、计算属性观察者

  • 上面的属性观察者只是对存储属性起作用,如果我们想对计算属性起作用怎么办?很简单,只需将相关代码添加到属性的 setter
    class Square {
        var width: Double 
        var area: Double { 
            get { 
                return width * width 
            } 
            set { 
                self.width = sqrt(newValue) 
            } 
        } 
        init(width: Double) { 
            self.width = width 
        } 
    }
    

3.3、继承属性观察者

  • 涉及到继承时,属性观察者调用顺序按照: 子willSet --> 父willSet --> 父didSet --> 子didSet 执行

4、延迟存储属性

  • 延迟属性必须有初始值
  • 延迟存储属性的初始值在其 第一次使用时才进行计算
  • 必须用 var 定义属性,因为 let 必须在实例的初始化方法完成之前就拥有值,而延迟属性类似于 懒加载 ,,用到时才赋值
  • 线程不安全
  • 用关键字lazy来标识一个 延迟存储属性
    class Person {
        lazy var age: Int = 20
    }
    
    • SIL文件:被 final 修饰、并且带 ? 是一个可选属性
    class Person { 
        @_hasStorage @_hasInitialValue final var $__lazy_storage_$_age: Int? { get set } 
        init() 
        @objc deinit 
    }
    
    • 判断有值时走bb1,没有值时走bb2
    switch_enum %4 : $Optional<Int>, case #Optional.some!enumelt: bb1, case #Optional.none!enumelt: bb2 // id: %7
    

5、类型属性

  • 类型属性 其实就是一个 全局变量( SIL 中创建,通过 LZPerson.age.unsafeMutableAddressor 进行内存地址访问) image.png

    • unsafeMutableAddressor:返回全局变量的内存地址
  • 类型属性 只会被初始化一次(底层是GCD的 dispatch_once_t 封装的 swift_once 方法,所以线程安全)

  • 通过 static 定义类型属性,如果是类,也可以用关键字 class,访问时直接通过类型访问

    struct LZPerson { 
        static var age: Int = 20 
    }
    LZPerson.age += 1
    

5.1、单例

5.1.1、OC单例

image.png

5.1.2、Swift单例

image.png

6、属性在Mach-O中

  • TargetClassDescriptor 结构:
    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这个结构体中,源码结构如下:
    struct FieldDescriptorMangledTypeName int32 
        Superclass int32 
        Kind uint16 
        FieldRecordSize uint16 
        NumFields uint32 
        FieldRecords [FieldRecord] 
    }
    
    • 其中NumFields代表当前有多少个属性,FieldRecords记录了每个属性的信息, FieldRecords 的结构体如下:
      struct FieldRecordFlags uint32 
          MangledTypeName int32 
          FieldName int32 
      }
      

Mach-O找属性位置

  • 拉入可执行文件到 MachOView 中,先看Section64(__TEXT,swift5_types)这项,先找 TargetClassDescriptor ,小端模式计算 image.png

  • 减去基地址后得到 6BA8,在Section64(__TEXT,__const)中找到了囊括 6BA86BA0 这排数据,再偏移8字节就是 6BA8 中的数据50 00 00 80也就是 TargetClassDescriptor 的起始地址 image.png

  • fieldDescriptorTargetClassDescriptor 结构中前边有4个 Int32 的属性,是4个4字节,所以往后查4个4字节,得到D4 03 00 00 image.png

  • D4 03 00 00 其实是 fieldDescriptor 的偏移地址而不是直接地址,所以要找到 fieldDescriptor 在 Mach-O 文件当中的具体位置,还需要加上 0x3D4(小端):

    0x6BB0 + 0x8 + 0x3D4 = 0x6F8C
    
  • FieldRecordsFieldDescriptor 结构体中还要偏移3个4字节、2个2字节 image.png

  • 得到了 FieldRecords 结构体的信息,但是注意这里存的都是偏移地址不是直接地址,还要算一下 image.png

  • 我们获取一下 FieldName 的地址信息,计算后减去基地址得到 6F59

    0x6F9C + 0x8 + 0xFFFFFFB5 = 0x100006F59
    
  • 可以在Section64(__TEXT,swift5_reflstr)中找到 agename image.png