美文网首页
Swift -- 1.类与结构体(上)

Swift -- 1.类与结构体(上)

作者: MissStitch丶 | 来源:发表于2022-01-04 15:29 被阅读0次

    1.类与结构体的异同

    主要的相同点:

    • 定义存储值的属性
    • 定义方法
    • 定义下标以使用下标语法提供对其值的访问(点语法访问值)
    • 定义初始化器
    • 使用extension来拓展功能
    • 遵循协议来提供某种功能

    主要的不同点:

    • 类有继承特性,而结构体没有
    • 类型转换使你能够在运行时检查和解释类实例的类型(Mirro)
    • 类有析构函数来释放其分配的资源(deinit)
    • 引用计数允许对一个类实例有多个引用

    对于类与结构体我们区分的第一件事就是:

    类是引用类型。也就意味着一个类类型的变量并不直接存储具体的实例对象,是对当前存储具体实例内存地址的引用

    这里借助2个指令来查看当前变量的内存结构
    po : p和po的区别在于使用po只会输出对应的值,而p则会输出返回值的类型以及命令结果的引用名。
    x/8g : 读取内存中的值(8g:8字节格式输出)
    
    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 : 获取变量的内存地址
    t和t1刚好差了8个字节,其实这8个字节存放的就是实例对象的内存地址
    
    对于x/8g 0x100506400结果的解释
    第一个8字节0x0000000100008180,毫无疑问,metadata(类似于OC中的isa)
    第二个8字节0x0000000600000003,这里我还不是很清楚,1个存6一个存3,待后续了解后补充
    第三个8字节0x0000000000000012,很明显低位的4字节存了18(age这个字段)
    第四个8字节0x0000000079646f4b,低位的4字节存放了"Kody"所对应的ASCII。
    iOS为小端模式,从右边开始读。0x4b-75-K;0x6f-111-o ;0x64-100-d;0x79-121-y
    
    class
    swift中有引用类型,就有值类型,最典型的就是Struct,结构体的定义也非常简单,相比较类类型的变量中存储的是地址,那么值类型存储就是具体的实例(或者说具体的值)
    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

      其实引用类型就相当于在线的Excel,当我们把这个链接共享给别人的时候,别人的修改我们是能够看到的;值类型就相当于本地的Excel,当我们把本地的Excel传递给别人的时候,就相当于重新复制了一份给别人,因此他们对内容的修改我们是无法感知的。

      另外引用类型和值类型还有一个最直观的区别就是存储的位置不同:一般情况,值类型存储在栈上,引用类型在堆上

      首先我们对内存区域来一个基本概念的认知,大家看下面这张图

    memory

    栈区(stack):局部变量和函数运行过程中的上下文

    //test是不是一个函数
    func test() {
        //我们在函数内部声明的age变量是不是就是一个局部变量
        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) 
    

    堆区(Heap):存储所有对象

    全局区(Global):存储全局变量;常量;代码区
    Segment&Section:Mach-o 文件有多个段(Segement),每个段有不同的功能,然后每个段又分为很多小的Section

    TEXT.text : 机器码
    TEXT.cString : 硬编码的字符串
    TEXT.const : 初始化过的常量
    DATA.data : 初始化过的可变的(静态/全局)变量
    DATA.const : 没有初始化过的常量
    DATA.bss : 没有初始化的(静态/全局)变量
    DATA.common : 没有初始化过的符号声明
    
    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) 
    

    我们来看例子

    struct LGTeacher{
        var age = 18
        var name = "Kody"
    }
    
    func test(){
        var t = LGTeacher()
        print("end")
    }
    
    test()
    

    frame varibale -L xxx查看当前变量的地址

    (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) 
    

    当运行至var t = LGTeacher(),栈指针会向下移动24字节内存空间,然后把age和name拷贝到分配好的内存空间上。作用域完成后,栈指针移动还原,销毁栈内存

    疑问:如果说当前结构体中有一个引用类型,会不会改变结构体的位置?

    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
    

    我们可以发现,当结构体存在引用类型时,结构体依然存放在栈上,引用类型依然会在堆上创建,并且栈上存放的是引用类型的地址0x000000010076f140,指向堆空间。
    因此结构体中是否有引用类型并不影响结构体的存储位置。

    当我们把struct改为class

    class LGTeacher{
        var age = 18
        var name = "Kody"
    }
    
    func test(){
        //栈上:分配8字节大小存储实例引用类型
        //堆上:寻找合适的内存区域
        //value的值拷贝到堆区内存空间中
        //把栈上的内存地址指向当前堆区
        var t = LGTeacher()
        print("end")
    }
    
    test()
    

    2.类与结构体的选择

    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)")
        }
    }
    

    统计1/10个变量的class和struct创建10000000次所需要的时间。

    Running tests
    
    class (1 field)
    8.673463547999745
    
    struct (1 field)
    4.282427998999992
    
    class (10 fields)
    7.942918924999503
    
    struct (10 fields)
    4.6826105930003905
    

    结果很明显,无论是1个变量还是10个变量,struct的性能都远高于class。
    一般来说,尽可能优先选用结构体,结构体在栈上线程安全,在进行内存分配的过程中也是比堆区内存分配快得多

    • 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值是有问题的。方法中的key(字符串)是存放在堆区上的,因此每次执行到该方法时,虽然有字典的缓存,缓存虽然命中,但是仍然会进行不停的堆内存的分配与销毁。很显然这个效率是无法接受的(此时该需求是聊天页面上的气泡)

    那么我们应该使用struct作为cache的key值来提高效率

    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
    }
    

    此时就没有了堆上的内存的分配与销毁,对于我们当前的代码执行效率就非常高了

    • 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
        }
    }
    

    此时出现了3个堆区变量,在分配内存及引用计数层面上消耗内存比较大,因此需要优化

    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优化为UUID值类型,将mineType优化为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")
    

    当前的类编译器不会自动提供成员初始化器

    • struct初始化器
    struct LGTeacher {
        var age: Int
        var name: String
    }
    
    var t = LGTeacher(age: 18, name: "Kody")
    

    当前的结构体编译器会自动提供成员初始化器(前提是我们没有指定初始化器)

    • 便捷初始化器
    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) {
            //调用父类初始化器之前,自身类的属性必须初始化完成
            self.subjectName = subjectName
            super.init(18, "Kody")
        }
    }
    
    var t = LGPerson("a")
    

    这里我们记住:

    • 指定初始化器必须保证在向上委托给父类初始化器之前,其所在类引入的所有属性都要初始化完成。
    • 指定初始化器必须先向上委托父类初始化器,然后才能为继承的属性设置新值。如果不这样做,指定初始化器赋予的新值将被父类中的初始化器所覆盖
    • 便捷初始化器必须先委托同类中的其它初始化器,然后再为任意属性赋新值(包括 同类里定义的属性)。如果没这么做,便捷构初始化器赋予的新值将被自己类中其它指定初始化器所覆盖。
    • 初始化器在第一阶段初始化完成之前,不能调用任何实例方法、不能读取任何实例属性的值,也不能引用 self 作为值。

    • 可失败初始化器
      意味着当前因为参数不合法或外部条件不满足,存在初始化失败的情况。这种Swift中可失败初始化器写return nil语句,来表明可失败初始化器在何种情况下回触发初始化失败
    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修饰符来表明所有该类的子类如果要自定义初始化器的话都必须实现该初始化器。
    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 = ""
        
        //自定义初始化器,必须实现required init(){}
        init(_ subjectName: String) {
            super.init(18, "Kody")
            self.subjectName = subjectName
        }
    
        required init(_ age: Int, _ name: String) {
            super.init(age, name)
        }
        
        //当然也可以添加一个便捷初始化器来完成这个功能,此时就不需要实现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开发的语言不管是OC还是Swift后端都是通过LLVM进行编译的,如图所示


    LLVM

    OC通过clang编译器,编译成IR,然后再生成可执行文件.o(这里也就是我们的机器码)
    Swift则是通过Swift编译器编译成iR,然后再生成可执行文件。


    Swift编译过程
    • Parse,语法分析,解析成抽象语法树
    • Sema,语义分析。比如说当前的类型检查是否正确、是否安全
    • SILGen,降级变成SILGen。SIL全称为Swift Interminal Language(Swift中间语言)
    • Raw SIL,原生的SIL,没有开启优化选项
    • SILOpt Canonical SIL,优化后的SIL。优化了一些额外的代码
    • IRGen和LLVM IR,由LLVM降级成IR
    • Machine Code,由后端代码变为机器码(x86、arm64、...)
    // 分析输出AST
    swiftc main.swift -dump-parse
    // 分析并且检查类型输出AST 
    swiftc main.swift -dump-ast
    // 生成中间体语言(SIL),未优化 
    swiftc main.swift -emit-silgen
    // 生成中间体语言(SIL),优化后的 
    swiftc main.swift -emit-sil
    // 生成LLVM中间体语言 (.ll文件) 
    swiftc main.swift -emit-ir
    // 生成LLVM中间体语言 (.bc文件) 
    swiftc main.swift -emit-bc
    // 生成汇编
    swiftc main.swift -emit-assembly
    // 编译生成可执行.out文件 
    swiftc -o main.o main.swift
    

    将main.swift输出成优化过后的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,分配一个全局变量
      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()
      // 定义一个方法引用给%5寄存器(函数的指针地址给到%5)
      %5 = function_ref @$s4main9LGTeacherCACycfC : $@convention(method) (@thick LGTeacher.Type) -> @owned LGTeacher // user: %6
    
      // 执行%5函数指针,参数为%4,并且把函数的返回值给到%6寄存器
      %6 = apply %5(%4) : $@convention(method) (@thick LGTeacher.Type) -> @owned LGTeacher // user: %7
    
      //将%6存储到%3,实例变量的内存地址存到全局变量
      store %6 to %3 : $*LGTeacher                    // id: %7
    
      //定义一个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'
    ...
    

    对于LGTeacher

    • @_hasStorage @_hasInitialValue表示有一个初始化过的存储属性
    • 这里的LGTeacher有2个初始化过的存储属性age、name
    • @objc标记的deinit和默认的init函数

    @main:入口函数,@
    %0:寄存器,也可以理解成常量。一旦赋值不能修改。虚拟的,跑到设备上会使用真的寄存器

    • 还原当前的名称,xcrun swift-demangle
    ❯ xcrun swift-demangle s4main1tAA9LGTeacherCvp
    $s4main1tAA9LGTeacherCvp ---> main.t : main.LGTeacher
    

    找到寄存器%5中s4main9LGTeacherCACycfC函数

    // 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,进入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:).
      大概意思为分配一个引用类型为T的对象,并且该对象的引用计数为1。也就是说在堆区上申请内存空间。如果对象标识为objc,将会使用Objective-C的+allocWithZone:方法申请内存空间。也就是oc的初始化方法
      我们通过代码来查看2者的区别
    class LGTeacher {
        var age = 18
        var name = "LG"
    }
    
    var t = LGTeacher()
    
    LGTeacher
    LGTeacher.__allocating_init

    进入汇编断点,callq其实相当于函数调用。调用LGTeacher.__allocating_init(),进入发现断点跑到了LGTeacher.__allocating_init,在这里执行swift_allocObject,并且执行初始化方法LGTeacher.init

    class LGTeacher: NSObject {
        var age = 18
        var name = "LG"
    }
    
    var t = LGTeacher()
    
    LGTeacher.__allocating_init

    执行objc_allocWithZone,并且使用objc_msgSend发送init消息

    至此,已经通过代码来解释SIL文档中的alloc_ref

    此时,我们可以通过Swift源码来分析_swift_allocObject_

    • 进入到 HeapObject.cpp文件,找到swift_allocObject函数
    //metadata,元数据类型。8字节
    //requiredSize,需要的字节大小
    //requiredAlignmentMask,对齐掩码。Swift对象8字节对齐,因此为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;
    }
    

    进入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其实就是在调用malloc开辟内存空间

    Swift对象内存分配:
    Object.__allocating_init(编译器生成) ----> swift_allocObject -----> _swift_allocObject_(HeapObject.cpp) ----> swift_slowAlloc(Heap.cpp) ----> malloc(Heap.cpp)

    Swift对象的内存结构HeapObject(OC objc_object),有2个属性:metadatarefCount,默认占用16字节大小。

    #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__
    };
    

    我们已经分析了实例对象的内存结构,接下来分析HeapMetadta,也就是metadata

    //其实就是TargetHeapMetadata类型,起了个别名
    using HeapMetadata = TargetHeapMetadata<InProcess>;
    

    点击进入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类,传入的参数就是MetadataKind。如果swift与OBJC交互的话,传入的参数就是isa。

    关于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

    此时我们继续进入TargetMetadata探究,其实已经进入了一个瓶颈了。继续查看分析这个结构体

      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;
        }
      }
    

    根据不同的MetadataKind类型来区分不同的数据类型。所以MetadataKind是所有类型元类的最终基类
    const auto cls = static_cast<const TargetClassMetadata<Runtime> *>(this);当我的metadata是class类型的时候,将当前指针强转为TargetClassMetadata类型

    进入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;
    

    进入TargetClassMetadata父类TargetAnyClassMetadata中有以下代码

      TargetSignedPointer<Runtime, const TargetClassMetadata<Runtime> *
                                       __ptrauth_swift_objc_superclass>
      Superclass;
    
      TargetPointer<Runtime, void> CacheData[2];
    
      StoredSize Data;
    

    看了这2个类,参考objc_class我们可以大胆的猜测这就是Swift类的数据结构

    通过源码总结的Swift类的数据结构

    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
    }
    

    通过代码来验证

    struct HeapObject {
        var metadata: UnsafeRawPointer
        var refCount1: UInt32
        var refCount2: UInt32
    }
    
    class LGTeacher {
        var age = 18
        var name = "LG"
    }
    
    var t = LGTeacher()
    
    //将t的指针重新绑定到HeapObject
    //获取实例对象的指针
    var objcRawPtr = Unmanaged.passUnretained(t).toOpaque()
    //重新绑定到HeapObject
    let objcPtr = objcRawPtr.bindMemory(to: HeapObject.self, capacity: 1)
    print(objcPtr.pointee)
    print("end")
    

    执行结果

    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

    那么此时的metadata我们是否也可以还原成Metadata(源码总结的)结构体呢?

    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
    //获取实例对象的指针
    var objcRawPtr = Unmanaged.passUnretained(t).toOpaque()
    //重新绑定到HeapObject
    let objcPtr = objcRawPtr.bindMemory(to: HeapObject.self, capacity: 1)
    print(objcPtr.pointee)
    
    //MemoryLayoutce,Swift中测量数据类型的大小
    let metadataPtr = objcPtr.pointee.metadata.bindMemory(to: Metadata.self, capacity: MemoryLayout<Metadata>.stride)
    print(metadataPtr.pointee)
    
    print("end")
    

    执行结果

    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,实例大小为16(metadata+refCount)+24(age+name(8+8))
    • instanceAlignmentMask: 7,8字节对齐掩码

    至此,通过代码已经证明了MetaData的数据结构

    相关文章

      网友评论

          本文标题:Swift -- 1.类与结构体(上)

          本文链接:https://www.haomeiwen.com/subject/giiyqrtx.html