一、存储属性
1.1 存储属性概念
存储属性是一个作为特定类和结构体实例一部分的常量或变量。存储属性要么是变量存储属性 (由 var 关键字引入)要么是常量存储属性(由 let 关键字引入)。
class LGTeacher{
var age: Int
var name: String
}
比如这里的 age 和 name 就是我们所说的存储属性,这里我们需要加以区分的是 let 和 var 两者的区别:从定义上: let 用来声明常量,常量的值一旦设置好便不能再被更改; var 用来声明变量,变量的值可以在将来设置为不同的值。
1.2 let和var示例
class示例
struct示例
1.3 let和var分析
先建一个简单示例
var age = 18
let x = 20
进行汇编调试:
汇编上看两个值并没有区别都是一个立即数
用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 可以用 set、get 来修饰,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
...
}
willset 和 didset 都是在 setter 方法中被调用了。
3.3 初始化期间willSet/didSet
- 初始化期间设置属性时不会调用
willSet和didSet观察者 - 只有在为完全初始化的实例分配新值时才会调用它们。
示例:
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
}
延迟存储属性的初始值在其第一次使用时才进行计算
调用age的getter方法后
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'
- getter 方法调用过程先会获取到 age 的地址,看它有没有值
- 如果没有值也就是
Optional.none,就会调用bb2,这里会得到值并赋值到 age 的地址空间 - 如果有值,就会调用
bb1,将值直接返回 - 所以延迟存储属性并不是线程安全的。
五、类型属性
- 类型属性其实就是一个全局变量
- 类型属性只会被初始化一次
class Teacher {
// 只被初始化一次
static var age: Int = 18
}
// 可以修改
Teacher.age = 30
Swift单例实现
class Teacher {
static let sharedInstance = SSLTeacher()
// 指定初始化器私有化,外界访问不到
private init(){}
}
Teacher.sharedInstance