延迟存储属性(Lazy)
- 用Lazy修饰的存储属性
- 延迟存储实行必须有一个默认的初始值
- 延迟存储属性在第一次访问的时候才被赋值
- 延迟存储属性并不能保证线程安全
- 延迟存储属性对实例对象大小有影响
class LGTeacher {
lazy var age: Int = 20
}
var t = LGTeacher()
t.age = 30 //此处设置了断点 ①
print("end") //此处设置了断点 ②
当断点停留在①的地方打印LGTeacher的内存结果为
(lldb) x/8g 0x000000010049c730
0x10049c730: 0x00000001000081e8 0x0000000000000002
0x10049c740: 0x0000000000000000 0x0000000000000001
0x10049c750: 0x5000000010040840 0x0000000000000000
0x10049c760: 0x0000000000000002 0x0002000010049e99
因为lazy关键字的原因,表现为延迟存储属性,并不会占用内存,所以当前0x000000000000000 为0,当过掉了断点①之后,打印LGTeacher的内存结果为
(lldb) x/8g 0x000000010049c730
0x10049c730: 0x00000001000081e8 0x0000000000000002
0x10049c740: 0x000000000000001e 0x0000000000000000
0x10049c750: 0x5000000010040840 0x0000000000000000
0x10049c760: 0x0000000000000002 0x0002000010049e99
(lldb)
由于完成了t.age = 30 的set操作,age的内存发生了变化,变成了0x000000000000001e
在SIL角度来剖析一下原理:
打开项目文件夹目录
cd /Users/zhou/Desktop/SwiftTwoPractice/SwiftTwoPractice
把 mian.swift编译成main.sil并打开(推荐使用vs code)
swiftc -emit-sil main.swift | xcrun swift-demangle >> ./main.sil && open main.sil
No application knows how to open /Users/zhou/Desktop/SwiftTwoPractice/SwiftTwoPractice/main.sil.
重点来了
第一点:
class LGTeacher {
lazy var age: Int { get set }
@_hasStorage @_hasInitialValue final var $__lazy_storage_$_age: Int? { get set }
@objc deinit
init()
}
当前_lazy_storage$_age是使用final来修饰的,在访问这个变量的过程中就是在访问这个函数的get方法,接下来找到main函数看一下操作
// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer>>):
alloc_global @main.t : main.LGTeacher // id: %2
%3 = global_addr @main.t : main.LGTeacher : $*LGTeacher // users: %7, %8
%4 = metatype $@thick LGTeacher.Type // user: %6
// function_ref LGTeacher.__allocating_init()
%5 = function_ref @main.LGTeacher.__allocating_init() -> main.LGTeacher : $@convention(method) (@thick LGTeacher.Type) -> @owned LGTeacher // user: %6
%6 = apply %5(%4) : $@convention(method) (@thick LGTeacher.Type) -> @owned LGTeacher // user: %7
store %6 to %3 : $*LGTeacher // id: %7
%8 = begin_access [read] [dynamic] %3 : $*LGTeacher // users: %9, %11
%9 = load %8 : $*LGTeacher // users: %16, %14, %15, %10
strong_retain %9 : $LGTeacher // id: %10
end_access %8 : $*LGTeacher // id: %11
%12 = integer_literal $Builtin.Int64, 30 // user: %13
%13 = struct $Int (%12 : $Builtin.Int64) // user: %15
%14 = class_method %9 : $LGTeacher, LGTeacher.age!setter : (LGTeacher) -> (Int) -> (), $@convention(method) (Int, @guaranteed LGTeacher) -> () // user: %15
%15 = apply %14(%13, %9) : $@convention(method) (Int, @guaranteed LGTeacher) -> ()
strong_release %9 : $LGTeacher // id: %16
%17 = integer_literal $Builtin.Int32, 0 // user: %18
%18 = struct $Int32 (%17 : $Builtin.Int32) // user: %19
return %18 : $Int32 // id: %19
} // end sil function 'main'
在%14的地方,去找到LGTeacher.age!setter方法
// LGTeacher.age.setter
sil hidden @main.LGTeacher.age.setter : Swift.Int : $@convention(method) (Int, @guaranteed LGTeacher) -> () {
// %0 "value" // users: %4, %2
// %1 "self" // users: %5, %3
bb0(%0 : $Int, %1 : $LGTeacher):
debug_value %0 : $Int, let, name "value", argno 1 // id: %2
debug_value %1 : $LGTeacher, let, name "self", argno 2 // id: %3
%4 = enum $Optional, #Optional.some!enumelt, %0 : $Int // user: %7
%5 = ref_element_addr %1 : $LGTeacher, #LGTeacher.$__lazy_storage_$_age // user: %6
%6 = begin_access [modify] [dynamic] %5 : $*Optional // users: %7, %8
store %4 to %6 : $*Optional // id: %7
end_access %6 : $*Optional // id: %8
%9 = tuple () // user: %10
return %9 : $() // id: %10
} // end sil function 'main.LGTeacher.age.setter : Swift.Int'
把我们设置的参数传递了进来(t.age = 30),看%6的这一行,$*Optional
@_hasStorage @_hasInitialValue final var _lazy_storage$_age: Int? { get set }
在编译器操作的过程当中,默认这哥们是一个可选类型Optional,把一个确定好的值赋值给一个可选类型
第二点:
对于get方法来说的话,基于上面打印的内存信息,在为负值之前内存地址是0x0,那么sil的switch_enum的表现形式应该为Optional.none!结合LGTeacher.age.getter方法看一下:
// LGTeacher.age.getter
sil hidden [lazy_getter] [noinline] @main.LGTeacher.age.getter : Swift.Int : $@convention(method) (@guaranteed LGTeacher) -> Int {
// %0 "self" // users: %14, %2, %1
bb0(%0 : $LGTeacher):
debug_value %0 : $LGTeacher, let, name "self", argno 1 // id: %1
%2 = ref_element_addr %0 : $LGTeacher, #LGTeacher.$__lazy_storage_$_age // user: %3
%3 = begin_access [read] [dynamic] %2 : $*Optional // users: %4, %5
%4 = load %3 : $*Optional // user: %6
end_access %3 : $*Optional // id: %5
switch_enum %4 : $Optional, 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, 20 // user: %11
%11 = struct $Int (%10 : $Builtin.Int64) // users: %18, %13, %12
debug_value %11 : $Int, let, name "tmp2" // id: %12
%13 = enum $Optional, #Optional.some!enumelt, %11 : $Int // user: %16
%14 = ref_element_addr %0 : $LGTeacher, #LGTeacher.$__lazy_storage_$_age // user: %15
%15 = begin_access [modify] [dynamic] %14 : $*Optional // users: %16, %17
store %13 to %15 : $*Optional // id: %16
end_access %15 : $*Optional // id: %17
br bb3(%11 : $Int)
switch_enum %4 : $Optional
, case Optional.some!enumelt: bb1, case Optional.none!enumelt: bb2 // id: %6
getter内当case是Optional.none!时候,会进入bb2,那也就意味着进入了bb2代码分支,在我第一次访问的过程中,把age(20)给到了可选类型,也就意味着在第一次访问的过程中从没有值变成了有值的操作
%10 = integer_literal $Builtin.Int64, 20
store %13 to %15 : $*Optional
综上论述就是懒加载(Lazy),及延迟存储属性的本质
总结:
在我们的底层过程中,首先是一个Optional,在没有被访问之前默认值是nil,在内存的表现是0x0,在第一次访问的过程中,访问的是getter方法,通过enum值得分支进行一个赋值的操作