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


  • 作者: Liwx
  • 邮箱: [email protected]
  • 源码: 需要源码的同学, 可以在评论区留下您的邮箱

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.size)     // 17
print(MemoryLayout.stride)   // 24
print(MemoryLayout.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.stride", MemoryLayout.stride)   // 8, 在64bit环境引用类型占用栈空间内存始终是8
print("MemoryLayout.stride", MemoryLayout.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 - 结构体和类】)