Swift延迟存储属性(Lazy)

延迟存储属性(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<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
  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<Int>, #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<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.LGTeacher.age.setter : Swift.Int'

把我们设置的参数传递了进来(t.age = 30),看%6的这一行,$*Optional<Int>,也就是说修饰Int的类型是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<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 : $LGTeacher, #LGTeacher.$__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<Int>, 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<Int>
综上论述就是懒加载(Lazy),及延迟存储属性的本质

总结:
在我们的底层过程中,首先是一个Optional,在没有被访问之前默认值是nil,在内存的表现是0x0,在第一次访问的过程中,访问的是getter方法,通过enum值得分支进行一个赋值的操作

推荐阅读更多精彩内容

  • 久违的晴天,家长会。 家长大会开好到教室时,离放学已经没多少时间了。班主任说已经安排了三个家长分享经验。 放学铃声...
    飘雪儿5阅读 4,514评论 16 21
  • 今天感恩节哎,感谢一直在我身边的亲朋好友。感恩相遇!感恩不离不弃。 中午开了第一次的党会,身份的转变要...
    迷月闪星情阅读 7,229评论 0 6
  • 哈里·基恩想和新教练何塞·穆里尼奥建立一种“牢固的关系”,这将有助于托特纳姆更上一层楼。 凯恩在4-2战胜奥林匹亚...
    疯狂SPORTS阅读 5,302评论 0 5
  • 可爱进取,孤独成精。努力飞翔,天堂翱翔。战争美好,孤独进取。胆大飞翔,成就辉煌。努力进取,遥望,和谐家园。可爱游走...
    赵原野阅读 1,036评论 1 1
  • 在妖界我有个名头叫胡百晓,无论是何事,只要找到胡百晓即可有解决的办法。因为是只狐狸大家以讹传讹叫我“倾城百晓”,...
    猫九0110阅读 963评论 2 3