swift-结构体和类

结构体

在 Swift 标准库中,绝大多数的公开类型都是结构体,而枚举和类只占很小一部分

比如Bool、Int、Double、 String、Array、Dictionary等常见类型都是结构体

struct Date { 
 var year: Int
 var month: Int
 var day: Int
 } 
 var date = Date(year: 2019, month: 6, day: 23) 

所有的结构体都有一个编译器自动生成的初始化器(initializer,初始化方法、构造器、构造方法)

在第⑥行调用的,可以传入所有成员值,用以初始化所有成员(存储属性,Stored Property)。

结构体的初始化器

编译器会根据情况为所有结构体类型自动生成初始化器(initializer),有可能会生成多个初始化器.生成初始化器的原则是:保证所有成员都有值.

image-20210402134927166
image-20210402135005932

比如:

struct TestStruct{
    var a: Int
    var b: Int
}
// 编译器自动生成了传入a,b的初始化器
let s = TestStruct(a: 10, b: 20)

如果结构体的成员有初始值,那么编译器会给结构体生成多个初始化器.

比如只有一个成员有初始值:

struct TestStruct{
    var a: Int = 0
    var b: Int
}

// 生成了两个初始化器
let s = TestStruct(a: 10, b: 20)
let s2 = TestStruct(b: 20)

两个成员都有初始化器:

struct TestStruct{
    var a: Int = 0
    var b: Int = 0
}

//生成4个初始化器
let s = TestStruct(a: 10, b: 20)
let s2 = TestStruct(b: 20)
let s3 = TestStruct(a: 10)
let s4 = TestStruct()

自定义初始化器

我们也可以给结构体自定义初始化器,但是一旦自定义初始化器后,编译器就不会再自动生成初始化器:

img
img

自定义初始化器后,只有自定义的初始化器有效,系统不会再生成初始化器

img

其实我们自定义的初始化器和系统自动生成的初始化器,本质是一模一样的,他们的底层汇编都是一样的.

结构体内存

结构体的内存和有关联值的枚举类型很相似:

func testStruct() {
    struct Point {
        var x = 10
        var y = 20
        var b = true
        
    }
    var p = Point(x: 11, y: 22, b: false)
    p = Point(x: 22)

    print(Mems.memStr(ofVal: &p))
    print(MemoryLayout.size)
    print(MemoryLayout.stride)
    print(MemoryLayout.alignment)
    
}

testStruct()

0x0000000000000016 0x0000000000000014 0x0000000000000001

17

24

8

Class 类

类的定义

类的定义和结构体很相似,但是==如果类的成员没有初始值,编译器不会为类生成初始化器==:

image-20210402140735056
  • 如果类的成员都有初始值,编译器才会为类生成无参初始化器:
  • 成员的初始化是在这个初始化器中完成的
func testClass() {

    class Point {
        var x = 10
        var y = 20
        var b = true
        func test() {
        }
    }
    let p = Point()
    p.test()
}

testClass()

类和结构体的区别

从上面可以看到结构体和类真的是非常的相似,那么结构体和类有什么区别呢?

类型不同,它们的本质区别是:结构体是值类型,类是引用类型

image-20210402141717033
//类
func test(){
class Person{
    var name:String
    var age: Int
    
    init(){
        self.name = "Jack"
        self.age = 18
    }
  }

let person = Person()

//结构体
struct Point {
    let x: Int
    let y: Int
  }
let point = Point(x: 10, y: 20)
}

如上所示的代码,因为point 和 person都是在test()函数内部定义的.所以它们的内存布局如下:

img
解读:变量`point , person`的内存空间都在栈空间.不同的是,`Struct`的数据也存储在变量的内存中.而`Class`类型的变量内存中存储的是一个地址,一个指向堆空间类对象的内存地址.`Class`类型的数据都存储在堆空间中.

let point = Point(x: 10, y: 20)打断点,可以看到Point的初始化代码的汇编简洁如下,根本就没有调用malloc代码:

image-20210402142418164

我们可以打个断点,看看point 变量和 person 变量的内存是不是如上图分析的那样,要搞清楚变量有没有分配堆空间内存,一个很重要的指标就是是否调用了malloc方法,因为这个方法是向堆空间申请内存的.我们在let person = Person()打上断点,然后一步步执行,会发现最终会调用malloc方法:

image-20210402143725775

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

Class.__allocating_init()

libswiftCore.dylib****:****swift_allocObject

libswiftCore.dylib****:****swift_slowAlloc

libsystem_malloc.dylib****:****malloc

**Class.__allocating_init()**

**libswiftCore.dylib****:****_swift_allocObject_**

**libswiftCore.dylib****:****swift_slowAlloc**

**libsystem_malloc.dylib****:****malloc**

直接打印出point变量和person变量的内容,更直观一些:

var point = Point(x: 10, y: 20)
var person = Person()
    
    print(Mems.ptr(ofVal:&person))
    print(Mems.memStr(ofVal:&person))
    print("------")
    print(Mems.ptr(ofVal:&point))
    print(Mems.memStr(ofVal:&point))
image-20210402144502149

如果想查看一个类型所创建出来的变量占用多少内存,可以使用MemoryLayout<类型>.stride查看,比如:
查看String类型对象占用多少内存:MemoryLayout.stride
查看Int类型对象占用多少内存:MemoryLayout.stride

如果想查看创建出来的指针变量在堆空间占用多少内存,可以使用malloc_size(ptr: UnsafeRawPointer)

    var ptr = malloc(16)
    print(malloc_size(ptr))//16
    var ptr2 = malloc(17)
    print(malloc_size(ptr2))//32
image-20210402145530486

mac iOS中的malloc函数分配的内存大小总是16的倍数

赋值操作不同,值类型是深拷贝,引用类型是浅拷贝

值类型的深度拷贝
image-20210402150031023

看看struct类型赋值操作的汇编语言:

func testValueType() {
    struct Point {
        var x: Int
        var y: Int
    }
    let p1 = Point(x: 10, y: 20)
    var p2 = p1
    p2.x = 11
    p2.y = 21
    print("123")
}
testValueType()
  0x100002653 <+19>:  movl   $0xa, %edi
    0x100002658 <+24>:  movl   $0x14, %esi
    0x10000265d <+29>:  callq  0x100002730               ; init(x: Swift.Int, y: Swift.Int) -> Point #1 in TestSwift.testValueType() -> () in Point #1 in TestSwift.testValueType() -> () at main.swift:11
    0x100002662 <+34>:  movq   %rax, -0x10(%rbp)
    0x100002666 <+38>:  movq   %rdx, -0x8(%rbp)
->  0x10000266a <+42>:  movq   %rax, -0x20(%rbp)
    0x10000266e <+46>:  movq   %rdx, -0x18(%rbp)
    0x100002672 <+50>:  movq   $0xb, -0x20(%rbp)
    0x10000267a <+58>:  movq   $0x15, -0x18(%rbp)
    0x100002682 <+66>:  movq   0x5a97(%rip), %rax        ; (void *)0x00007fff862faa70: type metadata for Any
    0x100002689 <+73>:  addq   $0x8, %rax
1

第一步:进入Point 的 init() 函数

2

第二步:Point 的 init() 函数 函数内部

img

第三步:深拷贝

在Swift标准库中,为了提升性能,String、Array、Dictionary、Set采取了Copy On Write的技术

比如仅当有“写”操作时,才会真正执行拷贝操作

对于标准库值类型的赋值操作,Swift 能确保最佳性能,所有没必要为了保证最佳性能来避免赋值

引用类型的浅拷贝

引用赋值给var、let或者给函数传参,是将内存地址拷贝一份

类似于制作一个文件的替身(快捷方式、链接),指向的是同一个文件。属于浅拷贝(shallow copy)

image-20210402152432157
func testReferenceType() {
    class Size {
        var width: Int
        var height: Int
        init(width: Int, height: Int) {
            self.width = width
            self.height = height
        }
    }
    
    let s1 = Size(width: 10, height: 20)
    let s2 = s1
    s2.width = 11
    s2.height = 22
    print()
}
testReferenceType()
image-20210402153859366
0x1000022e1 <+1>:   movq   %rsp, %rbp
    0x1000022e4 <+4>:   pushq  %r13
    0x1000022e6 <+6>:   subq   $0xa8, %rsp
    0x1000022ed <+13>:  movq   $0x0, -0x10(%rbp)
    0x1000022f5 <+21>:  movq   $0x0, -0x18(%rbp)
    0x1000022fd <+29>:  xorl   %eax, %eax
    0x1000022ff <+31>:  movl   %eax, %ecx
    0x100002301 <+33>:  movq   %rcx, %rdi
    0x100002304 <+36>:  movq   %rcx, -0x50(%rbp)
    0x100002308 <+40>:  callq  0x100002470               ; type metadata accessor for Size #1 in TestSwift.testReferenceType() -> () at 
    0x10000230d <+45>:  movl   $0xa, %edi
    0x100002312 <+50>:  movl   $0x14, %esi
    0x100002317 <+55>:  movq   %rax, %r13
    0x10000231a <+58>:  movq   %rdx, -0x58(%rbp)
    0x10000231e <+62>:  callq  0x100002490               ; __allocating_init(width: Swift.Int, height: Swift.Int) -> Size #1 in TestSwift.testReferenceType() -> () in Size #1 in TestSwift.testReferenceType() -> () at main.swift:14
    0x100002323 <+67>:  movq   %rax, -0x10(%rbp)
->  0x100002327 <+71>:  movq   %rax, %rdi
    0x10000232a <+74>:  movq   %rax, -0x60(%rbp)
    0x10000232e <+78>:  callq  0x1000072a6               ; symbol stub for: swift_retain
    0x100002333 <+83>:  movq   -0x60(%rbp), %rcx
    0x100002337 <+87>:  movq   %rcx, -0x18(%rbp)
    0x10000233b <+91>:  addq   $0x10, %rcx
    0x10000233f <+95>:  leaq   -0x30(%rbp), %rdx
    0x100002343 <+99>:  movl   $0x21, %esi
    0x100002348 <+104>: movq   %rcx, %rdi
    0x10000234b <+107>: movq   %rsi, -0x68(%rbp)
    0x10000234f <+111>: movq   %rdx, %rsi
    0x100002352 <+114>: movq   -0x68(%rbp), %rcx
    0x100002356 <+118>: movq   %rdx, -0x70(%rbp)
    0x10000235a <+122>: movq   %rcx, %rdx
    0x10000235d <+125>: movq   -0x50(%rbp), %rcx
    0x100002361 <+129>: movq   %rax, -0x78(%rbp)
image-20210402154648926
callq  0x100002490               ; __allocating_init(width: Swift.Int, height: Swift.Int) -> Size #1 in TestSwift.testReferenceType() -> () in Size #1 in TestSwift.testReferenceType() -> () at main.swift:14
movq   %rax, -0x10(%rbp)

函数callq后面执行的汇编指令为movq %rax, -0x10(%rbp) rax、rdx常作为函数返回值使用,所以-0x10(%rbp)将存放s1的地址值。

我们现在读取一下rax寄存器的值。register read rax 通过rax存储的内存值,窥探一下内存

image-20210402155126566
image-20210402155648867
image-20210402160853050

验证是操作s1的内存$0xb, 0x10(%rax)

image-20210402161002453

可以看到读出来的rax储存的是s1的地址,同样是s2的地址

movq $0xb, 0x10(%rax)

image-20210402161450695
image-20210402161923901
image-20210402162107268

通过上面汇编代码和内存地址值剖析,s1,s2同时指向堆中同一块内存区域。在s2中修改width的值,s1的类成员变量同样被修改了。

你可能感兴趣的:(swift-结构体和类)