lazy - ShenYj/ShenYj.github.io GitHub Wiki
延迟属性
在探索延迟属性前,准备一个足够简单的 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
-
普通存储型属性的类文件
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
+ 8RefCount
) + 8age
-
延迟属性
0x600000a799e0: 0x0000000109bb1dc8 0x0000000200000003 0x600000a799f0: 0x0000000000000000 0x0000000000000001
0x0000000000000000
是lazy 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
对实例对象大小产生影响