通过SIL了解Swift的延迟存储属性(Lazy)

延迟存储属性(Lazy)

  • 用Lazy修饰的存储属性
  • 延迟存储实行必须有一个默认的初始值
  • 延迟存储属性在第一次访问的时候才被赋值
  • 延迟存储属性并不能保证线程安全
  • 延迟存储属性对实例对象大小有影响
Swift 复制代码
class FFHobbyGirls{
   lazy var age: Int = 20
}
var t = FFHobbyGirls()  
t.age = 30   //此处设置了断点 ①
print("end") //此处设置了断点 ②

当断点停留在①的地方打印FFHobbyGirls的内存结果为

terminal 复制代码
(lldb) x/8g 0x000000010049c730
0x10049c730: 0x00000001000081e8 0x0000000000000002
0x10049c740: 0x0000000000000000 0x0000000000000001 
0x10049c750: 0x5000000010040840 0x0000000000000000
0x10049c760: 0x0000000000000002 0x0002000010049e99

因为lazy关键字的原因,表现为延迟存储属性,并不会占用内存,所以当前0x000000000000000 为0,当过掉了断点①之后,打印FFHobbyGirls的内存结果为

terminal 复制代码
(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.

重点来了

第一点:

Swift 复制代码
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函数看一下操作

sil 复制代码
// 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方法

sil 复制代码
// 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方法看一下:

sil 复制代码
// 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值得分支进行一个赋值的操作

相关推荐
一丝晨光1 天前
继承、Lambda、Objective-C和Swift
开发语言·macos·ios·objective-c·swift·继承·lambda
KWMax2 天前
RxSwift系列(二)操作符
ios·swift·rxswift
Mamong2 天前
Swift并发笔记
开发语言·ios·swift
小溪彼岸2 天前
【iOS小组件】小组件尺寸及类型适配
swiftui·swift
Adam.com3 天前
#Swift :回调地狱 的解决 —— 通过 task/await 来替代 nested mutiple trailing closure 来进行 回调的解耦
开发语言·swift
Anakki3 天前
【Swift官方文档】7.Swift集合类型
运维·服务器·swift
KeithTsui3 天前
集合论(ZFC)之 联合公理(Axiom of Union)注解
开发语言·其他·算法·binder·swift
東三城3 天前
【ios】---swift开发从入门到放弃
ios·swift
文件夹__iOS7 天前
[SwiftUI 开发] @dynamicCallable 与 callAsFunction:将类型实例作为函数调用
ios·swiftui·swift