一、类与结构体初识
/// 父类
class Person {
}
/// 类
class Teacher: Person {
var name: String
var age: Int
/// 析构方法
deinit {
}
/// 构造方法
init(name: String, age: Int) {
self.name = name
self.age = age
}
/// 方法
func description() {
}
/// 下标
subscript(index: Int) -> Int {
index
}
}
/// 拓展 Equatable 协议
extension Teacher: Equatable {
static func == (lhs: Teacher, rhs: Teacher) -> Bool {
lhs.name == rhs.name && lhs.age == rhs.age
}
}
/// 结构体
struct Student: Equatable {
var name: String
var age: Int
/// 构造方法
init(name: String, age: Int) {
self.name = name
self.age = age
}
/// 下标
subscript(index: Int) -> Int {
index
}
}
/// 拓展方法
extension Student {
/// 方法
func description() {
}
}
相同点:
- 定义存储值的属性
- 定义⽅法
- 定义下标以使⽤下标语法提供对其值的访问
- 定义初始化器
- 使⽤ extension 来拓展功能
- 遵循协议来提供某种功能
不同点:
- 类有继承的特性,⽽结构体没有
- 类型转换使您能够在运⾏时检查和解释类实例的类型
- 类有析构函数⽤来释放其分配的资源
- 引⽤计数允许对⼀个类实例有多个引⽤
二、引用类型与值类型
在使用类与结构体时,我们首先需要注意的是:
类是引用类型,一个类的变量并不直接储存具体的实例对象,而是对存储具体实例内存地址的引用;结构体是值类型。
1. 引用类型
var t1 = Teacher(name: "Tom", age: 3)
var t2 = t1
调试:
这里我们借助两个指令来查看当前变量的内存结构
po : p 和 po 的区别在于使用 po 只会输出对应的值,而 p 则会返回值的类型以及命令结果的引用名。 x/8g: 读取内存中的值(8g: 8字节格式输出)
通过调试可以发现 当 t2.age 被修改时,t1.age 也发生变化。我们可以通过 withUnsafePointer 来查看 t1 和 t2 的内存地址(注意 t1 和 t2 需要使用 var,如果使用 let 则无法使用 withUnsafePointer(to: &t1) { print($0) 来分析)。 t1 和 t2 在内存中的地址是相邻的,中间相差8字节;接下来我们通过x/8g来查看 t1 和 t2 的内存地址的存储信息都是 0x0000000100543620,这正是 Teacher 实例对象的内存地址。
结论: 我们创建一个 t1 来接收 Teacher 类的实例对象,但是 t1 并没有直接存储 Teacher 的实例对象,而是存储对 Teacher 实例对象内存地址的引用;同样的,如果我们将 t1 赋值给其他变量,比如 t2,那么 t1 和 t2 存储的都是 Teacher 实例变量内存地址的引用;任何对于 Teacher 实例的修改都会影响其它的引用方。
2. 值类型
与引用类型的变量中存储的地址相比,值类型存储的就是具体的实例或者说具体的值。
调试:
通过调试可以发现 当 s2.age 被修改时,s1.age 并没有发生变化。通过 po s1/s2 发现它们存储的是 name 和 age 具体的值,并不是内存地址。
结论: 我们创建一个 s1 来接收 Student 类的实例对象,s1 存储的是 Student 的实例对象;如果我们将 s1 赋值给其他变量,比如 s2,那么 s1 和 s2 存储的是独立的实例对象;任何对实例对象 Student 的修改都是独立的,并不会影响到其它的 Student 实例对象。
三、性能对比
这里我们通过运行 StructVsClassPerformance 来做分析。
这里对比的是 1个参数的 class 和 struct 与 10个参数的 class 和 struct 分别创建 1000万次消耗的时间。可以发现 struct 性能明显高于 class,class 比 struct 消耗的时间要多 50% 以上。*
1. 类的内存分布
LLDB 调试插件使用参考1. 内存结构
标识符 t1 在栈上,其存储的是指向堆空间的 Teacher 实例地址。
2. 结构体的内存分布
标识符 s1 在栈上,其存储的 Student 实例也是在栈上。
四、总结
基于 类 和 结构体 的特性差异,在实际开发中应选择合适的类型。结构体因为处在栈上,在内存分配上性能要高于类,但对其实例属性的修改相当于重新拷贝一份新的实例(类似于OC的深拷贝),在某些需要共享一个实例对象的业务场景并不适用。类处在堆上,使用时在遵循业务场景需要的基础上也要考虑性能问题,如果只是作为只读对象,应优先使用结构体。