Swift -- 1.類與結(jié)構(gòu)體(上)

1.類與結(jié)構(gòu)體的異同

主要的相同點(diǎn):

  • 定義存儲(chǔ)值的屬性
  • 定義方法
  • 定義下標(biāo)以使用下標(biāo)語(yǔ)法提供對(duì)其值的訪問(wèn)(點(diǎn)語(yǔ)法訪問(wèn)值)
  • 定義初始化器
  • 使用extension來(lái)拓展功能
  • 遵循協(xié)議來(lái)提供某種功能

主要的不同點(diǎn):

  • 類有繼承特性,而結(jié)構(gòu)體沒(méi)有
  • 類型轉(zhuǎn)換使你能夠在運(yùn)行時(shí)檢查和解釋類實(shí)例的類型(Mirro)
  • 類有析構(gòu)函數(shù)來(lái)釋放其分配的資源(deinit)
  • 引用計(jì)數(shù)允許對(duì)一個(gè)類實(shí)例有多個(gè)引用

對(duì)于類與結(jié)構(gòu)體我們區(qū)分的第一件事就是:

類是引用類型。也就意味著一個(gè)類類型的變量并不直接存儲(chǔ)具體的實(shí)例對(duì)象,是對(duì)當(dāng)前存儲(chǔ)具體實(shí)例內(nèi)存地址的引用。

這里借助2個(gè)指令來(lái)查看當(dāng)前變量的內(nèi)存結(jié)構(gòu)
po : p和po的區(qū)別在于使用po只會(huì)輸出對(duì)應(yīng)的值,而p則會(huì)輸出返回值的類型以及命令結(jié)果的引用名。
x/8g : 讀取內(nèi)存中的值(8g:8字節(jié)格式輸出)
class LGTeacher{
    
    var age: Int
    var name: String
    
    init(age: Int, name: String) {
        self.age = age
        self.name = name
    }
}

var t = LGTeacher(age: 18, name: "Kody")

var t1 = t

print("end")
(lldb) po t
<LGTeacher: 0x10070e630>

(lldb) po t1
<LGTeacher: 0x10070e630>

(lldb) x/8g 0x10070e630
0x10070e630: 0x0000000100008180 0x0000000600000003
0x10070e640: 0x0000000000000012 0x0000000079646f4b
0x10070e650: 0xe400000000000000 0x000000000000005f
0x10070e660: 0x0000000000000000 0x0000000000000000
(lldb) po withUnsafePointer(to: &t, {print($0)})
0x0000000100008218
0 elements

(lldb) po withUnsafePointer(to: &t1, {print($0)})
0x0000000100008220
0 elements

(lldb) 
withUnsafePointer : 獲取變量的內(nèi)存地址
t和t1剛好差了8個(gè)字節(jié),其實(shí)這8個(gè)字節(jié)存放的就是實(shí)例對(duì)象的內(nèi)存地址

對(duì)于x/8g 0x100506400結(jié)果的解釋
第一個(gè)8字節(jié)0x0000000100008180,毫無(wú)疑問(wèn),metadata(類似于OC中的isa)
第二個(gè)8字節(jié)0x0000000600000003,這里我還不是很清楚,1個(gè)存6一個(gè)存3,待后續(xù)了解后補(bǔ)充
第三個(gè)8字節(jié)0x0000000000000012,很明顯低位的4字節(jié)存了18(age這個(gè)字段)
第四個(gè)8字節(jié)0x0000000079646f4b,低位的4字節(jié)存放了"Kody"所對(duì)應(yīng)的ASCII。
iOS為小端模式,從右邊開(kāi)始讀。0x4b-75-K;0x6f-111-o ;0x64-100-d;0x79-121-y

class

swift中有引用類型,就有值類型,最典型的就是Struct,結(jié)構(gòu)體的定義也非常簡(jiǎn)單,相比較類類型的變量中存儲(chǔ)的是地址,那么值類型存儲(chǔ)就是具體的實(shí)例(或者說(shuō)具體的值)

struct LGStudent{
    var age: Int
    var name: String
}

var s = LGStudent(age:18, name:"kody")
var s1 = s

print("end")
(lldb) po s
? LGStudent
  - age : 18
  - name : "kody"

(lldb) po s1
? LGStudent
  - age : 18
  - name : "kody"

(lldb) 
struct

??其實(shí)引用類型就相當(dāng)于在線的Excel,當(dāng)我們把這個(gè)鏈接共享給別人的時(shí)候,別人的修改我們是能夠看到的;值類型就相當(dāng)于本地的Excel,當(dāng)我們把本地的Excel傳遞給別人的時(shí)候,就相當(dāng)于重新復(fù)制了一份給別人,因此他們對(duì)內(nèi)容的修改我們是無(wú)法感知的。

??另外引用類型和值類型還有一個(gè)最直觀的區(qū)別就是存儲(chǔ)的位置不同:一般情況,值類型存儲(chǔ)在棧上,引用類型在堆上。

??首先我們對(duì)內(nèi)存區(qū)域來(lái)一個(gè)基本概念的認(rèn)知,大家看下面這張圖

memory

棧區(qū)(stack):局部變量和函數(shù)運(yùn)行過(guò)程中的上下文

//test是不是一個(gè)函數(shù)
func test() {
    //我們?cè)诤瘮?shù)內(nèi)部聲明的age變量是不是就是一個(gè)局部變量
    var age: Int = 10
    print(age)
}

test()
(lldb) po withUnsafePointer(to: &age, {print($0)})
0x00007ffeefbff468
0 elements

(lldb) cat address 0x00007ffeefbff468
address:0x00007ffeefbff468, stack address (SP: 0x7ffeefbff440 FP: 0x7ffeefbff470) swiftTest.test() -> ()
(lldb) 

堆區(qū)(Heap):存儲(chǔ)所有對(duì)象

全局區(qū)(Global):存儲(chǔ)全局變量;常量;代碼區(qū)
Segment&Section:Mach-o 文件有多個(gè)段(Segement),每個(gè)段有不同的功能,然后每個(gè)段又分為很多小的Section

TEXT.text : 機(jī)器碼
TEXT.cString : 硬編碼的字符串
TEXT.const : 初始化過(guò)的常量
DATA.data : 初始化過(guò)的可變的(靜態(tài)/全局)變量
DATA.const : 沒(méi)有初始化過(guò)的常量
DATA.bss : 沒(méi)有初始化的(靜態(tài)/全局)變量
DATA.common : 沒(méi)有初始化過(guò)的符號(hào)聲明
int age = 10;

int a;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        char *p = "LG";
        NSLog(@"Hello, World!");
    }
    return 0;
}
(lldb) po &age
0x0000000100008020

(lldb) cat address 0x0000000100008020
address:0x0000000100008020, 8age <+0> , External: NO ocTest.__DATA.__data +8
(lldb) po &a
0x0000000100008024

(lldb) cat address 0x0000000100008024
address:0x0000000100008024, 0a <+0> , External: NO ocTest.__DATA.__common +0

(lldb) cat address 0x00007ffeefbff498
address:0x00007ffeefbff498, stack address (SP: 0x7ffeefbff490 FP: 0x7ffeefbff4b0) main
(lldb) x/8g 0x00007ffeefbff498
0x7ffeefbff498: 0x0000000100003f9e 0x00007ffeefbff4d0
0x7ffeefbff4a8: 0x0000000000000001 0x00007ffeefbff4c0
0x7ffeefbff4b8: 0x00007fff203abf3d 0x0000000000000000
0x7ffeefbff4c8: 0x0000000000000001 0x00007ffeefbff6b8
(lldb) cat address 0x0000000100003f9e
address:0x0000000100003f9e, 0ocTest.__TEXT.__cstring +0
(lldb) 

我們來(lái)看例子

struct LGTeacher{
    var age = 18
    var name = "Kody"
}

func test(){
    var t = LGTeacher()
    print("end")
}

test()

frame varibale -L xxx查看當(dāng)前變量的地址

(lldb) frame variable -L t
0x00007ffeefbff450: (swiftTest.LGTeacher) t = {
0x00007ffeefbff450:   age = 18
0x00007ffeefbff458:   name = "Kody"
}

(lldb) x/8g 0x00007ffeefbff450
0x7ffeefbff450: 0x0000000000000012 0x0000000079646f4b
0x7ffeefbff460: 0xe400000000000000 0x0000000000000000
0x7ffeefbff470: 0x00007ffeefbff490 0x0000000100003644
0x7ffeefbff480: 0x00007ffeefbff4b0 0x0000000100015025
(lldb) 

當(dāng)運(yùn)行至var t = LGTeacher(),棧指針會(huì)向下移動(dòng)24字節(jié)內(nèi)存空間,然后把a(bǔ)ge和name拷貝到分配好的內(nèi)存空間上。作用域完成后,棧指針移動(dòng)還原,銷毀棧內(nèi)存

疑問(wèn):如果說(shuō)當(dāng)前結(jié)構(gòu)體中有一個(gè)引用類型,會(huì)不會(huì)改變結(jié)構(gòu)體的位置?

class LGPerson {
    var age = 18
    var name = "LGMan"
}

struct LGTeacher{
    var age = 18
    var name = "Kody"
    var p = LGPerson()
}

func test(){
    var t = LGTeacher()
    print("end")
}

test()
(lldb) frame variable -L t
0x00007ffeefbff450: (swiftTest.LGTeacher) t = {
0x00007ffeefbff450:   age = 18
0x00007ffeefbff458:   name = "Kody"
scalar:   p = 0x000000010076f140 {
0x000000010076f150:     age = 18
0x000000010076f158:     name = "LGMan"
  }
}
(lldb) x/8g 0x00007ffeefbff450
0x7ffeefbff450: 0x0000000000000012 0x0000000079646f4b
0x7ffeefbff460: 0xe400000000000000 0x000000010076f140
0x7ffeefbff470: 0x00007ffeefbff490 0x0000000100002cb4
0x7ffeefbff480: 0x00007ffeefbff4b0 0x0000000100019025

我們可以發(fā)現(xiàn),當(dāng)結(jié)構(gòu)體存在引用類型時(shí),結(jié)構(gòu)體依然存放在棧上,引用類型依然會(huì)在堆上創(chuàng)建,并且棧上存放的是引用類型的地址0x000000010076f140,指向堆空間。
因此結(jié)構(gòu)體中是否有引用類型并不影響結(jié)構(gòu)體的存儲(chǔ)位置。

當(dāng)我們把struct改為class

class LGTeacher{
    var age = 18
    var name = "Kody"
}

func test(){
    //棧上:分配8字節(jié)大小存儲(chǔ)實(shí)例引用類型
    //堆上:尋找合適的內(nèi)存區(qū)域
    //value的值拷貝到堆區(qū)內(nèi)存空間中
    //把棧上的內(nèi)存地址指向當(dāng)前堆區(qū)
    var t = LGTeacher()
    print("end")
}

test()

2.類與結(jié)構(gòu)體的選擇

  • 1.引入github上StructVsClassPerformance這個(gè)案例來(lái)直觀的測(cè)試當(dāng)前結(jié)構(gòu)體和類的時(shí)間分配
class Tests {
    static func runTests() {
        print("Running tests")
        
        measure("class (1 field)") {
            var x = IntClass(0)
            for _ in 1...10000000 {
                x = x + IntClass(1)
            }
        }
        
        measure("struct (1 field)") {
            var x = IntStruct(0)
            for _ in 1...10000000 {
                x = x + IntStruct(1)
            }
        }
        
        measure("class (10 fields)") {
            var x = Int10Class(0)
            for _ in 1...10000000 {
                x = x + Int10Class(1)
            }
        }
        
        measure("struct (10 fields)") {
            var x = Int10Struct(0)
            for _ in 1...10000000 {
                x = x + Int10Struct(1)
            }
        }
    }
    
    static private func measure(_ name: String, block: @escaping () -> ()) {
        print()
        print("\(name)")
        let t0 = CACurrentMediaTime()
        
        block()
        
        let dt = CACurrentMediaTime() - t0
        print("\(dt)")
    }
}

統(tǒng)計(jì)1/10個(gè)變量的class和struct創(chuàng)建10000000次所需要的時(shí)間。

Running tests

class (1 field)
8.673463547999745

struct (1 field)
4.282427998999992

class (10 fields)
7.942918924999503

struct (10 fields)
4.6826105930003905

結(jié)果很明顯,無(wú)論是1個(gè)變量還是10個(gè)變量,struct的性能都遠(yuǎn)高于class。
一般來(lái)說(shuō),盡可能優(yōu)先選用結(jié)構(gòu)體,結(jié)構(gòu)體在棧上線程安全,在進(jìn)行內(nèi)存分配的過(guò)程中也是比堆區(qū)內(nèi)存分配快得多

  • 2.使用官方案例一
enum Color { case blue, green, gray }
enum Orientation { case left, right }
enum Tail { case none, tail, bubble }
var cache = [String : UIImage]()
func makeBalloon(_ color: Color, _ orientation: Orientation, _ tail: Tail) -> UIImage {
    let key = "\(color):\(orientation):\(tail)"
    if let image = cache[key] {
        return image
    }
    ...
}

分析:cache中使用string作為key值是有問(wèn)題的。方法中的key(字符串)是存放在堆區(qū)上的,因此每次執(zhí)行到該方法時(shí),雖然有字典的緩存,緩存雖然命中,但是仍然會(huì)進(jìn)行不停的堆內(nèi)存的分配與銷毀。很顯然這個(gè)效率是無(wú)法接受的(此時(shí)該需求是聊天頁(yè)面上的氣泡)

那么我們應(yīng)該使用struct作為cache的key值來(lái)提高效率

enum Color { case blue, green, gray }
enum Orientation { case left, right }
enum Tail { case none, tail, bubble }
var cache = [Balloon : UIImage]()
func makeBalloon(_ balloon: Balloon) -> UIImage {
    if let image = cache[balloon] {
        return image
    }
    ...
}

struct Balloon: Hashable {
    var color: Color
    var orientation: Orientation
    var tail: Tail
}

此時(shí)就沒(méi)有了堆上的內(nèi)存的分配與銷毀,對(duì)于我們當(dāng)前的代碼執(zhí)行效率就非常高了

  • 3.使用官方案例二
struct Attachment {
    let fileURL: URL
    let uuid: String
    let mineType: String
    
    init?(fileURL: URL, uuid: String, mineType: String) {
        guard mineType.isMineType
        else { return nil }
        self.fileURL = fileURL
        self.uuid = uuid
        self.mineType = mineType
    }
}

此時(shí)出現(xiàn)了3個(gè)堆區(qū)變量,在分配內(nèi)存及引用計(jì)數(shù)層面上消耗內(nèi)存比較大,因此需要優(yōu)化

struct Attachment {
    let fileURL: URL
    let uuid: UUID
    let mineType: MineType
    
    init?(fileURL: URL, uuid: UUID, mineType: MineType) {
        guard mineType.isMineType
        else { return nil }
        self.fileURL = fileURL
        self.uuid = uuid
        self.mineType = mineType
    }
}
enum MineType: String{
    case jpeg = "image/jpeg"
    ....
}

將uuid優(yōu)化為UUID值類型,將mineType優(yōu)化為enum值類型

3.初始化器

  • 類初始器
class LGTeacher{
    var age: Int
    var name: String

    init(_ age: Int, _ name: String) {
        self.age = age
        self.name = name
    }
}

var t = LGTeacher(18, "Kody")

當(dāng)前的類編譯器不會(huì)自動(dòng)提供成員初始化器

  • struct初始化器
struct LGTeacher {
    var age: Int
    var name: String
}

var t = LGTeacher(age: 18, name: "Kody")

當(dāng)前的結(jié)構(gòu)體編譯器會(huì)自動(dòng)提供成員初始化器(前提是我們沒(méi)有指定初始化器)

  • 便捷初始化器
class LGTeacher{
    var age: Int
    var name: String

    init(_ age: Int, _ name: String) {
        self.age = age
        self.name = name
    }
    
    convenience init() {
        self.init(18, "Kody")
    }
}

class LGPerson: LGTeacher {
    var subjectName: String
    
    init(_ subjectName: String) {
        //調(diào)用父類初始化器之前,自身類的屬性必須初始化完成
        self.subjectName = subjectName
        super.init(18, "Kody")
    }
}

var t = LGPerson("a")

這里我們記住:

  • 指定初始化器必須保證在向上委托給父類初始化器之前,其所在類引入的所有屬性都要初始化完成。
  • 指定初始化器必須先向上委托父類初始化器,然后才能為繼承的屬性設(shè)置新值。如果不這樣做,指定初始化器賦予的新值將被父類中的初始化器所覆蓋
  • 便捷初始化器必須先委托同類中的其它初始化器,然后再為任意屬性賦新值(包括 同類里定義的屬性)。如果沒(méi)這么做,便捷構(gòu)初始化器賦予的新值將被自己類中其它指定初始化器所覆蓋。
  • 初始化器在第一階段初始化完成之前,不能調(diào)用任何實(shí)例方法、不能讀取任何實(shí)例屬性的值,也不能引用 self 作為值。

  • 可失敗初始化器
    意味著當(dāng)前因?yàn)閰?shù)不合法或外部條件不滿足,存在初始化失敗的情況。這種Swift中可失敗初始化器寫return nil語(yǔ)句,來(lái)表明可失敗初始化器在何種情況下回觸發(fā)初始化失敗
class LGTeacher{
    var age: Int
    var name: String

    init?(_ age: Int, _ name: String) {
        if age < 18 { return nil}
        self.age = age
        self.name = name
    }
    
    convenience init?() {
        self.init(18, "Kody")
    }
    
}

var t = LGTeacher(17, "Kody")
print("end")
  • 必要初始化器
    在類的初始化器前添加required修飾符來(lái)表明所有該類的子類如果要自定義初始化器的話都必須實(shí)現(xiàn)該初始化器。
class LGPerson {
    var age: Int
    var name: String

    required init(_ age: Int, _ name: String) {
        self.age = age
        self.name = name
    }

    convenience init() {
        self.init(18, "Kody")
    }
}

class LGTeacher: LGPerson {
    var subjectName: String = ""
    
    //自定義初始化器,必須實(shí)現(xiàn)required init(){}
    init(_ subjectName: String) {
        super.init(18, "Kody")
        self.subjectName = subjectName
    }

    required init(_ age: Int, _ name: String) {
        super.init(age, name)
    }
    
    //當(dāng)然也可以添加一個(gè)便捷初始化器來(lái)完成這個(gè)功能,此時(shí)就不需要實(shí)現(xiàn)required init(){}
    convenience init(_ age: Int, _ name: String, _ subjectName: String) {
        self.init(age, name)
        self.subjectName = subjectName
    }
}

var t = LGTeacher(17, "kody")
print("end")

4.類的生命周期

iOS開(kāi)發(fā)的語(yǔ)言不管是OC還是Swift后端都是通過(guò)LLVM進(jìn)行編譯的,如圖所示


LLVM

OC通過(guò)clang編譯器,編譯成IR,然后再生成可執(zhí)行文件.o(這里也就是我們的機(jī)器碼)
Swift則是通過(guò)Swift編譯器編譯成iR,然后再生成可執(zhí)行文件。


Swift編譯過(guò)程
  • Parse,語(yǔ)法分析,解析成抽象語(yǔ)法樹(shù)
  • Sema,語(yǔ)義分析。比如說(shuō)當(dāng)前的類型檢查是否正確、是否安全
  • SILGen,降級(jí)變成SILGen。SIL全稱為Swift Interminal Language(Swift中間語(yǔ)言)
  • Raw SIL,原生的SIL,沒(méi)有開(kāi)啟優(yōu)化選項(xiàng)
  • SILOpt Canonical SIL,優(yōu)化后的SIL。優(yōu)化了一些額外的代碼
  • IRGen和LLVM IR,由LLVM降級(jí)成IR
  • Machine Code,由后端代碼變?yōu)闄C(jī)器碼(x86、arm64、...)
// 分析輸出AST
swiftc main.swift -dump-parse
// 分析并且檢查類型輸出AST 
swiftc main.swift -dump-ast
// 生成中間體語(yǔ)言(SIL),未優(yōu)化 
swiftc main.swift -emit-silgen
// 生成中間體語(yǔ)言(SIL),優(yōu)化后的 
swiftc main.swift -emit-sil
// 生成LLVM中間體語(yǔ)言 (.ll文件) 
swiftc main.swift -emit-ir
// 生成LLVM中間體語(yǔ)言 (.bc文件) 
swiftc main.swift -emit-bc
// 生成匯編
swiftc main.swift -emit-assembly
// 編譯生成可執(zhí)行.out文件 
swiftc -o main.o main.swift

將main.swift輸出成優(yōu)化過(guò)后的SIL

class LGTeacher {
    var age = 18
    var name = "LG"
}

var t = LGTeacher()
sil_stage canonical

import Builtin
import Swift
import SwiftShims

import Foundation

class LGTeacher {
  @_hasStorage @_hasInitialValue var age: Int { get set }
  @_hasStorage @_hasInitialValue var name: String { get set }
  @objc deinit
  init()
}

@_hasStorage @_hasInitialValue var t: LGTeacher { get set }

// t
sil_global hidden @$s4main1tAA9LGTeacherCvp : $LGTeacher

// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
  //alloc_global,分配一個(gè)全局變量
  alloc_global @$s4main1tAA9LGTeacherCvp          // id: %2

  //拿到全局變量的地址給到%3寄存器
  %3 = global_addr @$s4main1tAA9LGTeacherCvp : $*LGTeacher // user: %7

  //%4寄存器存放LGTeacher元類型
  %4 = metatype $@thick LGTeacher.Type            // user: %6

  // function_ref LGTeacher.__allocating_init()
  // 定義一個(gè)方法引用給%5寄存器(函數(shù)的指針地址給到%5)
  %5 = function_ref @$s4main9LGTeacherCACycfC : $@convention(method) (@thick LGTeacher.Type) -> @owned LGTeacher // user: %6

  // 執(zhí)行%5函數(shù)指針,參數(shù)為%4,并且把函數(shù)的返回值給到%6寄存器
  %6 = apply %5(%4) : $@convention(method) (@thick LGTeacher.Type) -> @owned LGTeacher // user: %7

  //將%6存儲(chǔ)到%3,實(shí)例變量的內(nèi)存地址存到全局變量
  store %6 to %3 : $*LGTeacher                    // id: %7

  //定義一個(gè)Int32值,并且值為0,然后return 0。類似于OC代碼中的main
  %8 = integer_literal $Builtin.Int32, 0          // user: %9
  %9 = struct $Int32 (%8 : $Builtin.Int32)        // user: %10
  
  return %9 : $Int32                              // id: %10
} // end sil function 'main'
...

對(duì)于LGTeacher

  • @_hasStorage @_hasInitialValue表示有一個(gè)初始化過(guò)的存儲(chǔ)屬性
  • 這里的LGTeacher有2個(gè)初始化過(guò)的存儲(chǔ)屬性age、name
  • @objc標(biāo)記的deinit和默認(rèn)的init函數(shù)

@main:入口函數(shù),@
%0:寄存器,也可以理解成常量。一旦賦值不能修改。虛擬的,跑到設(shè)備上會(huì)使用真的寄存器

  • 還原當(dāng)前的名稱,xcrun swift-demangle
? xcrun swift-demangle s4main1tAA9LGTeacherCvp
$s4main1tAA9LGTeacherCvp ---> main.t : main.LGTeacher

找到寄存器%5中s4main9LGTeacherCACycfC函數(shù)

// LGTeacher.__allocating_init()
sil hidden [exact_self_class] @$s4main9LGTeacherCACycfC : $@convention(method) (@thick LGTeacher.Type) -> @owned LGTeacher {
// %0 "$metatype"
bb0(%0 : $@thick LGTeacher.Type):
  %1 = alloc_ref $LGTeacher                       // user: %3
  // function_ref LGTeacher.init()
  %2 = function_ref @$s4main9LGTeacherCACycfc : $@convention(method) (@owned LGTeacher) -> @owned LGTeacher // user: %3
  %3 = apply %2(%1) : $@convention(method) (@owned LGTeacher) -> @owned LGTeacher // user: %4
  return %3 : $LGTeacher                          // id: %4
} // end sil function '$s4main9LGTeacherCACycfC'
  • metadata可以理解為isa
  • alloc_ref,進(jìn)入SIL文檔查詢
    Allocates an object of reference type T. The object will be initialized with retain count 1; its state will be otherwise uninitialized. The optional objc attribute indicates that the object should be allocated using Objective-C's allocation methods (+allocWithZone:).
    大概意思為分配一個(gè)引用類型為T的對(duì)象,并且該對(duì)象的引用計(jì)數(shù)為1。也就是說(shuō)在堆區(qū)上申請(qǐng)內(nèi)存空間。如果對(duì)象標(biāo)識(shí)為objc,將會(huì)使用Objective-C的+allocWithZone:方法申請(qǐng)內(nèi)存空間。也就是oc的初始化方法
    我們通過(guò)代碼來(lái)查看2者的區(qū)別
class LGTeacher {
    var age = 18
    var name = "LG"
}

var t = LGTeacher()
LGTeacher

LGTeacher.__allocating_init

進(jìn)入?yún)R編斷點(diǎn),callq其實(shí)相當(dāng)于函數(shù)調(diào)用。調(diào)用LGTeacher.__allocating_init(),進(jìn)入發(fā)現(xiàn)斷點(diǎn)跑到了LGTeacher.__allocating_init,在這里執(zhí)行swift_allocObject,并且執(zhí)行初始化方法LGTeacher.init。

class LGTeacher: NSObject {
    var age = 18
    var name = "LG"
}

var t = LGTeacher()
LGTeacher.__allocating_init

執(zhí)行objc_allocWithZone,并且使用objc_msgSend發(fā)送init消息

至此,已經(jīng)通過(guò)代碼來(lái)解釋SIL文檔中的alloc_ref

此時(shí),我們可以通過(guò)Swift源碼來(lái)分析_swift_allocObject_

  • 進(jìn)入到 HeapObject.cpp文件,找到swift_allocObject函數(shù)
//metadata,元數(shù)據(jù)類型。8字節(jié)
//requiredSize,需要的字節(jié)大小
//requiredAlignmentMask,對(duì)齊掩碼。Swift對(duì)象8字節(jié)對(duì)齊,因此為7
static HeapObject *_swift_allocObject_(HeapMetadata const *metadata,
                                       size_t requiredSize,
                                       size_t requiredAlignmentMask) {
  assert(isAlignmentMask(requiredAlignmentMask));
  auto object = reinterpret_cast<HeapObject *>(
      swift_slowAlloc(requiredSize, requiredAlignmentMask));

  // NOTE: this relies on the C++17 guaranteed semantics of no null-pointer
  // check on the placement new allocator which we have observed on Windows,
  // Linux, and macOS.
  new (object) HeapObject(metadata);

  // If leak tracking is enabled, start tracking this object.
  SWIFT_LEAKS_START_TRACKING_OBJECT(object);

  SWIFT_RT_TRACK_INVOCATION(object, swift_allocObject);

  return object;
}

進(jìn)入swift_slowAlloc

void *swift::swift_slowAlloc(size_t size, size_t alignMask) {
  void *p;
  // This check also forces "default" alignment to use AlignedAlloc.
  //#  define MALLOC_ALIGN_MASK 15
  if (alignMask <= MALLOC_ALIGN_MASK) {
#if defined(__APPLE__)
    p = malloc_zone_malloc(DEFAULT_ZONE(), size);
#else
    p = malloc(size);
#endif
  } else {
    size_t alignment = (alignMask == ~(size_t(0)))
                           ? _swift_MinAllocationAlignment
                           : alignMask + 1;
    p = AlignedAlloc(size, alignment);
  }
  if (!p) swift::crash("Could not allocate memory.");
  return p;
}

swift_slowAlloc其實(shí)就是在調(diào)用malloc開(kāi)辟內(nèi)存空間

Swift對(duì)象內(nèi)存分配:
Object.__allocating_init(編譯器生成) ----> swift_allocObject -----> _swift_allocObject_(HeapObject.cpp) ----> swift_slowAlloc(Heap.cpp) ----> malloc(Heap.cpp)

Swift對(duì)象的內(nèi)存結(jié)構(gòu)HeapObject(OC objc_object),有2個(gè)屬性:metadatarefCount,默認(rèn)占用16字節(jié)大小。

#define SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS       \
  InlineRefCounts refCounts

struct HeapObject {
  /// This is always a valid pointer to a metadata object.
  HeapMetadata const *__ptrauth_objc_isa_pointer metadata;

  SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;

#ifndef __swift__
  HeapObject() = default;

  // Initialize a HeapObject header as appropriate for a newly-allocated object.
  constexpr HeapObject(HeapMetadata const *newMetadata) 
    : metadata(newMetadata)
    , refCounts(InlineRefCounts::Initialized)
  { }
  
  // Initialize a HeapObject header for an immortal object
  constexpr HeapObject(HeapMetadata const *newMetadata,
                       InlineRefCounts::Immortal_t immortal)
  : metadata(newMetadata)
  , refCounts(InlineRefCounts::Immortal)
  { }

#ifndef NDEBUG
  void dump() const SWIFT_USED;
#endif

#endif // __swift__
};

我們已經(jīng)分析了實(shí)例對(duì)象的內(nèi)存結(jié)構(gòu),接下來(lái)分析HeapMetadta,也就是metadata

//其實(shí)就是TargetHeapMetadata類型,起了個(gè)別名
using HeapMetadata = TargetHeapMetadata<InProcess>;

點(diǎn)擊進(jìn)入TargetHeapMetaData

struct TargetHeapMetadata : TargetMetadata<Runtime> {
  using HeaderType = TargetHeapMetadataHeader<Runtime>;

  TargetHeapMetadata() = default;
  constexpr TargetHeapMetadata(MetadataKind kind)
    : TargetMetadata<Runtime>(kind) {}
#if SWIFT_OBJC_INTEROP
  constexpr TargetHeapMetadata(TargetAnyClassMetadata<Runtime> *isa)
    : TargetMetadata<Runtime>(isa) {}
#endif
};

TargetHeapMetaData繼承自TargetMetaData
初始化方法中,如果是純swift類,傳入的參數(shù)就是MetadataKind。如果swift與OBJC交互的話,傳入的參數(shù)就是isa。

關(guān)于MetadataKind的定義

name value
Class 0x0
Struct 0x200
Enum 0x201
Optional 0x202
ForeignClass 0x203
Opaque 0x300
Tuple 0x301
Function 0x302
Existential 0x303
Metatype 0x304
ObjCClassWrapper 0x305
ExistentialMetatype 0x306
HeapLocalVariable 0x400
HeapGenericLocalVariable 0x500
ErrorObject 0x501
LastEnumerated 0x7FF

此時(shí)我們繼續(xù)進(jìn)入TargetMetadata探究,其實(shí)已經(jīng)進(jìn)入了一個(gè)瓶頸了。繼續(xù)查看分析這個(gè)結(jié)構(gòu)體

  getTypeContextDescriptor() const {
    switch (getKind()) {
    case MetadataKind::Class: {
      const auto cls = static_cast<const TargetClassMetadata<Runtime> *>(this);
      if (!cls->isTypeMetadata())
        return nullptr;
      if (cls->isArtificialSubclass())
        return nullptr;
      return cls->getDescription();
    }
    case MetadataKind::Struct:
    case MetadataKind::Enum:
    case MetadataKind::Optional:
      return static_cast<const TargetValueMetadata<Runtime> *>(this)
          ->Description;
    case MetadataKind::ForeignClass:
      return static_cast<const TargetForeignClassMetadata<Runtime> *>(this)
          ->Description;
    default:
      return nullptr;
    }
  }

根據(jù)不同的MetadataKind類型來(lái)區(qū)分不同的數(shù)據(jù)類型。所以MetadataKind是所有類型元類的最終基類
const auto cls = static_cast<const TargetClassMetadata<Runtime> *>(this);當(dāng)我的metadata是class類型的時(shí)候,將當(dāng)前指針強(qiáng)轉(zhuǎn)為TargetClassMetadata類型

進(jìn)入TargetClassMetadata中有以下代碼

  constexpr TargetClassMetadata(const TargetAnyClassMetadata<Runtime> &base,
             ClassFlags flags,
             ClassIVarDestroyer *ivarDestroyer,
             StoredPointer size, StoredPointer addressPoint,
             StoredPointer alignMask,
             StoredPointer classSize, StoredPointer classAddressPoint)

  /// Swift-specific class flags.
  ClassFlags Flags;

  /// The address point of instances of this type.
  uint32_t InstanceAddressPoint;

  /// The required size of instances of this type.
  /// 'InstanceAddressPoint' bytes go before the address point;
  /// 'InstanceSize - InstanceAddressPoint' bytes go after it.
  uint32_t InstanceSize;

  /// The alignment mask of the address point of instances of this type.
  uint16_t InstanceAlignMask;

  /// Reserved for runtime use.
  uint16_t Reserved;

  /// The total size of the class object, including prefix and suffix
  /// extents.
  uint32_t ClassSize;

  /// The offset of the address point within the class object.
  uint32_t ClassAddressPoint;

進(jìn)入TargetClassMetadata父類TargetAnyClassMetadata中有以下代碼

  TargetSignedPointer<Runtime, const TargetClassMetadata<Runtime> *
                                   __ptrauth_swift_objc_superclass>
  Superclass;

  TargetPointer<Runtime, void> CacheData[2];

  StoredSize Data;

看了這2個(gè)類,參考o(jì)bjc_class我們可以大膽的猜測(cè)這就是Swift類的數(shù)據(jù)結(jié)構(gòu)

通過(guò)源碼總結(jié)的Swift類的數(shù)據(jù)結(jié)構(gòu)

struct Metadata{
    var kind: Int
    var superClass: Any.Type
    var cacheData: (Int, Int)
    var data: Int
    var classFlags: Int32
    var instanceAddressPoint: UInt32
    var instanceSize: UInt32
    var instanceAlignmentMask: UInt16
    var reserved: UInt16
    var classSize: UInt32
    var classAddressPoint: UInt32
    var typeDescriptor: UnsafeMutableRawPointer
    var iVarDestroyer: UnsafeRawPointer
}

通過(guò)代碼來(lái)驗(yàn)證

struct HeapObject {
    var metadata: UnsafeRawPointer
    var refCount1: UInt32
    var refCount2: UInt32
}

class LGTeacher {
    var age = 18
    var name = "LG"
}

var t = LGTeacher()

//將t的指針重新綁定到HeapObject
//獲取實(shí)例對(duì)象的指針
var objcRawPtr = Unmanaged.passUnretained(t).toOpaque()
//重新綁定到HeapObject
let objcPtr = objcRawPtr.bindMemory(to: HeapObject.self, capacity: 1)
print(objcPtr.pointee)
print("end")

執(zhí)行結(jié)果

HeapObject(metadata: 0x00000001000081a0, refCount1: 3, refCount2: 0)
(lldb) x/8g 0x00000001000081a0
0x1000081a0: 0x0000000100008168 0x00007ff852c3f8b8
0x1000081b0: 0x00007ff8112eb800 0x0000803000000000
0x1000081c0: 0x000000010143f2f2 0x0000000000000002
0x1000081d0: 0x0000000700000028 0x00000010000000a8
(lldb) 
  • 0x0000000100008168 ----> isa

那么此時(shí)的metadata我們是否也可以還原成Metadata(源碼總結(jié)的)結(jié)構(gòu)體呢?

struct HeapObject {
    var metadata: UnsafeRawPointer
    var refCount1: UInt32
    var refCount2: UInt32
}

struct Metadata{
    var kind: Int
    var superClass: Any.Type
    var cacheData: (Int, Int)
    var data: Int
    var classFlags: Int32
    var instanceAddressPoint: UInt32
    var instanceSize: UInt32
    var instanceAlignmentMask: UInt16
    var reserved: UInt16
    var classSize: UInt32
    var classAddressPoint: UInt32
    var typeDescriptor: UnsafeMutableRawPointer
    var iVarDestroyer: UnsafeRawPointer
}

class LGTeacher {
    var age = 18
    var name = "LG"
}

var t = LGTeacher()

//將t的指針重新綁定到HeapObject
//獲取實(shí)例對(duì)象的指針
var objcRawPtr = Unmanaged.passUnretained(t).toOpaque()
//重新綁定到HeapObject
let objcPtr = objcRawPtr.bindMemory(to: HeapObject.self, capacity: 1)
print(objcPtr.pointee)

//MemoryLayoutce,Swift中測(cè)量數(shù)據(jù)類型的大小
let metadataPtr = objcPtr.pointee.metadata.bindMemory(to: Metadata.self, capacity: MemoryLayout<Metadata>.stride)
print(metadataPtr.pointee)

print("end")

執(zhí)行結(jié)果

HeapObject(metadata: 0x00000001000081a8, refCount1: 3, refCount2: 0)
Metadata(kind: 4295000432, superClass: _TtCs12_SwiftObject, cacheData: (140703416891392,
140943646785536), data: 4485824738, classFlags: 2, instanceAddressPoint: 0, instanceSize: 40, 
instanceAlignmentMask: 7, reserved: 0, classSize: 168, classAddressPoint: 16, typeDescriptor: 
0x0000000100003c4c, iVarDestroyer: 0x0000000000000000)
(lldb) 
  • superClass: _TtCs12_SwiftObject,父類為_TtCs12_SwiftObject
  • instanceSize: 40,實(shí)例大小為16(metadata+refCount)+24(age+name(8+8))
  • instanceAlignmentMask: 7,8字節(jié)對(duì)齊掩碼

至此,通過(guò)代碼已經(jīng)證明了MetaData的數(shù)據(jù)結(jié)構(gòu)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容