一、首先我们直接来说Swift里类与结构体的关系
主要相同点:
- 定义存储值的属性
- 定义方法
- 定义下标以使用下标语法提供对其值的访问
- 定义初始化器
- 使用 extension 来拓展功能
- 遵循协议来提供某种功能
主要不同点:
- 有继承的特性,而结构体没有
- 类型转换使您能够在运行时检查和解释类实例的类型
- 类有析构函数用来释放其分配的资源
- 引用计数允许对一个类实例有多个引用
对于类和结构体来说,我们首先需要区分的就是: 类是引用类型,而结构体是值类型
类引用参考
结构体值参考
这里我们能通过几个LLDB命令行指令查看内存结构
po : p 和 po 的区别在于使用 po 只会输出对应的值,而 p 则会返回值的类型以及命令结果 的引用名。
x/8g: 读取内存中的值(8g: 8字节格式输出)
例如
-类:
class Teacher {
var name: String
init(_ name: String) {
self.name = name
}
}
var t = Teacher()
-结构体
struct Student {
var name: String
}
var s = Student(name: "Li")
二、类和结构体的初始化
需要注意的是 在类里,编译器默认不会为我们自动初始化成员变量,固当我们定义了成员变量时,我们要手动创建初始化器(指定初始化器),否则编译器会报错。例如上面的例子中我们在Teacher类中 定义了init(_ name: String) 初始化方法。
1、便捷初始化器
同时,我们也能为当前类提供便捷初始化器(注意:便捷初始化器必须从相同的类里调用另外一个初始化器)。
我们修改上面的Teacher类
class Teacher {
var name: String
var age: Int
init(_ age: Int, _ name: String) {
self.name = name
self.age = age
}
convenience init() {
self.init(20, "Li")
}
}
var t = Teacher()
对于便捷初始化器我们需要记住几点规则:
- 指定初始化器必须保证在向上委托给父类初始化器之前,其所在类引入的所有属性都要初始化完成。
- 指定初始化器必须先向上委托父类初始化器,然后才能为继承的属性设置新值。如果不这样做,指定初始化器赋予的新值将被父类中的初始化器所覆盖
- 便捷初始化器必须先委托同类中的其它初始化器,然后再为任意属性赋新值(包括 同类里定义的属性)。如果没这么做,便捷构初始化器赋予的新值将被自己类中其它指定初始化器所覆盖
- 初始化器在第一阶段初始化完成之前,不能调用任何实例方法、不能读取任何实例 属性的值,也不能引用 self 作为值
2、可失败初始化器
这个很好理解,就是在初始化时,存在返回nil 的初始化器。在有些类里,我们可能定义了一些判断条件,若初始化时,条件不符合则返回初始化失败即nil。
例如:
class Teacher {
var name: String
var age: Int
init?(_ age: Int, _ name: String) {
guard age >= 20 else {return nil}
self.name = name
self.age = age
}
convenience init?() {
self.init(18, "Li")
}
}
var t = Teacher()
此时,t 这个实例就是nil 因为在便捷初始化器里默认是age = 18 是小于 20的,固初始化失败
三、类的生命周期
1、编译
在iOS开发中,不管是Objective-C开发,还是 Swift开发编译器最终都是使用LLVM进行编译的,如图:
Objective-C 是通过clang 编译器编译成IR,然后再生成可执行文件.O的(即机器码)
Swift 则通过Swift编译器编译成IR。然后生成可执行文件。其流程如下
// 分析输出AST
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
2、Swift内存分配
在 Swift 对象分配中时,主要是通过以下流程创建对象
__allocating_init —> swift_allocObject —> _swift_allocObject_ —> swift_slowAlloc —> Malloc
Swift 对象的内存结构是 HeapObject(堆) 而OC是 isa 指针 heapObject 里有两个属性 Metadata 和 RefCount, 默认大小是16字节
我们通过阅读源码swift-swift-5.5.2-RELEASE 可以知道 TargetMetadata 是基类,为了继续深究 swift 类的数据结构,继续分析 TargetMetadata 类
通过 getTypeContextDescriptor() 函数 我满看到在判断是 class 时,会把 this 指针强转为 TargetClassMetadata 类型
我们再到 struct TargetClassMetadata 中看到
通过整理我们得出
struct Metadata{
var kind: Int
var superClass: Any.Type
var cacheData: (Int, Int)
var data: Int
var classFlags: Int32
var instanceAddressPoint: UInt32
var instanceSize: UInt32
var instanceAlignmentMask: UInt16
var reserved: UInt16
var classSize: UInt32
var classAddressPoint: UInt32
var typeDescriptor: UnsafeMutableRawPointer
var iVarDestroyer: UnsafeRawPointer
}
这样的结构。
下面我们通过代码来证明一下我们的推论是否正确。
//Metadata 结构体
struct Metadata{
var kind: Int
var superClass: Any.Type
var cacheData: (Int, Int)
var data: Int
var classFlags: Int32
var instanceAddressPoint: UInt32
var instanceSize: UInt32
var instanceAlignmentMask: UInt16
var reserved: UInt16
var classSize: UInt32
var classAddressPoint: UInt32
var typeDescriptor: UnsafeMutableRawPointer
var iVarDestroyer: UnsafeRawPointer
}
// 实例对象的结构体 HeapObject
struct HeapObject {
// 指针
var metadate: UnsafeRawPointer
var refcounted1: uint32
var refcounted2: uint32
}
class Teacher {
var age: Int = 20
var name: String = "Li"
}
var t = Teacher()
// 获取实例对象的指针
let objcPoint = Unmanaged.passUnretained(t as AnyObject).toOpaque()
// 将 objcPoint 绑定成 HeapObject 类型
let objcHeapPoint = objcPoint.bindMemory(to: HeapObject.self, capacity: 1)
// objcHeapPoint.pointee 获取指针,swift 固定语法
print(objcHeapPoint.pointee)
// 将 objcHeapPoint.pointee.metadate 的指针绑定为 Metadata.self 类型,MemoryLayout 用来测量数据类型的大小
let metadata = objcHeapPoint.pointee.metadate.bindMemory(to: Metadata.self, capacity: MemoryLayout<Metadata>.stride).pointee
print(metadata)
运行起来后 打印出
固我们以上的 Metadate 结构体推论是正确的。