Swift 进阶:三、属性

124 阅读5分钟

1 存储属性

1.1 存储属性的概述

存储属性是一个作为特定类和结构体实例一部分的常量或变量。存储属性要么是变量存储属性(由 var 关键字引入)要么是常量存储属性(由 let 关键字引入)。存储属性这里没有什么特别要强调的,因为随处可见

class LGTeacher{
    var age: Int
    var name: String
}

比如这里的 age 和 name 就是我们所说的存储属性,这里我们需要加以区分的是 let 和var 两者的区别,从定义上:

  • let 用来声明常量,常量的值一旦设置好便不能再被更改;
  • var 用来声明变量,变量的值可以在将来设置为不同的值。

1.2 存储属性的案例

class image.png struct

image.png

1.3 let和var的比较

1.3.1 汇编角度分析

先创建代码:

var age = 18
let x = 20
复制代码

进行汇编调试:

image.png

从汇编调试来看没有区别,都是将值存储到了寄存器中,下面通过 lldb 调试进行分析

image.png

lldb 调试来看也没有什么区别,都是存储在了 __DATA.__common 中,而且是相邻的地址。

1.3.2 sil 角度分析

将 main.swift 编译成 main.sil :

@_hasStorage @_hasInitialValue var age: Int { get set }
@_hasStorage @_hasInitialValue let x: Int { get }
...
复制代码
  • 通过 sil 我们可以发现,var 修饰的属性有 get 和 set 方法
  • let 修饰的属性只有 get 方法,所有 let 修饰的属性不能修改

2 计算属性

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

struct square{
    var width: Double
    var area: Double{
        get{
            return width * hegith
        }
        set{
            self.width = newValue
        }
    }
}

3 属性观察者

属性观察者会观察用来观察属性值的变化,一个 willSet 当属性将被改变调用,即使这个值与原有的值相同,而 didSet 在属性已经改变之后调用。它们的语法类似于 getter 和 setter。

class SubjectName{
    var subjectName: String = ""{
        willSet{
            print("subjectName will set value \(newValue)")
        }
        didSet{
            print("subjectName has been changed \(oldValue)")
        }
    }
}

这里我们在使用属性观察器的时候,需要注意的一点是在初始化期间设置属性时不会调用 willSet 和 didSet 观察者;只有在为完全初始化的实例分配新值时才会调用它们。运行下面这 段代码,你会发现当前并不会有任何的输出。

class SubjectName{
    var subjectName: String = "[unnamed]"{
        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进阶")

上面的属性观察者只是对存储属性起作用,如果我们想对计算属性起作用怎么办?很简单,只需 将相关代码添加到属性的 setter,也就是在setter方法调用newvalue和oldvalue即可查看属性变化值。我们先来看这段代码

class Square {
    var width: Double
    var area: Double {
        get {
            return width * width
        }
        set {
            let oldValue = self.area
            
            print("area will set value \(newValue)")
            self.width = sqrt(newValue)
            print("area has been changed \(oldValue)")
        }
    }
    init(width: Double) {
        self.width = width
    }
}

let square = Square(width: 10)
square.area = 400
print(square.area)

运行结果:
area will set value 400.0
area has been changed 100.0
400.0

3.1 属性观察者的继承

class JFTeacher {
    var age: Int {
        willSet{
            print("age will set value \(newValue)")
        } didSet{
            print("age has been changed \(oldValue)")
        }
    }
    var height: Double
    init(_ age: Int, _ height: Double) {
        self.age = age
        self.height = height
    }
}

class JFParTimeTeacher: JFTeacher {
    override var age: Int {
        willSet{
            print("override age will set value \(newValue)")
        } didSet{
            print("override age has been changed \(oldValue)")
        }
    }

    var subjectName: String
    init(_ subjectName: String) {
        self.subjectName = subjectName
        super.init(10, 180)
        self.age = 20
    }
}

var t = JFParTimeTeacher("Swift")
/*输出
 override age will set value 20   --- 子类的willSet
 age will set value 20            --- 父类的willSet 
 age has been changed 10          --- 父类的didSet 
 override age has been changed 10 --- 子类的didSet
 */

4 延迟存储属性

  • 延迟存储属性的初始值在其第一次使用时才进行计算,延迟存储属性必须有初始值
class JFTeacher {
    lazy var name: String //报错:Lazy properties must have an initializer
    lazy var name: String = "小明"//不报错
}
  • 用关键字 lazy 来标识一个延迟存储属性

5 类型属性

  • 类型属性其实就是一个全局变量
  • 类型属性只会被初始化一次
class JFTeacher {
    // 只被初始化一次
    static var age: Int = 18
}

// 可以修改
JFTeacher.age = 30

单例的实现

class JFTeacher {
    
    static let sharedInstance = JFTeacher()
    
    // 指定初始化器私有化,外界访问不到
    private init(){}
}

JFTeacher.sharedInstance

6 属性在源码中的结构

在第一节课的过程中我们讲到了 Metadata 的元数据结构,我们回顾一下

struct Metadata{
    var kind: Int
    var superClass: Any.Type
    var cacheData: (Int, Int)
    var data: Int
    var classFlags: Int32
    var instanceAddressPoint: UInt32
    var instanceSize: UInt32
    var instanceAlignmentMask: UInt16
    var reserved: UInt16
    var classSize: UInt32
    var classAddressPoint: UInt32
    var typeDescriptor: UnsafeMutableRawPointer
    var iVarDestroyer: UnsafeRawPointer
}

上一节课讲到方法调度的过程中我们认识了 typeDescriptor ,这里面记录了 V-Table 的相关信息,接下来我们需要认识一下 typeDescriptor 中的 fieldDescripto

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
    var size: UInt32
    //V-Table
}

fieldDescriptor 记录了当前的属性信息,其中 fieldDescriptor 在源码中的结构如下: 其中 NumFields 代表当前有多少个属性, FieldRecords 记录了每个属性的信息,

struct FieldDescriptor {
    MangledTypeName int32
    Superclass int32
    Kind uint16
    FieldRecordSize uint16
    NumFields uint32
    FieldRecords [FieldRecord]
}

FieldRecords 的结构体如下:

struct FieldRecord{
    Flags uint32
    MangledTypeName int32
    FieldName int32
}