推迟存储特点(Lazy)

  • 用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值得分支进行一个赋值的操作