Swift內(nèi)存分配過程
對(duì)象的內(nèi)存分配過程,可以使用符號(hào)斷點(diǎn)進(jìn)行驗(yàn)證,下面演示如何為
__allocating_init添加斷點(diǎn):選擇符號(hào)斷點(diǎn)添加__allocating_init選擇匯編模式
運(yùn)行代碼,來(lái)到
__allocating_init函數(shù)執(zhí)行內(nèi)部,發(fā)現(xiàn)它做了兩件事:
- 調(diào)用
swift_allocObject函數(shù)- 執(zhí)行
demo.LGTeacher.init方法,進(jìn)行初始化變量
demo`LGTeacher.__allocating_init():
0x1000029c0 <+0>: pushq %rbp
0x1000029c1 <+1>: movq %rsp, %rbp
0x1000029c4 <+4>: pushq %r13
0x1000029c6 <+6>: pushq %rax
0x1000029c7 <+7>: movl $0x28, %esi
0x1000029cc <+12>: movl $0x7, %edx
-> 0x1000029d1 <+17>: movq %r13, %rdi
//1、調(diào)用swift_allocObject函數(shù)
0x1000029d4 <+20>: callq 0x100003be2 ; symbol stub for: swift_allocObject
0x1000029d9 <+25>: movq %rax, %r13
//2、執(zhí)行demo.LGTeacher.init方法,進(jìn)行初始化變量
0x1000029dc <+28>: callq 0x100002a20 ; demo.LGTeacher.init() -> demo.LGTeacher at main.swift:10
0x1000029e1 <+33>: addq $0x8, %rsp
0x1000029e5 <+37>: popq %r13
0x1000029e7 <+39>: popq %rbp
0x1000029e8 <+40>: retq
添加
swift_allocObject斷點(diǎn),發(fā)現(xiàn)其內(nèi)部依次調(diào)用_swift_allocObject_和swift_slowAlloc兩個(gè)函數(shù)
libswiftCore.dylib`swift_allocObject:
-> 0x7fff6cd73d00 <+0>: pushq %rbp
0x7fff6cd73d01 <+1>: movq %rsp, %rbp
0x7fff6cd73d04 <+4>: pushq %rbx
0x7fff6cd73d05 <+5>: pushq %rax
0x7fff6cd73d06 <+6>: movq %rdi, %rbx
0x7fff6cd73d09 <+9>: movq 0x26d55b98(%rip), %rax ; _swift_allocObject
//1、調(diào)用_swift_allocObject_t函數(shù)
0x7fff6cd73d10 <+16>: leaq 0x1d69(%rip), %rcx ; _swift_allocObject_
0x7fff6cd73d17 <+23>: cmpq %rcx, %rax
0x7fff6cd73d1a <+26>: jne 0x7fff6cd73d39 ; <+57>
0x7fff6cd73d1c <+28>: movq %rsi, %rdi
0x7fff6cd73d1f <+31>: movq %rdx, %rsi
//2、調(diào)用swift_slowAlloc函數(shù)
0x7fff6cd73d22 <+34>: callq 0x7fff6cd73c90 ; swift_slowAlloc
0x7fff6cd73d27 <+39>: movq %rbx, (%rax)
0x7fff6cd73d2a <+42>: movq $0x2, 0x8(%rax)
0x7fff6cd73d32 <+50>: addq $0x8, %rsp
0x7fff6cd73d36 <+54>: popq %rbx
0x7fff6cd73d37 <+55>: popq %rbp
0x7fff6cd73d38 <+56>: retq
0x7fff6cd73d39 <+57>: movq %rbx, %rdi
0x7fff6cd73d3c <+60>: addq $0x8, %rsp
0x7fff6cd73d40 <+64>: popq %rbx
0x7fff6cd73d41 <+65>: popq %rbp
0x7fff6cd73d42 <+66>: jmpq *%rax
0x7fff6cd73d44 <+68>: nopw %cs:(%rax,%rax)
0x7fff6cd73d4e <+78>: nop
添加
swift_slowAlloc斷點(diǎn),其內(nèi)部最終調(diào)用了malloc函數(shù)
libswiftCore.dylib`swift_slowAlloc:
-> 0x7fff6cd73c90 <+0>: pushq %rbp
0x7fff6cd73c91 <+1>: movq %rsp, %rbp
0x7fff6cd73c94 <+4>: subq $0x10, %rsp
0x7fff6cd73c98 <+8>: movq %rdi, %rdx
0x7fff6cd73c9b <+11>: cmpq $0xf, %rsi
0x7fff6cd73c9f <+15>: ja 0x7fff6cd73cb0 ; <+32>
0x7fff6cd73ca1 <+17>: movq %rdx, %rdi
//調(diào)用了malloc函數(shù)
0x7fff6cd73ca4 <+20>: callq 0x7fff6cdf028c ; symbol stub for: malloc
0x7fff6cd73ca9 <+25>: testq %rax, %rax
0x7fff6cd73cac <+28>: jne 0x7fff6cd73cdb ; <+75>
0x7fff6cd73cae <+30>: jmp 0x7fff6cd73ce1 ; <+81>
0x7fff6cd73cb0 <+32>: incq %rsi
0x7fff6cd73cb3 <+35>: movl $0x10, %eax
0x7fff6cd73cb8 <+40>: cmovneq %rsi, %rax
0x7fff6cd73cbc <+44>: cmpq $0x8, %rax
0x7fff6cd73cc0 <+48>: movl $0x8, %esi
0x7fff6cd73cc5 <+53>: cmovaq %rax, %rsi
0x7fff6cd73cc9 <+57>: leaq -0x8(%rbp), %rdi
0x7fff6cd73ccd <+61>: callq 0x7fff6cdf038e ; symbol stub for: posix_memalign
0x7fff6cd73cd2 <+66>: movq -0x8(%rbp), %rax
0x7fff6cd73cd6 <+70>: testq %rax, %rax
0x7fff6cd73cd9 <+73>: je 0x7fff6cd73ce1 ; <+81>
0x7fff6cd73cdb <+75>: addq $0x10, %rsp
0x7fff6cd73cdf <+79>: popq %rbp
0x7fff6cd73ce0 <+80>: retq
0x7fff6cd73ce1 <+81>: callq 0x7fff6cdefb00 ; swift_slowAlloc.cold.1
0x7fff6cd73ce6 <+86>: nopw %cs:(%rax,%rax)
以上可以得出?個(gè)簡(jiǎn)單的結(jié)論:
swift內(nèi)存分配過程:__allocating_init->swift_allocObject->_swift_allocObject_->swift_slowAlloc->malloc
實(shí)例對(duì)象的本質(zhì)
使用VSCode打開Swift源碼,找到
_swift_allocObject_函數(shù),添加斷點(diǎn):_swift_allocObject__swift_allocObject_函數(shù),負(fù)責(zé)創(chuàng)建當(dāng)前實(shí)例對(duì)象,有下列3個(gè)參數(shù)。在其內(nèi)部先后調(diào)用swift_slowAlloc、new HeapObject兩個(gè)函數(shù)
metadata:元數(shù)據(jù)requiredSize:創(chuàng)建實(shí)例對(duì)象分配的實(shí)際內(nèi)存大小,這里看到占用40字節(jié)requiredAlignmentMask:字節(jié)對(duì)齊方式,必須為8的倍速。不足會(huì)自動(dòng)補(bǔ)齊,以空間換取時(shí)間,提高訪問效率??梢钥吹秸加?字節(jié)
找到
swift_slowAlloc函數(shù):swift_slowAllocswift_slowAlloc函數(shù)內(nèi)部,又調(diào)用malloc,負(fù)責(zé)在堆中創(chuàng)建size大小的內(nèi)存空間,并進(jìn)行內(nèi)存的字節(jié)對(duì)齊
回到
_swift_allocObject_函數(shù),當(dāng)swift_slowAlloc完成創(chuàng)建內(nèi)存的工作后,繼續(xù)執(zhí)行new HeapObject來(lái)進(jìn)行初始化對(duì)象的工作,最終返回HeapObject結(jié)構(gòu)體
了解一下
HeapObject結(jié)構(gòu)體:實(shí)例對(duì)象初始化需要HeapObjectmetadata和refCounts兩個(gè)參數(shù):
metadata:元數(shù)據(jù),類型為HeapMetadata,是指針類型,占8字節(jié)refCounts:引用計(jì)數(shù),因?yàn)?code>swift也是采用ARC進(jìn)行內(nèi)存管理。類型為InlineRefCounts,而InlineRefCounts是Class類型,占8字節(jié)
實(shí)例對(duì)象的本質(zhì):
oc:objc_object結(jié)構(gòu)體,默認(rèn)有class類型的isa指針,占8字節(jié)swift:HeapObject結(jié)構(gòu)體,默認(rèn)有元數(shù)據(jù)metadata、引用計(jì)數(shù)refCounts,占16字節(jié)
類結(jié)構(gòu)的探索
找到
HeapMetadata定義:HeapMetadataHeapMetadata針對(duì)TargetHeapMetadata類型取別名,而TargetHeapMetadata是模板類型,接收一個(gè)Inprocess參數(shù),也是下文中的kind屬性
找到
TargetHeapMetaData定義:TargetHeapMetaDataTargetHeapMetadata繼承自TargetMetaData,TargetMetaData內(nèi)部有kind屬性,對(duì)于kind屬性其實(shí)就是unsigned long類型,主要用于區(qū)分是哪種類型的元數(shù)據(jù)。
找到
MetadataKind.def文件,里面記錄了所有元數(shù)據(jù)類型,包含Class、Struct、Enum等
| Name | Value |
|---|---|
| Class | 0x0 |
| Struct | 0x200 |
| Enum | 0x201 |
| Optional | 0x2027 |
| ForeignClass | 0x203 |
| Opaque | 0x300 |
| Tuple | 0x301 |
| Function | 0x302 |
| Existential | 0x303 |
| Metatype | 0x304 |
| ObjCClassWrapper | 0x305 |
| ExistentialMetatype | 0x306 |
| HeapLocalVariable | 0x400 |
| HeapGenericLocalVariable | 0x500 |
| ErrorObject | 0x501 |
| LastEnumerated | 0x7FF |
找到
TargetMetaData定義:除了包含TargetMetaData -getClassObjectkind屬性外,還有一個(gè)getClassObject方法。該方法有個(gè)返回類型TargetClassMetadata,其內(nèi)部通過kind去匹配上述枚舉值。一旦匹配成功,將this,也就是當(dāng)前Metadata的指針,強(qiáng)轉(zhuǎn)相應(yīng)類型。當(dāng)前枚舉為Class,所以this會(huì)被強(qiáng)轉(zhuǎn)為ClassMetadata并返回
下面我們通過
lldb進(jìn)行驗(yàn)證
通過lldb驗(yàn)證
po metadata->getKind(),打印kind類型為Classpo metadata->getClassObject(),打印出元數(shù)據(jù)內(nèi)存地址為0x000000010f4dfc88x/8g 0x000000010f4dfc88,可以看到元數(shù)據(jù)里記錄的信息
由此得出結(jié)論,其實(shí)當(dāng)前的
TargetMetadata就是TargetClassMetadata,因?yàn)樵趦?nèi)存結(jié)構(gòu)中,它們可以直接進(jìn)行指針轉(zhuǎn)換
找到
TargetClassMetadata定義:TargetClassMetadataTargetClassMetadata繼承自TargetAnyClassMetadata,所以擁有了父類的kind、superclass、cacheData等屬性
找到
TargetAnyClassMetadata定義:TargetAnyClassMetadata
TargetAnyClassMetadata繼承自TargetHeapMetadata,而TargetHeapMetadata又繼承自TargetMetadata,所以擁有了父類的kind屬性。
經(jīng)過源碼閱讀,我們得出當(dāng)前
metadata的數(shù)據(jù)結(jié)構(gòu)體如下:
struct swift_class_t : NSObject {
void *kind; //8字節(jié)
void *superClass;
void *cacheData
void *data
uint32_t flags; //4字節(jié)
uint32_t instanceAddressOffset; //4字節(jié)
uint32_t instanceSize;//4字節(jié)
uint16_t instanceAlignMask; //2字節(jié)
uint16_t reserved; //2字節(jié)
uint32_t classSize; //4字節(jié)
uint32_t classAddressOffset; //4字節(jié)
void *description;
// ...
};
類結(jié)構(gòu)的探索:
- 當(dāng)
metadata的kind類型為Class,類結(jié)構(gòu)繼承關(guān)系如下:
TargetClassMetadata->TargetAnyClassMetadata->TargetHeapMetadata->TargetMetaDataTargetMetaData類似oc中的objc_object,內(nèi)含kind屬性HeapMetadata針對(duì)TargetHeapMetadata取別名,類似oc中的objc_classTargetHeapMetadata為模板類型,接收一個(gè)Inprocess參數(shù),也就是kind屬性kind屬性為unsigned long類型,類似oc中的isa
Swift屬性
存儲(chǔ)屬性:
- 占用存儲(chǔ)空間
- 要么是
let修飾的常量- 要么是
var修飾的變量
class LGTeacher{
let age: Int = 18
var name: String = "Zang"
}
let t = LGTeacher()
上述代碼中的
age和name,都是變量存儲(chǔ)屬性
通過SIL進(jìn)行驗(yàn)證:
class LGTeacher {
//_hasStorage 表示是存儲(chǔ)屬性
@_hasStorage @_hasInitialValue final let age: Int { get }
@_hasStorage @_hasInitialValue var name: String { get set }
@objc deinit
init()
}
為什么說存儲(chǔ)屬性占用存儲(chǔ)空間?
class LGTeacher{
let age: Int = 18
var name: String = "Zang"
}
let t = LGTeacher()
print("size of t.age: \(MemoryLayout.size(ofValue: t.age))")
print("size of t.name: \(MemoryLayout.size(ofValue: t.name))")
print("size of LGTeacher Class: \(class_getInstanceSize(LGTeacher.self))")
//輸出以下內(nèi)容:
//size of t.age: 8
//size of t.name: 16
//size of LGTeacher Class: 40
通過上面代碼可以看出
LGTeacher Class共占40字節(jié)
metadata:元數(shù)據(jù),占8字節(jié)refCounts:引用計(jì)數(shù),占8字節(jié)age:Int類型存儲(chǔ)屬性,占8字節(jié)name:String類型存儲(chǔ)屬性,占16字節(jié)
再來(lái)使用
po、x/8g,查看HeapObject存儲(chǔ)地址:可以看出HeapObject存儲(chǔ)地址HeapObject存放了該類的元數(shù)據(jù)和引用計(jì)數(shù),還存放了該類下的存儲(chǔ)屬性
計(jì)算屬性
- 不占?存儲(chǔ)空間
- 本質(zhì)是
get/set?法
class Square{
var width: Double = 8.0
var area: Double{
get{
return width * width
}
set{
width = sqrt(newValue)
}
}
}
print("size of Square Class: \(class_getInstanceSize(Square.self))")
//輸出以下內(nèi)容:
//size of Square Class: 24
通過上面代碼可以看出
Square Class共占24字節(jié)
metadata:元數(shù)據(jù),占8字節(jié)refCounts:引用計(jì)數(shù),占8字節(jié)width:Double類型存儲(chǔ)屬性,占8字節(jié)area:計(jì)算屬性,本質(zhì)是get/set?法,不占?存儲(chǔ)空間
通過SIL進(jìn)行驗(yàn)證:
class Square {
@_hasStorage @_hasInitialValue var width: Double { get set }
var area: Double { get set }
@objc deinit
init()
}
上述代碼中,只有
width屬性被標(biāo)記了_hasStorage,所以只有它是存儲(chǔ)屬性。而area屬性有的只是get/set?法,所以它是計(jì)算屬性
SIL中的
getter?法
// Square.width.getter
sil hidden [transparent] @$s4main6SquareC5widthSdvg : $@convention(method) (@guaranteed Square) -> Double {
// %0 "self" // users: %2, %1
bb0(%0 : $Square):
debug_value %0 : $Square, let, name "self", argno 1 // id: %1
%2 = ref_element_addr %0 : $Square, #Square.width // user: %3
%3 = begin_access [read] [dynamic] %2 : $*Double // users: %4, %5
%4 = load %3 : $*Double // user: %6
end_access %3 : $*Double // id: %5
return %4 : $Double // id: %6
} // end sil function '$s4main6SquareC5widthSdvg'
SIL中的
setter?法
// Square.width.setter
sil hidden [transparent] @$s4main6SquareC5widthSdvs : $@convention(method) (Double, @guaranteed Square) -> () {
// %0 "value" // users: %6, %2
// %1 "self" // users: %4, %3
bb0(%0 : $Double, %1 : $Square):
debug_value %0 : $Double, let, name "value", argno 1 // id: %2
debug_value %1 : $Square, let, name "self", argno 2 // id: %3
%4 = ref_element_addr %1 : $Square, #Square.width // user: %5
%5 = begin_access [modify] [dynamic] %4 : $*Double // users: %6, %7
store %0 to %5 : $*Double // id: %6
end_access %5 : $*Double // id: %7
%8 = tuple () // user: %9
return %8 : $() // id: %9
} // end sil function '$s4main6SquareC5widthSdvs'
屬性觀察者
willSet:新值存儲(chǔ)前調(diào)用,可獲取即將被更新的新值newValuedidSet:新值存儲(chǔ)后調(diào)用,可獲取被更新前的原始值oldValue
class LGTeacher{
var name: String = "無(wú)"{
willSet{
print("willSet-新值存儲(chǔ)前調(diào)用,當(dāng)前值:\(name),即將被更新為:\(newValue)")
}
didSet{
print("didSet-新值存儲(chǔ)后調(diào)用,當(dāng)前值:\(name),被更新前的原始值:\(oldValue)")
}
}
}
var t = LGTeacher()
t.name = "Zang"
print("size of LGTeacher Class: \(class_getInstanceSize(LGTeacher.self))")
//輸出以下內(nèi)容:
//willSet-新值存儲(chǔ)前調(diào)用,當(dāng)前值:無(wú),即將被更新為:Zang
//didSet-新值存儲(chǔ)后調(diào)用,當(dāng)前值:Zang,被更新前的原始值:無(wú)
//size of LGTeacher Class: 32
init方法中修改屬性,能否觸發(fā)屬性觀察者的
willSet、didSet?
//父類LGTeacher
class LGTeacher{
var name: String = "無(wú)" {
willSet{
print("willSet-新值存儲(chǔ)前調(diào)用,當(dāng)前值:\(name),即將被更新為:\(newValue)")
}
didSet{
print("didSet-新值存儲(chǔ)后調(diào)用,當(dāng)前值:\(name),被更新前的原始值:\(oldValue)")
}
}
init() {
self.name = "Teacher"
}
}
//子類LGChild
class LGChild : LGTeacher{
override init() {
super.init()
self.name = "Child"
}
}
var t = LGChild()
//輸出以下內(nèi)容:
//willSet-新值存儲(chǔ)前調(diào)用,當(dāng)前值:Teacher,即將被更新為:Child
//didSet-新值存儲(chǔ)后調(diào)用,當(dāng)前值:Child,被更新前的原始值:Teacher
對(duì)上述結(jié)果的打印,有些神奇的地方:
- 父類
init方法將name賦值為Teacher,但并沒有觸發(fā)willSet、didSet
因?yàn)榇藭r(shí)父類的初始化還未完成- 子類
init方法將name賦值為Child,此時(shí)卻觸發(fā)了willSet、didSet
因?yàn)榇藭r(shí)super.init,也就是父類的初始化已完成init方法會(huì)調(diào)用memset清理其他屬性的內(nèi)存空間(不包括metadata、refCounts),因?yàn)橛锌赡苁桥K數(shù)據(jù),被別人使用過,之后才會(huì)賦值。
能否在當(dāng)前類的計(jì)算屬性上,再添加屬性觀察者?
計(jì)算屬性,能否添加屬性觀察者?
很明顯,在當(dāng)前類的計(jì)算屬性上,無(wú)法再添加屬性觀察者。編譯報(bào)錯(cuò):“willSet cannot be provided together with a getter”
但我們可以通過類的繼承,對(duì)父類的計(jì)算屬性,通過子類添加屬性觀察者
class Square{
var width: Double = 8.0
var area: Double{
get{
return width * width
}
set{
width = sqrt(newValue)
}
}
}
class LGChild : Square{
override var area: Double{
willSet{
print("willSet-新值存儲(chǔ)前調(diào)用,當(dāng)前值:\(area),即將被更新為:\(newValue)")
}
didSet{
print("didSet-新值存儲(chǔ)后調(diào)用,當(dāng)前值:\(area),被更新前的原始值:\(oldValue)")
}
}
}
var t = LGChild()
t.area=16;
//輸出以下內(nèi)容:
//willSet-新值存儲(chǔ)前調(diào)用,當(dāng)前值:64.0,即將被更新為:16.0
//didSet-新值存儲(chǔ)后調(diào)用,當(dāng)前值:16.0,被更新前的原始值:64.0
子類和父類能否同時(shí)存在
willSet、didSet?
class LGTeacher{
var name: String = "無(wú)" {
willSet{
print("LGTeacher-willSet-新值存儲(chǔ)前調(diào)用,當(dāng)前值:\(name),即將被更新為:\(newValue)")
}
didSet{
print("LGTeacher-didSet-新值存儲(chǔ)后調(diào)用,當(dāng)前值:\(name),被更新前的原始值:\(oldValue)")
}
}
}
class LGChild : LGTeacher{
override var name: String {
willSet{
print("LGChild-willSet-新值存儲(chǔ)前調(diào)用,當(dāng)前值:\(name),即將被更新為:\(newValue)")
}
didSet{
print("LGChild-didSet-新值存儲(chǔ)后調(diào)用,當(dāng)前值:\(name),被更新前的原始值:\(oldValue)")
}
}
}
var t = LGChild()
t.name="Zang";
//輸出以下內(nèi)容:
//LGChild-willSet-新值存儲(chǔ)前調(diào)用,當(dāng)前值:無(wú),即將被更新為:Zang
//LGTeacher-willSet-新值存儲(chǔ)前調(diào)用,當(dāng)前值:無(wú),即將被更新為:Zang
//LGTeacher-didSet-新值存儲(chǔ)后調(diào)用,當(dāng)前值:Zang,被更新前的原始值:無(wú)
//LGChild-didSet-新值存儲(chǔ)后調(diào)用,當(dāng)前值:Zang,被更新前的原始值:無(wú)
上述代碼證明子類和父類,可以同時(shí)存在
willSet和didSet。
調(diào)用順序:子類willSet->父類willSet->父類didSet->子類didSet
t是子類的實(shí)例對(duì)象,當(dāng)name屬性被修改,首先觸發(fā)子類的willSet方法- 之后會(huì)調(diào)用父類的
setter方法- 然后觸發(fā)父類的
willSet和didSet方法- 最后觸發(fā)子類的
didSet方法
延遲存儲(chǔ)屬性
- 使?
lazy修飾的存儲(chǔ)屬性- 延遲存儲(chǔ)屬性必須有?個(gè)默認(rèn)初始值
- 延遲存儲(chǔ)屬性在第?次訪問的時(shí)候才會(huì)被賦值
- 延遲存儲(chǔ)屬性并不能保證線程安全
- 延遲存儲(chǔ)屬性會(huì)影響實(shí)例對(duì)象的??
class LGTeacher{
lazy var age: Int = 18
}
var t = LGTeacher()
print("age: \(t.age)")
print("size of LGTeacher Class: \(class_getInstanceSize(LGTeacher.self))")
//輸出以下內(nèi)容:
//age: 18
//size of LGTeacher Class: 32
對(duì)上述結(jié)果的打印,有些神奇的地方:
正常來(lái)說LGTeacher應(yīng)該由metadata、refCounts、Int屬性age組成,共占24字節(jié)。但打印結(jié)果中的LGTeacher,為什么輸出32字節(jié)?
繼續(xù)通過SIL查看代碼:
class LGTeacher {
lazy var age: Int { get set }
@_hasStorage @_hasInitialValue final var $__lazy_storage_$_age: Int? { get set }
@objc deinit
init()
}
通過SIL可以看到
age屬性,由于設(shè)置lazy關(guān)鍵字的原因,被加上了final修飾符,類型變?yōu)?code>Optional<Int>可選類型
Optional<Int>:Optional本質(zhì)是enum占1字節(jié),Int占8字節(jié),故此Optional<Int>占9字節(jié)。加上metadata、refCounts共計(jì)25字節(jié),再經(jīng)過內(nèi)存對(duì)齊,需要8的倍數(shù),最終LGTeacher輸出32字節(jié)。
再來(lái)使用
po、x/8g,查看lazy屬性首次訪問的情況:很明顯首次訪問之前,內(nèi)存地址是lazy屬性第一次訪問0x0,沒有值。當(dāng)使用t.age觸發(fā)getter方法后,地址變成0x12,也就是18。
通過SIL的
getter方法驗(yàn)證:
// 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
//這里在驗(yàn)證age是否有值,有值進(jìn)入bb1流程,沒值進(jìn)入bb2流程
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
//將初始化的默認(rèn)值18賦值給age屬性
%10 = integer_literal $Builtin.Int64, 18 // 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) // id: %18
// %19 // user: %20
bb3(%19 : $Int): // Preds: bb2 bb1
return %19 : $Int // id: %20
} // end sil function 'main.LGTeacher.age.getter : Swift.Int'
- 在
getter方法中,讀取age屬性的值。再通過case #Optional.some!enumelt判斷age屬性是否有值,有值進(jìn)入bb1流程,沒值進(jìn)入bb2流程。首次使用age屬性沒有值,進(jìn)入bb2將初始化的默認(rèn)值18賦值給age屬性。- 這段代碼也可以看出
lazy并不能保證線程安全。當(dāng)多線程同時(shí)對(duì)age屬性賦值時(shí),getter方法中case #Optional.some!enumelt判斷,很可能多次進(jìn)入bb2流程,造成多次初始化的情況。
類型屬性
- 使?
static來(lái)聲明?個(gè)類型屬性- 類型屬性屬于這個(gè)類的本身,不管有多少個(gè)實(shí)例,類型屬性只有?份
- 類型屬性必須有?個(gè)默認(rèn)初始值
- 類型屬性只會(huì)被初始化一次
- 類型屬性是線程安全的
class LGTeacher{
static var age: Int = 18
}
var t = LGTeacher()
//print("age: \(t.age)")
print("age: \(LGTeacher.age)")
print("size of LGTeacher Class: \(class_getInstanceSize(LGTeacher.self))")
//輸出以下內(nèi)容:
//age: 18
//size of LGTeacher Class: 16
- 類型屬性必須有初始值,否則編譯報(bào)錯(cuò):“static var declaration requires an initializer expression or getter/setter specifier”
- 類型屬性必須通過
LGTeacher.age訪問,不能通過t.age訪問,后者編譯報(bào)錯(cuò):“Static member age cannot be used on instance of type LGTeacher”LGTeacher輸出16字節(jié),說明LGTeacher里不包含類型屬性的存儲(chǔ)空間
通過SIL進(jìn)行驗(yàn)證:
class LGTeacher {
@_hasStorage @_hasInitialValue static var age: Int { get set }
@objc deinit
init()
}
// globalinit_029_12232F587A4C5CD8B1EEDF696793B2FC_token0
sil_global private @globalinit_029_12232F587A4C5CD8B1EEDF696793B2FC_token0 : $Builtin.Word
// static LGTeacher.age
sil_global hidden @static main.LGTeacher.age : Swift.Int : $Int
通過最后一行代碼就能看出,
age屬性變成全局變量,所以說類型屬性其實(shí)就是一個(gè)全局變量
添加符號(hào)斷點(diǎn)查看匯編代碼,可以看到調(diào)用了一個(gè)
swift_once方法,它就是GCD的單例方法dispatch_once_f。所以說類型屬性只會(huì)初始化一次,并且它是線程安全的swift_once
正確聲明?個(gè)單利:
class LGTeacher{
static let shareInstance = LGTeacher.init()
private init(){ }
}
var t=LGTeacher.shareInstance
- 使用
static+let初始化實(shí)例對(duì)象- 設(shè)置
init方法為private私有訪問權(quán)限















