推迟存储特点(Lazy)
class FFHobbyGirls{
lazy var age: Int = 20
}
var t = FFHobbyGirls()
t.age = 30 //此处设置了断点 ①
print("end") //此处设置了断点 ②
当断点停留在①的当地打印FFHobbyGirls的内存成果为
(lldb) x/8g 0x000000010049c730
0x10049c730: 0x00000001000081e8 0x0000000000000002
0x10049c740: 0x0000000000000000 0x0000000000000001
0x10049c750: 0x5000000010040840 0x0000000000000000
0x10049c760: 0x0000000000000002 0x0002000010049e99
因为lazy关键字的原因,表现为推迟存储特点,并不会占用内存,所以当时0x000000000000000 为0,当过掉了断点①之后,打印FFHobbyGirls的内存成果为
(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 FFHobbyGirls{
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<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
alloc_global @main.t : main.FFHobbyGirls // id: %2
%3 = global_addr @main.t : main.FFHobbyGirls: $*FFHobbyGirls// users: %7, %8
%4 = metatype $@thick FFHobbyGirls.Type // user: %6
// function_ref FFHobbyGirls.__allocating_init()
%5 = function_ref @main.FFHobbyGirls.__allocating_init() -> main.FFHobbyGirls: $@convention(method) (@thick FFHobbyGirls.Type) -> @owned FFHobbyGirls// user: %6
%6 = apply %5(%4) : $@convention(method) (@thick FFHobbyGirls.Type) -> @owned FFHobbyGirls// user: %7
store %6 to %3 : $*FFHobbyGirls // id: %7
%8 = begin_access [read] [dynamic] %3 : $*FFHobbyGirls// users: %9, %11
%9 = load %8 : $*FFHobbyGirls // users: %16, %14, %15, %10
strong_retain %9 : $FFHobbyGirls // id: %10
end_access %8 : $*FFHobbyGirls // id: %11
%12 = integer_literal $Builtin.Int64, 30 // user: %13
%13 = struct $Int (%12 : $Builtin.Int64) // user: %15
%14 = class_method %9 : $FFHobbyGirls, FFHobbyGirls.age!setter : (FFHobbyGirls) -> (Int) -> (), $@convention(method) (Int, @guaranteed FFHobbyGirls) -> () // user: %15
%15 = apply %14(%13, %9) : $@convention(method) (Int, @guaranteed FFHobbyGirls) -> ()
strong_release %9 : $FFHobbyGirls // 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的当地,去找到FFHobbyGirls.age!setter办法
// FFHobbyGirls.age.setter
sil hidden @main.FFHobbyGirls.age.setter : Swift.Int : $@convention(method) (Int, @guaranteed FFHobbyGirls) -> () {
// %0 "value" // users: %4, %2
// %1 "self" // users: %5, %3
bb0(%0 : $Int, %1 : $FFHobbyGirls):
debug_value %0 : $Int, let, name "value", argno 1 // id: %2
debug_value %1 : $FFHobbyGirls, let, name "self", argno 2 // id: %3
%4 = enum $Optional<Int>, #Optional.some!enumelt, %0 : $Int // user: %7
%5 = ref_element_addr %1 : $FFHobbyGirls, #FFHobbyGirls.$__lazy_storage_$_age // user: %6
%6 = begin_access [modify] [dynamic] %5 : $*Optional<Int> // users: %7, %8
store %4 to %6 : $*Optional<Int> // id: %7
end_access %6 : $*Optional<Int> // id: %8
%9 = tuple () // user: %10
return %9 : $() // id: %10
} // end sil function 'main.FFHobbyGirls.age.setter : Swift.Int'
把咱们设置的参数传递了进来(t.age = 30),看%6的这一行,$*Optional,也就是说润饰Int的类型是Optional,那也意味着,对当时推迟存储来说,在默许编译器生出的结尾也是一个“?”
@_hasStorage @_hasInitialValue final var _lazy_storage$_age: Int? { get set }
在编译器操作的进程当中,默许这哥们是一个可选类型Optional,把一个确定好的值赋值给一个可选类型
第二点:
关于get办法来说的话,基于上面打印的内存信息,在为负值之前内存地址是0x0,那么sil的switch_enum的表现形式应该为Optional.none!结合FFHobbyGirls.age.getter办法看一下:
// FFHobbyGirls.age.getter
sil hidden [lazy_getter] [noinline] @main.FFHobbyGirls.age.getter : Swift.Int : $@convention(method) (@guaranteed FFHobbyGirls) -> Int {
// %0 "self" // users: %14, %2, %1
bb0(%0 : $FFHobbyGirls):
debug_value %0 : $FFHobbyGirls, let, name "self", argno 1 // id: %1
%2 = ref_element_addr %0 : $FFHobbyGirls, #FFHobbyGirls.$__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, 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<Int>, #Optional.some!enumelt, %11 : $Int // user: %16
%14 = ref_element_addr %0 : $FFHobbyGirls, #FFHobbyGirls.$__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)
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值得分支进行一个赋值的操作