Swift底層進(jìn)階--002:類、對(duì)象、屬性

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_slowAlloc
swift_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)體:

HeapObject
實(shí)例對(duì)象初始化需要metadatarefCounts兩個(gè)參數(shù):

  • metadata:元數(shù)據(jù),類型為HeapMetadata,是指針類型,占8字節(jié)
  • refCounts:引用計(jì)數(shù),因?yàn)?code>swift也是采用ARC進(jìn)行內(nèi)存管理。類型為InlineRefCounts,而InlineRefCountsClass類型,占8字節(jié)

實(shí)例對(duì)象的本質(zhì):

  • ocobjc_object結(jié)構(gòu)體,默認(rèn)有class類型的isa指針,占8字節(jié)
  • swiftHeapObject結(jié)構(gòu)體,默認(rèn)有元數(shù)據(jù)metadata、引用計(jì)數(shù)refCounts,占16字節(jié)
類結(jié)構(gòu)的探索

找到HeapMetadata定義:

HeapMetadata
HeapMetadata針對(duì)TargetHeapMetadata類型取別名,而TargetHeapMetadata是模板類型,接收一個(gè)Inprocess參數(shù),也是下文中的kind屬性

找到TargetHeapMetaData定義:

TargetHeapMetaData
TargetHeapMetadata繼承自TargetMetaData,TargetMetaData內(nèi)部有kind屬性,對(duì)于kind屬性其實(shí)就是unsigned long類型,主要用于區(qū)分是哪種類型的元數(shù)據(jù)。

找到MetadataKind.def文件,里面記錄了所有元數(shù)據(jù)類型,包含ClassStruct、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 -getClassObject
除了包含kind屬性外,還有一個(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類型為Class
  • po metadata->getClassObject(),打印出元數(shù)據(jù)內(nèi)存地址為0x000000010f4dfc88
  • x/8g 0x000000010f4dfc88,可以看到元數(shù)據(jù)里記錄的信息

由此得出結(jié)論,其實(shí)當(dāng)前的TargetMetadata就是TargetClassMetadata,因?yàn)樵趦?nèi)存結(jié)構(gòu)中,它們可以直接進(jìn)行指針轉(zhuǎn)換

找到TargetClassMetadata定義:

TargetClassMetadata
TargetClassMetadata繼承自TargetAnyClassMetadata,所以擁有了父類的kind、superclasscacheData等屬性

找到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)metadatakind類型為Class,類結(jié)構(gòu)繼承關(guān)系如下:
    TargetClassMetadata->TargetAnyClassMetadata->TargetHeapMetadata->TargetMetaData
  • TargetMetaData類似oc中的objc_object,內(nèi)含kind屬性
  • HeapMetadata針對(duì)TargetHeapMetadata取別名,類似oc中的objc_class
  • TargetHeapMetadata為模板類型,接收一個(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()

上述代碼中的agename,都是變量存儲(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é)
  • ageInt類型存儲(chǔ)屬性,占8字節(jié)
  • nameString類型存儲(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é)
  • widthDouble類型存儲(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)用,可獲取即將被更新的新值newValue
  • didSet:新值存儲(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)存空間(不包括metadatarefCounts),因?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í)存在willSetdidSet
調(diào)用順序:子類willSet->父類willSet->父類didSet->子類didSet

  • t是子類的實(shí)例對(duì)象,當(dāng)name屬性被修改,首先觸發(fā)子類的willSet方法
  • 之后會(huì)調(diào)用父類的setter方法
  • 然后觸發(fā)父類的willSetdidSet方法
  • 最后觸發(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)該由metadatarefCounts、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屬性首次訪問的情況:

lazy屬性第一次訪問
很明顯首次訪問之前,內(nèi)存地址是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)限
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容