Swift属性

202 阅读3分钟

一、存储属性

1.1 存储属性概念

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

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

比如这里的 age 和 name 就是我们所说的存储属性,这里我们需要加以区分的是 let 和 var 两者的区别:从定义上: let 用来声明常量,常量的值一旦设置好便不能再被更改; var 用来声明变量,变量的值可以在将来设置为不同的值。

1.2 let和var示例

class示例

image.png

struct示例

image.png

1.3 let和var分析

先建一个简单示例

var age = 18
let x = 20

进行汇编调试:

image.png

汇编上看两个值并没有区别都是一个立即数

用lldb调试查看内存详情,内存上也是连续的

(lldb) po withUnsafePointer(to: &age){print($0)}
0x0000000100008158
0 elements
(lldb) x/8g 0x0000000100008158
0x100008158: 0x0000000000000012 0x0000000000000014
0x100008168: 0x0000000000000000 0x0000000000000000
0x100008178: 0x0000000000000000 0x0000000000000000
0x100008188: 0x0000000000000000 0x0000000000000000

用命令swiftc -emit-ir查看sil中间代码

@_hasStorage @_hasInitialValue var age: Int { get set }
@_hasStorage @_hasInitialValue let x: Int { get }

发现了两者的不同,在于var属性有get和set方法,let属性只有get方法

二、计算属性

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

2.1 计算属性示例
计算属性写法

在 Swift 中,重写一个实例的 setter 和 getter 可以用 setget 来修饰,setter 传入新的默认值叫做 newValue,也可以自定义。Swift 中,在拥有返回值的方法里,如果方法内部只有 return,那么可以直接省略 return。

struct Circle {
    var radius: Double
    var diameter: Double {
        set {
            radius = newValue / 2
        }
        get {
            radius * 2
        }
    }
}
只读计算属性
struct Circle {
    var radius: Double
    var diameter: Double {
        get {
            radius * 2
        }
    }
}

或者

struct Circle {
    var radius: Double
    private(set) var diameter: Double
    
    init(_ radius: Double) {
        self.radius = 10
        diameter = radius * 2
    }
}

前者不管是内部访问还是外部访问,都不能进行赋值。而后者是内部可以进行赋值,外部不能进行赋值

2.2 计算属性本质
示例
struct Circle {
    var radius: Double
    var diameter: Double {
        set {
            radius = newValue / 2
        }
        get {
            radius * 2
        }
    }
}

var c = Circle(radius: 10)
c.radius = 20
c.diameter = 30
let a = c.diameter
结构体在sil中的实现
struct Circle {
  @_hasStorage var radius: Double { get set }
  var diameter: Double { get set }
  init(radius: Double)
}

radius 和 diameter 后面虽然有 { get set },但前面的修饰符却是又区别的。radius 有 @_hasStorage 修饰,diameter 没有,说明 radius 属性是一个可存储的值,这点和第一大点就相呼应了。而 diameter 没有 @_hasStorage修饰,说明,diameter 只有 setter 和 getter 方法。

存储属性getter/getter在sil内部的实现

查看sil中radius属性的getter和setter实现

// Circle.radius.getter
sil hidden [transparent] @$s4main6CircleV6radiusSdvg : $@convention(method) (Circle) -> Double {
// %0 "self"                                      // users: %2, %1
bb0(%0 : $Circle):
  debug_value %0 : $Circle, let, name "self", argno 1 // id: %1
  %2 = struct_extract %0 : $Circle, #Circle.radius // user: %3
  return %2 : $Double                             // id: %3
} // end sil function '$s4main6CircleV6radiusSdvg'
// Circle.radius.setter
sil hidden [transparent] @$s4main6CircleV6radiusSdvs : $@convention(method) (Double, @inout Circle) -> () {
// %0 "value"                                     // users: %6, %2
// %1 "self"                                      // users: %4, %3
bb0(%0 : $Double, %1 : $*Circle):
  debug_value %0 : $Double, let, name "value", argno 1 // id: %2
  debug_value_addr %1 : $*Circle, var, name "self", argno 2 // id: %3
  %4 = begin_access [modify] [static] %1 : $*Circle // users: %7, %5
  %5 = struct_element_addr %4 : $*Circle, #Circle.radius // user: %6
  store %0 to %5 : $*Double                       // id: %6
  end_access %4 : $*Circle                        // id: %7
  %8 = tuple ()                                   // user: %9
  return %8 : $()                                 // id: %9
} // end sil function '$s4main6CircleV6radiusSdvs'

调用 radius 的 getter 的时候,底层的 sil 拿到 Circle 中 radius 的值直接返回。在调用 radius 的 setter 的时候,会取 Circle 实例的地址,对 radius 进行修改。

计算属性getter/setter在sil内部的实现

查看sil中diameter属性的getter和setter实现

// Circle.diameter.getter
sil hidden @$s4main6CircleV8diameterSdvg : $@convention(method) (Circle) -> Double {
// %0 "self"                                      // users: %2, %1
bb0(%0 : $Circle):
  debug_value %0 : $Circle, let, name "self", argno 1 // id: %1
  %2 = struct_extract %0 : $Circle, #Circle.radius // user: %4
  %3 = float_literal $Builtin.FPIEEE64, 0x4000000000000000 // 2 // user: %5
  %4 = struct_extract %2 : $Double, #Double._value // user: %5
  %5 = builtin "fmul_FPIEEE64"(%4 : $Builtin.FPIEEE64, %3 : $Builtin.FPIEEE64) : $Builtin.FPIEEE64 // user: %6
  %6 = struct $Double (%5 : $Builtin.FPIEEE64)    // user: %7
  return %6 : $Double                             // id: %7
} // end sil function '$s4main6CircleV8diameterSdvg'
// Circle.diameter.setter
sil hidden @$s4main6CircleV8diameterSdvs : $@convention(method) (Double, @inout Circle) -> () {
// %0 "newValue"                                  // users: %5, %2
// %1 "self"                                      // users: %8, %3
bb0(%0 : $Double, %1 : $*Circle):
  debug_value %0 : $Double, let, name "newValue", argno 1 // id: %2
  debug_value_addr %1 : $*Circle, var, name "self", argno 2 // id: %3
  %4 = float_literal $Builtin.FPIEEE64, 0x4000000000000000 // 2 // user: %6
  %5 = struct_extract %0 : $Double, #Double._value // user: %6
  %6 = builtin "fdiv_FPIEEE64"(%5 : $Builtin.FPIEEE64, %4 : $Builtin.FPIEEE64) : $Builtin.FPIEEE64 // user: %7
  %7 = struct $Double (%6 : $Builtin.FPIEEE64)    // user: %10
  %8 = begin_access [modify] [static] %1 : $*Circle // users: %11, %9
  %9 = struct_element_addr %8 : $*Circle, #Circle.radius // user: %10
  store %7 to %9 : $*Double                       // id: %10
  end_access %8 : $*Circle                        // id: %11
  %12 = tuple ()                                  // user: %13
  return %12 : $()                                // id: %13
} // end sil function '$s4main6CircleV8diameterSdvs'

diameter 属性 setter 的内部实现会自动生成一个名为 newValue 的常量,并且会把外部传进来的值赋值给 newValue。观察 diameter 属性 的 setter 和 getter 中,并未发现有 diameter 相关的存储变量。所以其实,计算属性根本不会有存储在实例的成员变量,那也就意味着计算属性不占内存。计算属性的本质就是方法(函数),并且计算属性并不占用实例的内存。

三、属性观察者

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

3.1 属性观察者示例
class SubjectName {
    var subjectName: String = ""{
        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 
3.2 属性观察者sil内部实现
class SubjectName {
  @_hasStorage @_hasInitialValue var subjectName: String { get set }
}

// SubjectName.subjectName.setter
sil hidden @$s4main11SubjectNameC07subjectC0SSvw  : $@convention(method) (@owned String, @guaranteed SubjectName) -> () {
   ...
  // function_ref SubjectName.subjectName.willset
  ...
  // function_ref SubjectName.subjectName.didset
  ...
} 

willsetdidset 都是在 setter 方法中被调用了。

3.3 初始化期间willSet/didSet
  • 初始化期间设置属性时不会调用 willSetdidSet 观察者
  • 只有在为完全初始化的实例分配新值时才会调用它们。

示例:

class SubjectName {
    var subjectName: String = ""{
        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")

运行上面例子并不打印willSet和didSet方法的内容,查看sil文件

// SubjectName.init(subjectName:)
sil hidden @$s4main11SubjectNameC07subjectC0ACSS_tcfc : $@convention(method) (@owned String, @owned SubjectName) -> @owned SubjectName {
	...
  %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
  ...
} // end sil function '$s4main11SubjectNameC07subjectC0ACSS_tcfc'

通过 sil 文件,发现初始化时并没有调用 setter 方法,而是对属性的地址直接进行了赋值,所以监听方法不会被调用。

3.4 计算属性观察者

上面的属性观察者只是对存储属性起作用,如果我们想对计算属性起作用怎么办呢?我们可以在setter方法中添加观察的代码

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.6 继承属性观察者
class Teacher {
    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 ParTimeTeacher: Teacher {
    
    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 = ParTimeTeacher("Swift")
//运行结果
//override age will set value 20
//age will set value 20
//age has been changed 10
//override age has been changed 10

四、延迟存储属性

4.1 延迟存储属性示例

用关键字 lazy 来标识一个延迟存储属性,延迟存储属性必须有初始值

class Person {
    lazy var age: Int = 18
}

延迟存储属性的初始值在其第一次使用时才进行计算

image.png

调用age的getter方法后

image.png

4.2 延时存储属性sil原理

查看sil源码,观察 age 的声明是有个 符号的,说明 age 是个可选类型

class Person {
  lazy var age: Int { get set }
  @_hasStorage @_hasInitialValue final var $__lazy_storage_$_age: Int? { get set }
  @objc deinit
  init()
}

age 的初始化,默认是被赋值了 Optional.none 也就是 0

// variable initialization expression of Person.$__lazy_storage_$_age
sil hidden [transparent] @$s4main6PersonC21$__lazy_storage_$_age029_12232F587A4C5CD8B1EEDF696793G2FCLLSiSgvpfi : $@convention(thin) () -> Optional<Int> {
bb0:
  %0 = enum $Optional<Int>, #Optional.none!enumelt // user: %1
  return %0 : $Optional<Int>                      // id: %1
} // end sil function '$s4main6PersonC21$__lazy_storage_$_age029_12232F587A4C5CD8B1EEDF696793G2FCLLSiSgvpfi'

查看 age 的 getter 方法,看下它的调用过程:

// Person.age.getter
sil hidden [lazy_getter] [noinline] @$s4main6PersonC3ageSivg : $@convention(method) (@guaranteed Person) -> Int {
// %0 "self"                                      // users: %14, %2, %1
bb0(%0 : $Person):
  debug_value %0 : $Person, let, name "self", argno 1 // id: %1
  %2 = ref_element_addr %0 : $Person, #Person.$__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 : $Person, #Person.$__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
} // end sil function '$s4main6PersonC3ageSivg'
  1. getter 方法调用过程先会获取到 age 的地址,看它有没有值
  2. 如果没有值也就是 Optional.none ,就会调用 bb2,这里会得到值并赋值到 age 的地址空间
  3. 如果有值,就会调用 bb1,将值直接返回
  4. 所以延迟存储属性并不是线程安全的。

五、类型属性

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

// 可以修改
Teacher.age = 30

Swift单例实现

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