1 Swift类与结构体
相同
- 定义属性用于存储值
- 定义方法用于提供功能
- 定义下标操作用于通过下标语法访问它们的值
- 定义构造器用于设置初始值
- 通过扩展以增加默认实现之外的功能
- 遵循协议以提供某种标准功能
区分
- 类有继承的特性,而结构体没有
- 类型转换使您能够在运行时检查和解释类实例的类型 类有析构函数用来释放其分配的资源
- 引用计数允许对一个类实例有多个引用
类与结构体本质区分:
类是引用类型。也就意味着一个类类型的变量并不直接存储具体的实例对象,是对当前存储具体实例内存地址的引用。结构体是值类型,相比较引用类型的变量中存储的是地址,那么值类型存储的就是具体的实例。
其实引用类型就相当于在线的 Excel ,当我们把这个链接共享给别人的时候,别人的修改我们 是能够看到的;值类型就相当于本地的 Excel ,当我们把本地的 Excel 传递给别人的时候,就相当于重新复制了一份给别人,至于他们对于内容的修改我们是无法感知的。
另外引用类型和值类型还有一个最直观的区别就是存储的位置不同:一般情况,值类型存储的在栈上,引用类型存储在堆上。
实际项目中运用类和结构体需要注意的事项:
由于二者在内存位置上的存储位置的不同,决定了二者运行速度的不同,栈(Stack)速度 > 堆(Heap)速度。进一步讲就是我们在优化或是设计程序时尽量用结构体而非类。
初始化器
初始化器是为了可以完整地初始化实例,所以在初始化器中必须完成对存储属性的赋值
默认初始化器
- 编译器不会自动提供类的初始化器,需要自己提供一个指定初始化器
- 结构体来说编译器会提供默认的初始化方法(前提是我们自己没有指定初始化器)!
可失败初始化器
- 当前因为参数的不合法或者外部条件的不满足,存在初始化失败的情况。
必要初始化器
- 在类的初始化器前添加 required 修饰符来表明所有该类的子类都必须实现该初始化器
2 类的生命周期
iOS开发的语言不管是OC还是Swift后端都是通过LLVM进行编译的,如下图所示:
- OC 通过 clang 编译器,编译成 IR,然后再生成可执行文件 .o(这里也就是我们的机器码)
- Swift 则是通过 Swift 编译器编译成 IR,然后在生成可执行文件 如下图:
我们接下来看下前端编译器swift的命令,类似我们oc中clang的命令语句
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
SIL文件分析
根据上面的指令我们生成main的sil文件,我们新建一个工程,在main定义一个Person类 包含一个name属性和一个age属性
在终端输入下面的代码会生成sil文件,并在终端打印生成的main.sil文件
swiftc main.swift -emit-sil
上面的%0,%1等是虚拟的寄存器。在SIL文件中,你会看到很多不懂的关键词,你可以查看GitHub上的官方文档查阅
- @main代表入口函数
- %0 1 2...: 寄存器,这里是虚拟的,跑到设备上会使用真的寄存器
- alloc_global @$s4main1tAA9LGTeacherCvp:分配一个全局变量,该全局变量的名称是混写的,我们可以通过终端指令xcrun swift-demangle将其还原:
- %3 = global_addr @$s4main1tAA9LGTeacherCvp:拿到该全局变量的地址给%3寄存器
- %4 = metatype $@thick LGTeacher.Type:获取LGTeacher.Type的元类型给%4寄存器
- %5 = function_ref @$s4main9LGTeacherCACycfC : %5是LGTeacher.__allocating_init()函数的引用,也就是拿到LGTeacher.__allocating_init()函数的指针地址
- %6 = apply %5(%4):使用%5函数并且传入参数%4,把结果返回值(也就是实例变量)给到%6
- store %6 to %3:将%6也就是实例变量的内存地址,存放到%3这个全局变量中
- Int在Swift底层中t就是一个Struct类型,%8和%9是在构建一个Int32的整数类型0,所以也就是return 0,类似与OC中main函数最终的return 0
3 类的初始化流程
- @main代表入口函数首先调用__allocating_init()函数,它是用来创建实例对象的,所以需要看到函数执行的过程,那么在SIL文件中搜索s4main9LGTeacherCACycfC,看到下面的代码。这个函数需要一个元数据类型,这里是LGTeacher.Type,你可以直接把这个元数据类型先理解成isa指针。
- 看这个alloc_ref是啥呢,alloc_ref表示创建一个T的实例对象,并且引用计数加1,所以alloc_ref实际上是去堆区申请内存空间。
- 汇编模式查看下__allocating_init方法,它在具体做什么呢,我们点击跳进去,里面有两个方法,swift_allocObject在堆区找合适的内存空间,init()在初始化所有的成员变量。
- 那么如果让LGTeacher继承自NSObject,又会有什么变化呢,运行后就可以看到LGTeacher.__allocating_init里面调用的就是OC的初始化方法objc_allocWithZone 和 init 两个方法了。
class LGTeacher:NSObject {
var age: Int = 18
var name: String = "asd"
}
swift_allocObject 接下来我们看下这个函数到底干了什么
- 打开swift源码然后找到HeapObjec.cpp文件,在里面找到了_swift_allocObject_ 方法并且看到这里有三个参数,没错是三个参数 一个metadata(元数据类型),requiredSize(所需要的大小),requireAlignmentMask(所需要对齐的掩码 它的值为7).
- 然后调用swift_slowAlloc方法,并将requiredSize和requiredAlignmentMask作为参数传了进去,然后swift_slowAlloc方法返回了一个HeapObject类型的指针,malloc函数 也就是分配内存空间的。
总结下Swift对象进行内存分配的流程
分析HeapObject结构体
-
里面包含HeapMetadata,占用8字节内存大小
-
HeapMetadata是TargetHeapMetadata这个类型的别名定义,所以接下去需要看TargetHeapMetadata。
-
我们知道在objc中类的结构是objc_class 它的结构如下
那么swift中类的结构是什么呢 如下
从上面不难看出 superClass cacheData这些关键词都是一样的
4 总结
- swift中的类和struct最主要的区别在于类是一个引用类型,而结构体是值类型。当不涉及继承关系时,我们用来存贮值的时候我们可以优先考虑结构体来存储。
- swift中的类本质是heapObject类型的结构体包含metadata和引用计数,我么oc中的类本质是objc_class 类型的结构体。
- swift中实例对象存贮的是metadata和引用计数,成员变量。oc中实例变量存储的是isa和成员变量
- swift中metadata中包含类的信息通过getTypeContextDescriptor获取描述的类型把数据存在对应的结构体中。