lazy - ShenYj/ShenYj.github.io GitHub Wiki

lazy

延迟属性

在探索延迟属性前,准备一个足够简单的 Swift class 文件,并通过以下命令生成 SIL

swiftc -sdk /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator15.2.sdk -target x86_64-apple-ios15.2-simulator -emit-sil xxx.swift | xcrun swift-demangle >> xxx.sil

通过SIL对比

准备

  • 普通存储型属性的类文件 Normal

    public class SwiftClass {
        
        var age: Int = 20
    }
  • 延迟属性的类文件 Lazy

    public class Lazy {
        lazy var age: Int = 20
    }
编译成 SIL 代码 (普通存储型属性)
sil_stage canonical

import Builtin
import Swift
import SwiftShims

import Foundation

@_hasMissingDesignatedInitializers public class Normal {
  @_hasStorage @_hasInitialValue var age: Int { get set }
  @objc deinit
  init()
}

// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
  %2 = integer_literal $Builtin.Int32, 0          // user: %3
  %3 = struct $Int32 (%2 : $Builtin.Int32)        // user: %4
  return %3 : $Int32                              // id: %4
} // end sil function 'main'

// variable initialization expression of Normal.age
sil [transparent] @variable initialization expression of Normal.Normal.age : Swift.Int : $@convention(thin) () -> Int {
bb0:
  %0 = integer_literal $Builtin.Int64, 20         // user: %1
  %1 = struct $Int (%0 : $Builtin.Int64)          // user: %2
  return %1 : $Int                                // id: %2
} // end sil function 'variable initialization expression of Normal.Normal.age : Swift.Int'

// Int.init(_builtinIntegerLiteral:)
sil public_external [transparent] @Swift.Int.init(_builtinIntegerLiteral: Builtin.IntLiteral) -> Swift.Int : $@convention(method) (Builtin.IntLiteral, @thin Int.Type) -> Int {
// %0                                             // user: %2
bb0(%0 : $Builtin.IntLiteral, %1 : $@thin Int.Type):
  %2 = builtin "s_to_s_checked_trunc_IntLiteral_Int64"(%0 : $Builtin.IntLiteral) : $(Builtin.Int64, Builtin.Int1) // user: %3
  %3 = tuple_extract %2 : $(Builtin.Int64, Builtin.Int1), 0 // user: %4
  %4 = struct $Int (%3 : $Builtin.Int64)          // user: %5
  return %4 : $Int                                // id: %5
} // end sil function 'Swift.Int.init(_builtinIntegerLiteral: Builtin.IntLiteral) -> Swift.Int'

// Normal.age.getter
sil hidden [transparent] @Normal.Normal.age.getter : Swift.Int : $@convention(method) (@guaranteed Normal) -> Int {
// %0 "self"                                      // users: %2, %1
bb0(%0 : $Normal):
  debug_value %0 : $Normal, let, name "self", argno 1 // id: %1
  %2 = ref_element_addr %0 : $Normal, #Normal.age // user: %3
  %3 = begin_access [read] [dynamic] %2 : $*Int   // users: %4, %5
  %4 = load %3 : $*Int                            // user: %6
  end_access %3 : $*Int                           // id: %5
  return %4 : $Int                                // id: %6
} // end sil function 'Normal.Normal.age.getter : Swift.Int'

// Normal.age.setter
sil hidden [transparent] @Normal.Normal.age.setter : Swift.Int : $@convention(method) (Int, @guaranteed Normal) -> () {
// %0 "value"                                     // users: %6, %2
// %1 "self"                                      // users: %4, %3
bb0(%0 : $Int, %1 : $Normal):
  debug_value %0 : $Int, let, name "value", argno 1 // id: %2
  debug_value %1 : $Normal, let, name "self", argno 2 // id: %3
  %4 = ref_element_addr %1 : $Normal, #Normal.age // user: %5
  %5 = begin_access [modify] [dynamic] %4 : $*Int // users: %6, %7
  store %0 to %5 : $*Int                          // id: %6
  end_access %5 : $*Int                           // id: %7
  %8 = tuple ()                                   // user: %9
  return %8 : $()                                 // id: %9
} // end sil function 'Normal.Normal.age.setter : Swift.Int'

// Normal.age.modify
sil hidden [transparent] @Normal.Normal.age.modify : Swift.Int : $@yield_once @convention(method) (@guaranteed Normal) -> @yields @inout Int {
// %0 "self"                                      // users: %2, %1
bb0(%0 : $Normal):
  debug_value %0 : $Normal, let, name "self", argno 1 // id: %1
  %2 = ref_element_addr %0 : $Normal, #Normal.age // user: %3
  %3 = begin_access [modify] [dynamic] %2 : $*Int // users: %5, %8, %4
  yield %3 : $*Int, resume bb1, unwind bb2        // id: %4

bb1:                                              // Preds: bb0
  end_access %3 : $*Int                           // id: %5
  %6 = tuple ()                                   // user: %7
  return %6 : $()                                 // id: %7

bb2:                                              // Preds: bb0
  end_access %3 : $*Int                           // id: %8
  unwind                                          // id: %9
} // end sil function 'Normal.Normal.age.modify : Swift.Int'

// Normal.deinit
sil @Normal.Normal.deinit : $@convention(method) (@guaranteed Normal) -> @owned Builtin.NativeObject {
// %0 "self"                                      // users: %2, %1
bb0(%0 : $Normal):
  debug_value %0 : $Normal, let, name "self", argno 1 // id: %1
  %2 = unchecked_ref_cast %0 : $Normal to $Builtin.NativeObject // user: %3
  return %2 : $Builtin.NativeObject               // id: %3
} // end sil function 'Normal.Normal.deinit'

// Normal.__deallocating_deinit
sil @Normal.Normal.__deallocating_deinit : $@convention(method) (@owned Normal) -> () {
// %0 "self"                                      // users: %3, %1
bb0(%0 : $Normal):
  debug_value %0 : $Normal, let, name "self", argno 1 // id: %1
  // function_ref Normal.deinit
  %2 = function_ref @Normal.Normal.deinit : $@convention(method) (@guaranteed Normal) -> @owned Builtin.NativeObject // user: %3
  %3 = apply %2(%0) : $@convention(method) (@guaranteed Normal) -> @owned Builtin.NativeObject // user: %4
  %4 = unchecked_ref_cast %3 : $Builtin.NativeObject to $Normal // user: %5
  dealloc_ref %4 : $Normal                        // id: %5
  %6 = tuple ()                                   // user: %7
  return %6 : $()                                 // id: %7
} // end sil function 'Normal.Normal.__deallocating_deinit'

// Normal.__allocating_init()
sil hidden [exact_self_class] @Normal.Normal.__allocating_init() -> Normal.Normal : $@convention(method) (@thick Normal.Type) -> @owned Normal {
// %0 "$metatype"
bb0(%0 : $@thick Normal.Type):
  %1 = alloc_ref $Normal                          // user: %3
  // function_ref Normal.init()
  %2 = function_ref @Normal.Normal.init() -> Normal.Normal : $@convention(method) (@owned Normal) -> @owned Normal // user: %3
  %3 = apply %2(%1) : $@convention(method) (@owned Normal) -> @owned Normal // user: %4
  return %3 : $Normal                             // id: %4
} // end sil function 'Normal.Normal.__allocating_init() -> Normal.Normal'

// Normal.init()
sil hidden @Normal.Normal.init() -> Normal.Normal : $@convention(method) (@owned Normal) -> @owned Normal {
// %0 "self"                                      // users: %2, %6, %1
bb0(%0 : $Normal):
  debug_value %0 : $Normal, let, name "self", argno 1 // id: %1
  %2 = ref_element_addr %0 : $Normal, #Normal.age // user: %5
  %3 = integer_literal $Builtin.Int64, 20         // user: %4
  %4 = struct $Int (%3 : $Builtin.Int64)          // user: %5
  store %4 to %2 : $*Int                          // id: %5
  return %0 : $Normal                             // id: %6
} // end sil function 'Normal.Normal.init() -> Normal.Normal'

sil_vtable Normal {
  #Normal.age!getter: (Normal) -> () -> Int : @Normal.Normal.age.getter : Swift.Int	// Normal.age.getter
  #Normal.age!setter: (Normal) -> (Int) -> () : @Normal.Normal.age.setter : Swift.Int	// Normal.age.setter
  #Normal.age!modify: (Normal) -> () -> () : @Normal.Normal.age.modify : Swift.Int	// Normal.age.modify
  #Normal.init!allocator: (Normal.Type) -> () -> Normal : @Normal.Normal.__allocating_init() -> Normal.Normal	// Normal.__allocating_init()
  #Normal.deinit!deallocator: @Normal.Normal.__deallocating_deinit	// Normal.__deallocating_deinit
}



// Mappings from '#fileID' to '#filePath':
//   'Normal/Normal.swift' => 'Normal.swift'


编译成 SIL 代码 (延迟属性)
sil_stage canonical

import Builtin
import Swift
import SwiftShims

import Foundation

@_hasMissingDesignatedInitializers public class Lazy {
  lazy var age: Int { get set }
  @_hasStorage @_hasInitialValue final var $__lazy_storage_$_age: Int? { get set }
  @objc deinit
  init()
}

// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
  %2 = integer_literal $Builtin.Int32, 0          // user: %3
  %3 = struct $Int32 (%2 : $Builtin.Int32)        // user: %4
  return %3 : $Int32                              // id: %4
} // end sil function 'main'

// Lazy.age.getter
sil hidden [lazy_getter] [noinline] @Lazy.Lazy.age.getter : Swift.Int : $@convention(method) (@guaranteed Lazy) -> Int {
// %0 "self"                                      // users: %14, %2, %1
bb0(%0 : $Lazy):
  debug_value %0 : $Lazy, let, name "self", argno 1 // id: %1
  %2 = ref_element_addr %0 : $Lazy, #Lazy.$__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 : $Lazy, #Lazy.$__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)                              // id: %18

// %19                                            // user: %20
bb3(%19 : $Int):                                  // Preds: bb2 bb1
  return %19 : $Int                               // id: %20
} // end sil function 'Lazy.Lazy.age.getter : Swift.Int'

// Int.init(_builtinIntegerLiteral:)
sil public_external [transparent] @Swift.Int.init(_builtinIntegerLiteral: Builtin.IntLiteral) -> Swift.Int : $@convention(method) (Builtin.IntLiteral, @thin Int.Type) -> Int {
// %0                                             // user: %2
bb0(%0 : $Builtin.IntLiteral, %1 : $@thin Int.Type):
  %2 = builtin "s_to_s_checked_trunc_IntLiteral_Int64"(%0 : $Builtin.IntLiteral) : $(Builtin.Int64, Builtin.Int1) // user: %3
  %3 = tuple_extract %2 : $(Builtin.Int64, Builtin.Int1), 0 // user: %4
  %4 = struct $Int (%3 : $Builtin.Int64)          // user: %5
  return %4 : $Int                                // id: %5
} // end sil function 'Swift.Int.init(_builtinIntegerLiteral: Builtin.IntLiteral) -> Swift.Int'

// Lazy.age.setter
sil hidden @Lazy.Lazy.age.setter : Swift.Int : $@convention(method) (Int, @guaranteed Lazy) -> () {
// %0 "value"                                     // users: %4, %2
// %1 "self"                                      // users: %5, %3
bb0(%0 : $Int, %1 : $Lazy):
  debug_value %0 : $Int, let, name "value", argno 1 // id: %2
  debug_value %1 : $Lazy, let, name "self", argno 2 // id: %3
  %4 = enum $Optional<Int>, #Optional.some!enumelt, %0 : $Int // user: %7
  %5 = ref_element_addr %1 : $Lazy, #Lazy.$__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 'Lazy.Lazy.age.setter : Swift.Int'

// Lazy.age.modify
sil hidden [transparent] @Lazy.Lazy.age.modify : Swift.Int : $@yield_once @convention(method) (@guaranteed Lazy) -> @yields @inout Int {
// %0 "self"                                      // users: %9, %15, %4, %1
bb0(%0 : $Lazy):
  debug_value %0 : $Lazy, let, name "self", argno 1 // id: %1
  %2 = alloc_stack $Int                           // users: %13, %7, %5, %10, %16, %6
  // function_ref Lazy.age.getter
  %3 = function_ref @Lazy.Lazy.age.getter : Swift.Int : $@convention(method) (@guaranteed Lazy) -> Int // user: %4
  %4 = apply %3(%0) : $@convention(method) (@guaranteed Lazy) -> Int // user: %5
  store %4 to %2 : $*Int                          // id: %5
  yield %2 : $*Int, resume bb1, unwind bb2        // id: %6

bb1:                                              // Preds: bb0
  %7 = load %2 : $*Int                            // user: %9
  // function_ref Lazy.age.setter
  %8 = function_ref @Lazy.Lazy.age.setter : Swift.Int : $@convention(method) (Int, @guaranteed Lazy) -> () // user: %9
  %9 = apply %8(%7, %0) : $@convention(method) (Int, @guaranteed Lazy) -> ()
  dealloc_stack %2 : $*Int                        // id: %10
  %11 = tuple ()                                  // user: %12
  return %11 : $()                                // id: %12

bb2:                                              // Preds: bb0
  %13 = load %2 : $*Int                           // user: %15
  // function_ref Lazy.age.setter
  %14 = function_ref @Lazy.Lazy.age.setter : Swift.Int : $@convention(method) (Int, @guaranteed Lazy) -> () // user: %15
  %15 = apply %14(%13, %0) : $@convention(method) (Int, @guaranteed Lazy) -> ()
  dealloc_stack %2 : $*Int                        // id: %16
  unwind                                          // id: %17
} // end sil function 'Lazy.Lazy.age.modify : Swift.Int'

// variable initialization expression of Lazy.$__lazy_storage_$_age
sil [transparent] @variable initialization expression of Lazy.Lazy.($__lazy_storage_$_age in _E855EA26640240F00798E8BB7EA69620) : Swift.Int? : $@convention(thin) () -> Optional<Int> {
bb0:
  %0 = enum $Optional<Int>, #Optional.none!enumelt // user: %1
  return %0 : $Optional<Int>                      // id: %1
} // end sil function 'variable initialization expression of Lazy.Lazy.($__lazy_storage_$_age in _E855EA26640240F00798E8BB7EA69620) : Swift.Int?'

// Lazy.deinit
sil @Lazy.Lazy.deinit : $@convention(method) (@guaranteed Lazy) -> @owned Builtin.NativeObject {
// %0 "self"                                      // users: %2, %1
bb0(%0 : $Lazy):
  debug_value %0 : $Lazy, let, name "self", argno 1 // id: %1
  %2 = unchecked_ref_cast %0 : $Lazy to $Builtin.NativeObject // user: %3
  return %2 : $Builtin.NativeObject               // id: %3
} // end sil function 'Lazy.Lazy.deinit'

// Lazy.__deallocating_deinit
sil @Lazy.Lazy.__deallocating_deinit : $@convention(method) (@owned Lazy) -> () {
// %0 "self"                                      // users: %3, %1
bb0(%0 : $Lazy):
  debug_value %0 : $Lazy, let, name "self", argno 1 // id: %1
  // function_ref Lazy.deinit
  %2 = function_ref @Lazy.Lazy.deinit : $@convention(method) (@guaranteed Lazy) -> @owned Builtin.NativeObject // user: %3
  %3 = apply %2(%0) : $@convention(method) (@guaranteed Lazy) -> @owned Builtin.NativeObject // user: %4
  %4 = unchecked_ref_cast %3 : $Builtin.NativeObject to $Lazy // user: %5
  dealloc_ref %4 : $Lazy                          // id: %5
  %6 = tuple ()                                   // user: %7
  return %6 : $()                                 // id: %7
} // end sil function 'Lazy.Lazy.__deallocating_deinit'

// Lazy.__allocating_init()
sil hidden [exact_self_class] @Lazy.Lazy.__allocating_init() -> Lazy.Lazy : $@convention(method) (@thick Lazy.Type) -> @owned Lazy {
// %0 "$metatype"
bb0(%0 : $@thick Lazy.Type):
  %1 = alloc_ref $Lazy                            // user: %3
  // function_ref Lazy.init()
  %2 = function_ref @Lazy.Lazy.init() -> Lazy.Lazy : $@convention(method) (@owned Lazy) -> @owned Lazy // user: %3
  %3 = apply %2(%1) : $@convention(method) (@owned Lazy) -> @owned Lazy // user: %4
  return %3 : $Lazy                               // id: %4
} // end sil function 'Lazy.Lazy.__allocating_init() -> Lazy.Lazy'

// Lazy.init()
sil hidden @Lazy.Lazy.init() -> Lazy.Lazy : $@convention(method) (@owned Lazy) -> @owned Lazy {
// %0 "self"                                      // users: %2, %5, %1
bb0(%0 : $Lazy):
  debug_value %0 : $Lazy, let, name "self", argno 1 // id: %1
  %2 = ref_element_addr %0 : $Lazy, #Lazy.$__lazy_storage_$_age // user: %4
  %3 = enum $Optional<Int>, #Optional.none!enumelt // user: %4
  store %3 to %2 : $*Optional<Int>                // id: %4
  return %0 : $Lazy                               // id: %5
} // end sil function 'Lazy.Lazy.init() -> Lazy.Lazy'

sil_vtable Lazy {
  #Lazy.age!getter: (Lazy) -> () -> Int : @Lazy.Lazy.age.getter : Swift.Int	// Lazy.age.getter
  #Lazy.age!setter: (Lazy) -> (Int) -> () : @Lazy.Lazy.age.setter : Swift.Int	// Lazy.age.setter
  #Lazy.age!modify: (Lazy) -> () -> () : @Lazy.Lazy.age.modify : Swift.Int	// Lazy.age.modify
  #Lazy.init!allocator: (Lazy.Type) -> () -> Lazy : @Lazy.Lazy.__allocating_init() -> Lazy.Lazy	// Lazy.__allocating_init()
  #Lazy.deinit!deallocator: @Lazy.Lazy.__deallocating_deinit	// Lazy.__deallocating_deinit
}



// Mappings from '#fileID' to '#filePath':
//   'Lazy/Lazy.swift' => 'Lazy.swift'



对比结果

  • 通过 SIL 对比get/set方法发现 lazy修饰后,属性被包装成了 Optional 类型

内存分析

实例化一个实例对象,然后看其内存分布

  • 普通的属性在实例化结果后,值已经确定,内存大小也已确定

    (lldb) x/4xg n
    0x600000a6e700: 0x0000000109bb1f58 0x0000000200000003
    0x600000a6e710: 0x0000000000000014 0x0000000000000000
    

    0x0000000000000014就是默认的age = 20

    po print(class_getInstanceSize(Normal.self))
    

    此时得到的结果是: 24

    16 (Swift默认的 8 MetaData + 8 RefCount) + 8 age

  • 延迟属性

    0x600000a799e0: 0x0000000109bb1dc8 0x0000000200000003
    0x600000a799f0: 0x0000000000000000 0x0000000000000001
    

    0x0000000000000000lazy var age, 实例化后默认的值为 0

    在使用了 age 的时候打一个断点可以发现,其值立马变为初始值 20

    使用了延迟属性后的实例对象 Size 为: 32

    32 的原因 => 16 + 9 ==经过内存对齐 =》 32

补充

  • 打印一个实例对象的 Size func class_getInstanceSize(_ cls: AnyClass?) -> Int
  • MemoryLayout<Optional<Int>>.size 得到Int 被包装成可选值后实际大小为9字节
  • MemoryLayout<Optional<Int>>.stride是对齐后 16字节

总结

结合 Swift 语法

  • lazy只能修饰存储属性
  • lazy必须有一个默认的初始值
  • lazy第一次访问的时候才被赋值
  • lazy不能保证线程安全
  • lazy对实例对象大小产生影响
⚠️ **GitHub.com Fallback** ⚠️