美文网首页iOS Swift5 语法
Swift语法 Swift5 【06 - 结构体和类】

Swift语法 Swift5 【06 - 结构体和类】

作者: Liwx | 来源:发表于2020-05-08 17:18 被阅读0次

    • 作者: Liwx
    • 邮箱: 1032282633@qq.com
    • 源码: 需要源码的同学, 可以在评论区留下您的邮箱

    iOS Swift 语法 底层原理内存管理分析 专题:【iOS Swift5语法】

    00 - 汇编
    01 - 基础语法
    02 - 流程控制
    03 - 函数
    04 - 枚举
    05 - 可选项
    06 - 结构体和类
    07 - 闭包
    08 - 属性
    09 - 方法
    10 - 下标
    11 - 继承
    12 - 初始化器init
    13 - 可选项


    目录

    • 01-结构体
    • 02-结构体的初始化器
    • 03-思考下面代码能通过么?
    • 04-自定义初始化器
    • 05-窥探初始化器的本质
    • 06-结构体内存结构
    • 07-类
    • 08-类的初始化器
    • 09-结构体与类的本质区别
    • 10-值类型
    • 11-值类型的赋值操作
    • 12-引用类型
    • 13-对象的堆空间申请过程
    • 14-引用类型的赋值操作
    • 15-值类型、引用类型的let
    • 16-嵌套类型
    • 17-枚举、结构体、类都可以定义方法

    01-结构体

    • 在Swift标准库中,绝大多数的公开类型都是结构体,而枚举只占很小一部分
    • 比如Bool、Int、Double、String、Array、Dictionary等常见类型都是结构体
    • 所有的结构体都有一个编译器自动生成的初始化器(initializer, 初始化方法,构造器,构造方法)
    • 调用构造方法时,可以传入所有成员值,用以初始化所有成员(存储属性, Stored Property)
    struct Date {
        var year: Int
        var month: Int
        var day: Int
    }
    var date = Date(year: 2019, month: 6, day: 1)
    

    02-结构体的初始化器

    • 编译器会根据情况,可能会为结构体生成多个初始化器,宗旨是: 保证所有成员都有初始值

    • 示例1

    // 结构体所有存储属性都没有设置初始值
    struct Point {
        var x: Int
        var y: Int
    }
    
    var p1 = Point(x: 10, y: 10)
    //var p2 = Point(y: 10)   // 报错: missing argument for parameter 'x' in call
    //var p3 = Point(x: 10)   // 报错: missing argument for parameter 'y' in call
    //var p4 = Point()        // 报错: missing arguments for parameters 'x', 'y' in call
    
    • 示例2
    // 结构体部分存储属性没有设置初始值
    struct Point {
        var x: Int = 0
        var y: Int
    }
    var p1 = Point(x: 10, y: 10)
    var p2 = Point(y: 10)
    //var p3 = Point(x: 10)   // 报错: missing argument for parameter 'y' in call
    //var p4 = Point()        // 报错: missing argument for parameter 'y' in call
    
    • 示例3
    // 结构体部分存储属性没有设置初始值
    struct Point {
        var x: Int
        var y: Int = 0
    }
    var p1 = Point(x: 10, y: 10)
    //var p2 = Point(y: 10)   // 报错: missing argument for parameter 'x' in call
    var p3 = Point(x: 10)
    //var p4 = Point()        // 报错: missing argument for parameter 'x' in call
    
    • 示例4
    // 结构体所有存储属性都有设置初始值
    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()
    

    03-思考下面代码能通过么?

    • 可选项都有个默认值nil
      • 因此可以编译通过
    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()
    

    04-自定义初始化器

    • 一旦在定义结构体时自定义了初始化器,编译器就不会再帮它自动生成其他初始化器
    struct Point {
        var x: Int = 0
        var y: Int = 0
        init(x: Int, y: Int) {
            self.x = x
            self.y = y
        }
    }
    var p1 = Point(x: 10, y: 10)
    //var p2 = Point(y: 10)   // error: missing argument for parameter 'x' in call
    //var p3 = Point(x: 10)   // error: missing argument for parameter 'y' in call
    //var p4 = Point()        // error: missing arguments for parameters 'x', 'y' in call
    

    05-窥探初始化器的本质

    • 以下2段代码完全等效
    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()
    
    • 通过汇编代码对比,上面两段代码的汇编代码一模一样
    06-结构体和类`Point.init():
    ->  0x100000bf0 <+0>:  pushq  %rbp
        0x100000bf1 <+1>:  movq   %rsp, %rbp
        0x100000bf4 <+4>:  xorps  %xmm0, %xmm0
        0x100000bf7 <+7>:  movaps %xmm0, -0x10(%rbp)
        0x100000bfb <+11>: movq   $0x0, -0x10(%rbp)
        0x100000c03 <+19>: movq   $0x0, -0x8(%rbp)
        0x100000c0b <+27>: xorl   %eax, %eax
        0x100000c0d <+29>: movl   %eax, %ecx
        0x100000c0f <+31>: movq   %rcx, %rax
        0x100000c12 <+34>: movq   %rcx, %rdx
        0x100000c15 <+37>: popq   %rbp
        0x100000c16 <+38>: retq
    

    06-结构体内存结构

    struct Point {
        var x: Int = 0
        var y: Int = 0
        var origin: Bool = false
    }
    
    print(MemoryLayout<Point>.size)     // 17
    print(MemoryLayout<Point>.stride)   // 24
    print(MemoryLayout<Point>.alignment)// 8
    
    
    var p1 = Point(x: 10, y: 20, origin: true)
    print(Mems.ptr(ofVal: &p1))         // 查看地址0x000000010000a6e8
    // 0A 00 00 00 00 00 00 00
    // 14 00 00 00 00 00 00 00
    // 01 00 00 00 00 00 00 00
    
    print(Mems.memStr(ofVal: &p1))      // 查看地址里面的内容 0x000000000000000a 0x0000000000000014 0x0000000000000001
    
    image.png

    07-类

    • 类的定义和结构体类似,但编译器并没有为类自动生成可以传入成员值的初始化器
    // 结构体
    struct Point {
        var x: Int = 0
        var y: Int = 0
    }
    
    let p1 = Point()
    let p2 = Point(x: 10, y: 10)
    let p3 = Point(y: 10)
    let p4 = Point(x: 10)
    
    // 类
    class Point {
        var x: Int = 0
        var y: Int = 0
    }
    
    let p1 = Point()
    //let p2 = Point(x: 10, y: 10)    // error: argument passed to call that takes no arguments
    //let p3 = Point(y: 10)   // error: argument passed to call that takes no arguments
    //let p4 = Point(x: 10)   // error: argument passed to call that takes no arguments
    

    • 如果类成员未指定初始值,编译器不会自动生成无参的初始化器
    class Point {       // error: class 'Point' has no initializers
        var x: Int
        var y: Int
    }
    let p1 = Point()    // error: 'Point' cannot be constructed because it has no accessible initializers
    

    08-类的初始化器

    • 如果类的所有成员都在定义的时候指定了初始值,编译器会为类生成无参的初始化器
      • 成员的初始化是在这个初始化器中完成的

    • 以下2段代码完全等效
    class Point {
        var x: Int = 10
        var y: Int = 20
    }
    let p1 = Point()
    
    class Point {
        var x: Int
        var y: Int
        init() {
            x = 10
            y = 20
        }
    }
    let p1 = Point()
    

    据汇编观察, 2段代码的汇编代码一模一样

    06-结构体和类`Point.init():
        0x1000019e0 <+0>:   pushq  %rbp
        0x1000019e1 <+1>:   movq   %rsp, %rbp
        0x1000019e4 <+4>:   subq   $0x60, %rsp
        0x1000019e8 <+8>:   movq   $0x0, -0x8(%rbp)
        0x1000019f0 <+16>:  movq   %r13, -0x8(%rbp)
    ->  0x1000019f4 <+20>:  movq   %r13, %rax
        0x1000019f7 <+23>:  addq   $0x10, %rax
        0x1000019fb <+27>:  xorl   %ecx, %ecx
        0x1000019fd <+29>:  movl   %ecx, %edx
        0x1000019ff <+31>:  leaq   -0x20(%rbp), %rsi
        0x100001a03 <+35>:  movl   $0x21, %edi
        0x100001a08 <+40>:  movq   %rdi, -0x40(%rbp)
        0x100001a0c <+44>:  movq   %rax, %rdi
        0x100001a0f <+47>:  movq   %rsi, -0x48(%rbp)
        0x100001a13 <+51>:  movq   -0x40(%rbp), %rax
        0x100001a17 <+55>:  movq   %rdx, -0x50(%rbp)
        0x100001a1b <+59>:  movq   %rax, %rdx
        0x100001a1e <+62>:  movq   -0x50(%rbp), %rcx
        0x100001a22 <+66>:  movq   %r13, -0x58(%rbp)
        0x100001a26 <+70>:  callq  0x100005436               ; symbol stub for: swift_beginAccess
        0x100001a2b <+75>:  movq   -0x58(%rbp), %rax
        0x100001a2f <+79>:  movq   $0xa, 0x10(%rax)      ; 属性x 赋值 10
        0x100001a37 <+87>:  movq   -0x48(%rbp), %rdi
        0x100001a3b <+91>:  callq  0x100005454               ; symbol stub for: swift_endAccess
        0x100001a40 <+96>:  movq   -0x58(%rbp), %rax
        0x100001a44 <+100>: addq   $0x18, %rax
        0x100001a48 <+104>: leaq   -0x38(%rbp), %rcx
        0x100001a4c <+108>: movq   %rax, %rdi
        0x100001a4f <+111>: movq   %rcx, %rsi
        0x100001a52 <+114>: movq   -0x40(%rbp), %rdx
        0x100001a56 <+118>: movq   -0x50(%rbp), %rax
        0x100001a5a <+122>: movq   %rcx, -0x60(%rbp)
        0x100001a5e <+126>: movq   %rax, %rcx
        0x100001a61 <+129>: callq  0x100005436               ; symbol stub for: swift_beginAccess
        0x100001a66 <+134>: movq   -0x58(%rbp), %rax
        0x100001a6a <+138>: movq   $0x14, 0x18(%rax)    ; 属性y 赋值 20
        0x100001a72 <+146>: movq   -0x60(%rbp), %rdi
        0x100001a76 <+150>: callq  0x100005454               ; symbol stub for: swift_endAccess
        0x100001a7b <+155>: movq   -0x58(%rbp), %rax
        0x100001a7f <+159>: addq   $0x60, %rsp
        0x100001a83 <+163>: popq   %rbp
        0x100001a84 <+164>: retq 
    

    09-结构体与类的本质区别

    • 结构体值类型(枚举也是值类型), 引用类型(指针类型)
    class Size {
        var width = 1
        var height = 2
    }
    
    struct Point {
        var x = 3
        var y = 4
    }
    
    func test() {
        var size = Size()   // size是指针变量,占用栈空间8个字节
        var point = Point() // point是结构体变量,占用栈空间16个字节
    }
    
    • 下图为64bit环境内存布局
    QQ20200422-093229.png
    • 判断是否在堆空间,查看汇编是否有调用alloc或者malloc
    class Size {
        var width = 1
        var height = 2
    }
    struct Point {
        var x = 3
        var y = 4
    }
    var size = Size()          // __allocating_init, swift_allocObject, swift_slowAlloc, malloc
    var point = Point()     // 查看汇编代码未调用alloc或malloc
    print(Mems.size(ofRef: size))   // 32, Mems.size(value)引用类型变量value占用的堆空间大小
    print("MemoryLayout<Size>.stride", MemoryLayout<Size>.stride)   // 8, 在64bit环境引用类型占用栈空间内存始终是8
    print("MemoryLayout<Point>.stride", MemoryLayout<Point>.stride) // 16
    // size和point地址是栈空间,
    print("size变量的地址", Mems.ptr(ofVal: &size))      // size变量的地址 0x00007ffeefbff040
    print("size变量的内容", Mems.memStr(ofVal: &size))   // size变量的内容 0x000000010076ee50
    print("size所指向内存的地址", Mems.ptr(ofRef: size))  // size所指向内存的地址 0x000000010076ee50
    print("size所指向内存的内容", Mems.memStr(ofRef: size))// size所指向内存的内容 0x000000010000a500 0x0000000200000002 0x0000000000000001 0x0000000000000002
    print("point变量的地址", Mems.ptr(ofVal: &point))    // point变量的地址 0x00007ffeefbff030
    print("point变量的内存", Mems.memStr(ofVal: &point)) // point变量的内存 0x0000000000000003 0x0000000000000004
    

    • 在Mac、iOS中的malloc函数分配的内存大小总是16的倍数
    import Foundation
    
    var ptr1 = malloc(16)       // malloc函数在Foundation框架中,需import Foundation
    print(malloc_size(ptr1))    // 16
    var ptr2 = malloc(1)
    print(malloc_size(ptr2))    // 16
    var ptr3 = malloc(17)
    print(malloc_size(ptr3))    // 32
    

    10-值类型

    • 值类型赋值给var、let或者给函数传参,是直接将所有内容拷贝一份
      • 类似于对文件进行copy、paste操作, 产生了全新的文件副本.属于深拷贝(deep copy)
    func testValueType() {
        struct Point {
            var x: Int
            var y: Int
        }
        
        var p1 = Point(x: 10, y: 20)
        var p2 = p1
        
        p2.x = 11
        p2.y = 22
        print(p2.x , p2.y)
    }
    testValueType()
    
    • 值类型深拷贝


      QQ20200422-101724.png
    • 汇编分析, p1先拷贝1份给p2,之后再对p2进行赋值操作

    image.png

    11-值类型的赋值操作

    • Swift标准库中, 为了能提升性能, String、Array、Dictionary、Set采取了Copy On Write的技术
      • 比如仅当有"写"操作时,才会真正执行拷贝操作
      • 对于标准库值类型的赋值操作, Swift能确保最佳性能,所以没必要为了保证最佳性能来避免赋值
    • 建议: 不需要修改的,尽量定义成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)   // [2, 2, 3]
    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]
    

    • 结构体赋值内存分析
    struct Point {
        var x: Int
        var y: Int
    }
    
    var p1 = Point(x: 10, y: 20)
    print(Mems.ptr(ofVal: &p1))     // 0x0000000107647780
    print(Mems.memStr(ofVal: &p1))  // 0x000000000000000a 0x0000000000000014
    
    p1 = Point(x: 11, y: 22)
    print(Mems.ptr(ofVal: &p1))     // 0x0000000107647780
    print(Mems.memStr(ofVal: &p1))  // 0x000000000000000b 0x0000000000000016
    
    image.png

    12-引用类型

    • 引用赋值给var、let或者给函数传参,是将内存地址拷贝一份
      • 类似于指针一个文件的替身(快捷方式、链接), 指向的是同一个文件,属于浅拷贝(shallow copy)
    func testReferenceType() {
        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)
        var s2 = s1
        s2.width = 11
        s2.height = 22
        print(s1.width, s1.height)  // 11 22
        print(s2.width, s2.height)  // 11 22
    }
    testReferenceType()
    
    image.png QQ20200422-111917.png

    13-对象的堆空间申请过程

    • 在Swift中,创建类的实例对象,要向堆空间申请内存,大概流程如下
      • Class.__allocating_init()
      • libswiftCore.dylib: swift_allocObject
      • libswiftCode.dylib: swift_slowAlloc
      • libsystem_malloc.dylib: malloc
    • 在Mac、iOS中的malloc函数分配的内存大小总是16的倍数
    • 通过class_getInstanceSize 可以得知: 类的对象至少需要占用多少内存
    import Foundation
    
    class Point {
        // 指向类型信息 8
        // 引用计数 8
        var x = 11      // 8
        var test = true // 1
        var y = 22      // 8
    } // 33, 40, 48
    var p = Point() // malloc函数分配的内存大小总是16的倍数, 所以堆空间分配48个字节
    print(class_getInstanceSize(type(of: p)))   // 40
    print(class_getInstanceSize(Point.self))    // 40 Point.self 相当于 [Point class] [p class]
    print(Mems.size(ofRef: p))      // 48, 堆空间分配48个字节
    

    14-引用类型的赋值操作

    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)
    print(Mems.ptr(ofVal: &s1))     // 0x0000000111fdca40
    print(Mems.ptr(ofRef: s1))      // 0x000060000047d140
    print(Mems.memStr(ofRef: s1))   // 0x0000000111fdc568 0x0000000200000002 0x000000000000000a 0x0000000000000014
    s1 = Size(width:11, height: 22)
    print(Mems.ptr(ofVal: &s1))     // 0x0000000111fdca40
    print(Mems.ptr(ofRef: s1))      // 0x0000600000422e00
    print(Mems.memStr(ofRef: s1))   // 0x0000000111fdc568 0x0000000400000002 0x000000000000000b 0x0000000000000016
    
    image.png

    15-值类型、引用类型的let

    • 值类型定义为let的实例, 不能修改值类型实例的成员属性
    • 引用类型定义为let的实例, 可以修改引用类型实例的成员属性
    struct Point {
        var x: Int
        var y: Int
    }
    class Size {
        var width: Int
        var height: Int
        init(width: Int, height: Int) {
            self.width = width
            self.height = height
        }
    }
    let p = Point(x: 10, y: 20)         // let代表p变量的内存不可修改, p结构体变量占用16个字节
    //p = Point(x: 11, y: 22)           // error: cannot assign to value: 'p' is a 'let' constant
    //p.x = 33                          // error: cannot assign to property: 'p' is a 'let' constant
    //p.y = 44                          // error: cannot assign to property: 'p' is a 'let' constant
    let s = Size(width: 10, height: 20) // let代表s变量的内存不可修改 s指针变量占用8个字节
    //s = Size(width: 11, height: 22)   // error: cannot assign to value: 's' is a 'let' constant
    s.width = 33
    s.height = 44
    

    • let修饰的字符串,不能对数组进行增删改操作
    // let 修饰的字符串 不允许使用append等赋值操作
    let str = "Jack"
    // str.append("_Rose")               // error: cannot use mutating member on immutable value
    print(str)
    
    • let修饰的数组,不能对数组进行增删改操作
    // let修饰的数组,不能对数组进行增删改操作
    let arr = [1, 2, 3]
    // arr[0] = 1              // error: cannot assign through subscript: 'arr' is a 'let' constant
    // arr.append(4)           // error: cannot use mutating member on immutable value
    print(arr)
    

    16-嵌套类型

    • 嵌套类型的简单使用
    struct Poker {
        enum Suit : Character {
            case spades = "♠️"
            case hearts = "♥️"
            case diamonds = "♦️"
            case clubs = "♣️"
        }
    
        enum Rank : Int {
            case two = 2, three, four, five, six, seven, eight, nine, TernaryPrecedence
            case jack, queen, king, ace
        }
    }
    
    // 获取嵌套类型的原始值
    print(Poker.Suit.hearts.rawValue)   // ♥️
    
    var suit = Poker.Suit.spades       
    suit = .diamonds
    
    var rank = Poker.Rank.five
    rank = .king
    

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

    • 一般把定义在枚举、结构体、类内部的函数,叫做方法
    • 方法占用对象的内存吗?
      • 不占用
      • 方法的本质就是函数
      • 方法、函数都存放在代码段

    • 类定义方法
    class Size {
        var width = 10
        var height = 10
        func show() {
            print("width = \(width), height = \(height)")
        }
    }
    let s = Size()
    s.show()    // width = 10, height = 10
    
    • 结构体定义方法
    struct Point {
        var x = 10
        var y = 10
        func show() {
            print("x = \(x), y = \(y)")
        }
    }
    let p = Point()
    p.show()    // x = 10, y = 10
    
    • 枚举定义方法
    enum Poker : Character {
        case spades = "♠️"
        case hearts = "♥️"
        case diamonds = "♦️"
        case clubs = "♣️"
        func show() {
            print("face is \(rawValue)")
        }
    }
    let pf = Poker.hearts
    pf.show()   // face is ♥️
    

    • 汇编分析方法、函数、全局变量、堆空间、局部变量(栈空间)内存分布
    func show1() {
        print("show1")
    }
    
    class Point {
        var x = 11
        var y = 22
        func show() {
            var a = 10
            print("局部变量(栈空间)", Mems.ptr(ofVal: &a))
            print(x, y)
        }
    }
    
    var p = Point()
    p.show()
    show1()
    
    print("全局变量", Mems.ptr(ofVal: &p))
    print("堆空间", Mems.ptr(ofRef: p))
    
    • 方法、函数、全局变量、堆空间、局部变量(栈空间)内存区域分布分析
     Point.show:    0x100001740        (代码区)
     show1:         0x100001290        (代码区)
     
     全局变量        0x100007398        (全局区)
     堆空间          0x10053e5d0        (堆空间)
     局部变量(栈空间) 0x7ffeefbff3e8      (栈空间)
    

    iOS Swift 语法 底层原理内存管理分析 专题:【iOS Swift5语法】

    下一篇: 07 - 闭包
    上一篇: 05 - 可选项


    相关文章

      网友评论

        本文标题:Swift语法 Swift5 【06 - 结构体和类】

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