美文网首页Swift底层原理分析
Swift底层原理探索4----结构体和类

Swift底层原理探索4----结构体和类

作者: RUNNING_NIUER | 来源:发表于2020-04-13 15:56 被阅读0次

    结构体

    • Swift标准库中,绝大多数公开类型都是结构体,而枚举和类只占很小的一部分
      比如BoolIntDoubleStringArrayDictionary等常见类型都是结构体
    struct Date {
        var year: Int
        var month: Int
        var day: Int
    }
    var date = Date(year: 2020, month:02, day:29)//编译器为结构体自动生成的初始化器
    
    image
    • 所有的结构体都有一个编译器自动生成的初始化器(initializer初始化方法构造器构造方法
      在第⑥行调用的,可以传入所有成员值,用以初始化所有成员(存储属性Stored Property

    结构体的初始化器

    编译器会根据情况,可能会为结构体生成多个初始化器,宗旨是:保证所有成员有初始值,保证代码的安全,看一下如下几种场景,报错的原因都是因为有成员变量没有赋初始化值

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    下面这样,就没有问题
    struct Point {
        var x: Int = 0
        var y: Int = 0
    }
    var p1 = Point(x: 10, y: 10)
    var p2 = Point(y: 10)
    var p3 = Point(x: 10)
    var p4 = Point()
    

    再看一个场景

    struct Point {
        var x: Int?
        var y: Int?
    }
    var p1 = Point(x: 10, y: 10)
    var p2 = Point(y: 10)
    var p3 = Point(x: 10)
    var p4 = Point()
    

    由于var x: Int? var y: Int?声明之后,默认有初始值nil,因此也不会有上述的报错

    自定义初始化器

    一旦在定义结构体时自定义了初始化器,编译器就不会再帮它自动生成其他初始化器


    image

    窥探初始化器的本质

    下面是两端等效的代码,第一段使用了系统生成的无参初始化起,第二段使用了我们自定义的无参初始化器

    struct Point {
        var x: Int = 0
        var y: Int = 0
    }
    var p = Point()
    
    struct Point {
        var x: Int
        var y: Int
        init() {
            x = 0
            y = 0
        }
    }
    var p = Point()
    

    我们分别来看一下他们的汇编代码

    func testStruct() {
        struct Point {
            var x: Int = 0
            var y: Int = 0
        }
        var p = Point() //在这里加上断点并且运行程序
    }
    
    testStruct()
    
    ***************汇编代码
    SwiftTest`init() in Point #1 in testStruct():
    ->  0x1000010b0 <+0>:  pushq  %rbp
        0x1000010b1 <+1>:  movq   %rsp, %rbp
        0x1000010b4 <+4>:  xorps  %xmm0, %xmm0
        0x1000010b7 <+7>:  movaps %xmm0, -0x10(%rbp)
        0x1000010bb <+11>: movq   $0x0, -0x10(%rbp)
        0x1000010c3 <+19>: movq   $0x0, -0x8(%rbp)
        0x1000010cb <+27>: xorl   %eax, %eax
        0x1000010cd <+29>: movl   %eax, %ecx
        0x1000010cf <+31>: movq   %rcx, %rax
        0x1000010d2 <+34>: movq   %rcx, %rdx
        0x1000010d5 <+37>: popq   %rbp
        0x1000010d6 <+38>: retq 
    
    func testStruct() {
        struct Point {
            var x: Int
            var y: Int
            init() {
                x = 0
                y = 0
            }
        }
        var p = Point()  // 这里加上断点并且运行程序
    }
    
    testStruct()
    
    ***************汇编代码
    SwiftTest`init() in Point #1 in testStruct():
    ->  0x1000010b0 <+0>:  pushq  %rbp
        0x1000010b1 <+1>:  movq   %rsp, %rbp
        0x1000010b4 <+4>:  xorps  %xmm0, %xmm0
        0x1000010b7 <+7>:  movaps %xmm0, -0x10(%rbp)
        0x1000010bb <+11>: movq   $0x0, -0x10(%rbp)
        0x1000010c3 <+19>: movq   $0x0, -0x8(%rbp)
        0x1000010cb <+27>: xorl   %eax, %eax
        0x1000010cd <+29>: movl   %eax, %ecx
        0x1000010cf <+31>: movq   %rcx, %rax
        0x1000010d2 <+34>: movq   %rcx, %rdx
        0x1000010d5 <+37>: popq   %rbp
        0x1000010d6 <+38>: retq   
    

    对比上述两段程序,他们所调用的无参初始化器函数底层的汇编代码是一模一样的,因此判断,是等效的。语法糖或许会骗人,但汇编永远不会骗你。

    结构体内存结构

    struct Point {
        var x: Int = 10
        var y: Int = 20
        var origin: Bool = true
    }
    var p = Point()
    print(MemoryLayout<Point>.size)
    print(MemoryLayout<Point>.stride)
    print(MemoryLayout<Point>.alignment)
    print(Mems.memStr(ofVal: &p)) //输出结构体内存里面的数据
    
    ****************运行结果
    17
    24
    8
    0x000000000000000a 0x0000000000000014 0x0000000000000001
    Program ended with exit code: 0
    

    看得出,Swift的结构体和C语言的结构体内存结构是一样的,成员变量内存都是紧挨在一起的

    类的定义和结构体类似,但编译器并没有为类自动生成可以传入成员值的初始化器

    image
    在类的每个成员都有默认值的情况下,系统则会为它创建一个无参初始化器
    image
    需要注意的是 var x: Int?optional,因此它会自动获得一个默认值nil。相比较于StructClass的声明方式几乎和Struct一模一样,也可以在内部增加方法。从表看上看,只有初始化器又一些不同点。

    类的初始化器

    成员的初始化是在这个初始化器中完成的,以下是两端完全等效的代码(证明方法同上---汇编)

    class Point {
        var x: Int = 10
        var y: Int = 20
    }
    let p = Point()
    
    class Point {
        var x: Int
        var y: Int
        init() {
            x = 10
            y = 20
        }
    }
    let p = Point()
    

    结构体与类的本质区别

    结构体是值类型(枚举也是值类型),类是引用类型(指针类型

    class Size {
        var width = 1
        var height = 2
    }
    
    struct Point {
        var x = 3
        var y = 4
    }
    
    func test() {
        var size = Size()
        var point = Point()
    }
    

    上面代码中的sizepoint在内存中的分布情况如下

    image

    对象的堆空间申请过程

    先看下面的场景

    func testClassAndStruct() {
        class Size {
            var width = 1
            var height = 2
        }
    
        struct Point {
            var x = 3
            var y = 4
        }
    
        var size = Size() //-----这里加上断点并运行,打开汇编界面
        //var point = Point()
    }
    
    testClassAndStruct()
    
    image
    image
    image
    image
    image

    在Swift中,创建类的实例对象,要向堆空间申请内存,大概流程如下

    • Class.__allocating_init()
    • libswiftCore.dylib:_swift_allocObject_
    • libswiftCore.dylib:swift_slowAlloc
    • libsystem_malloc.dylib:malloc
      在Mac、iOS中的malloc函数分配的内存大小总是16的倍数
      通过class_getInstanceSize可以得知类的对象真正使用的内存大小
    class Point {
        var x = 11
        var test = true
        var y = 22
    }
    var p = Point()
    print(class_getInstanceSize(type(of: p)))  //40
    print(class_getInstanceSize(Point.self))   //40
    

    值类型

    • 值类型赋值给varlet或者给函数传参,是直接将所有内容拷贝一份
      类似于对文件进行copypaste操作,产生了全新的文件副本。属于深拷贝(deep copy
      image

    值类型的赋值操作

    • 在Swift标准库中,为了提升性能,StringArrayDictionarySet采取了Copy On Write的技术
      1. 比如仅当有“”操作时,才会真正执行拷贝操作
      2. 对于标准库值类型的赋值操作,Swift能确保最佳性能,所以没有必要为了保证最佳新能来避免赋值

    需要注意的是,上面说的仅仅针对Swift标准库,对于自定义的结构体来说,Swift不会使用Copy On Write技术

    • 建议:不需要修改的,尽量定义成let
    var s1 = "Jack"
    var s2 = s1
    s2.append("_Rose")
    print(s1)//Jack
    print(s2)//Jack_Rose
    
    var a1 = [1,2,3]
    var a2 = a1
    a2.append(4)
    a1[0] = 2
    print(a1)//[1,3,4]
    print(a2)//[1,2,3,4]
    
    var d1 = ["max": 10, "min":2]
    var d2 = d1
    d1["other"] = 7
    d2["max"] = 12
    print(d1)//["other": 7, "max": 10, "min":2]
    print(d2)//["max": 12, "min":2]
    

    引用类型

    • 引用赋值给varlet或者给函数传参,是将内存地址拷贝一份
      类似于制作一个文件的替身(快捷方式,链接),指向的是同一个文件。属于浅拷贝(shallow copy
    class Size {
        var width: Int
        var height: Int
        init(width: Int, height: Int) {
            self.width = width
            self.height = height
        }
    }
    
    func test() {
        var s1 = Size(width: 10, height: 20)
        var s2 = s1
    
        s2.width = 11
        s2.height = 22
    }
    
    test()
    
    image

    引用类型的赋值操作

    class Size {
        var width: Int
        var height: Int
        init(width: Int, height: Int) {
            self.width = width
            self.height = height
        }
    }
    
    var s1 = Size(width: 10, height: 20)
    s1 = Size(width: 11, height: 12)
    
    image

    值类型、引用类型的let

    在这里插入图片描述

    总结一下就是:let的含义就是其修饰的常量所对应的那段内存空间里面的内容不可以修改

    • 值类型由于所有的成员都在其内存里面,因此被let修饰时候,其内部的成员都是不可以修改的
    • 引用类型由于它的内存里存放的只是指针,所以只有这个指针的值不能修改,但是该指针所指向的堆空间的那个对象实例,并不是引用类型内存里面的东西,因此被let修饰之后,仍然可以修改堆空间对象的成员的值。

    嵌套类型

    struct Poker {
        enum Suit: Character {
            case spades = "️", heart = "️", diamonds = "️", clubs = "️"
        }
        enum Rand: Int {
            case two = 2, three, four, five, six, seven, eight, nine, ten
            case jack, queen, king, ace
        }
    }
    print(Poker.Suit.heart.rawValue)
    
    var suit = Poker.Suit.spades
    suit = .diamonds
    
    var rank = Poker.Rand.five
    rank = .king
    

    枚举、结构体、类都可以定义方法

    • 一般把定义在枚举、结构体、类内部的函数,叫做方法
    class Size {
        var width = 10
        var height = 10
        func show() {
            print("width= \(width), height= \(height)")
        }
    }
    let s = Size()
    s.show()
    
    struct Point {
        var x = 10
        var y = 10
        func show() {
            print("x= \(x), y= \(y)")
        }
    }
    let p = Point()
    p.show()
    
    enum PokerFace: Character {
        case spades = "️", heart = "️", diamonds = "️", clubs = "️"
        func show() {
            print("face is \(rawValue)")
        }
    }
    let pf = PokerFace.heart
    pf.show()
    
    • 方法占用内存的对象吗?
      1. 不占用
      2. 方法的本质就是函数
      3. 方法、函数都放在代码段

    相关文章

      网友评论

        本文标题:Swift底层原理探索4----结构体和类

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