「这是我参与2022首次更文挑战的第4天,活动详情查看:2022首次更文挑战」。
本篇重点:
类与结构体在类型及初始化方面的相同与不同点。
一、Class & Struct
1.1 类与结构体的相同/不同点
- 相同点
- 定义存储值的属性
- 定义方法
- 定义初始化器
- 使用下标语发访问其值
- 使用extension来扩展功能
- 遵循协议提供某种功能
struct/class UIPerson{
var age: Int //age:存储值属性
var name: String
init(age: Int, name: String) { // 定义方法 定义初始化器
self.age = age
self.name = name
}
}
extension UIPerson{ //extension 扩展方法
func shrink(){
print("extention")
}
}
var u = UIPerson.init(age: 19, name: "io") // 下标访问其值
u.shrink() //invoke extension
- 不同点
- Class 可以有继承,而Struct没有
- Class 有析构函数 ->
deinit() - Class 有引用计数
- 类型转换能够在运行时检查和解释
类实例的类型 - 内存中存储位置不同
- Classs 是引用类型,Struct是值类型
1.2值类型与引用类型
- 值类型:变量存储的就是具体的实例
- 引用类型:变量不存储具体的实例对象,而是对具体实例对象的内存地址的引用
- 关于存储位置:值类型存储在 Stack区,运行速度要比Heap快
// 同样一段代码,TestClass分别时class、struct时,t、p的内存存储的差别如下图所示。
var t = TestClass(name: a)
var p = t
-
值类型
-
引用类型
1.3 内存储存区域分析
关于内存布局目前还只懂些皮毛,就简单画一下图。
Stack区域存储局部变量和运行上下文,所以创建下面一段代码来分析一下对象的存储位置。
struct UIPerson{
var age = 18
var name = "Luck"
}
func test(){
//t 就是局部变量
var t = UIPerson()
print("end")
}
test()
根据上述的内存图来分析,其中 var t 作为局部变量是在Stack中开辟了8字节进行存储,然后它所指向了UIPerson是在Stack上开辟的内存空间。
可以使用以下命令来验证上述分析是否正确:frame varibale -L xxx ⬇️
同样还是将UIperson改为引用类型的Class,再进行测试。
1.4 Stack与Heap速度比较
分析完了 值类型 、 引用类型以及其他代码在Stack、heap上的空间分配,接下来对刚才提到的堆栈速度进行一下比较。直接使用 Git上StructVsClassPerformance的例子对速度进行测试。
二、初始化器
2.1 声明初始化器
如图示的方式为类或结构体定义属性,编译器在类上报了3个错误,而结构图报了2个,来说明一下这2种错误的原因。
- Swift是类型安全语言,所以定义存储属性时,至少是要有类型的。
- 在未指定初始化器时,编译器会为Struct生成默认的
成员初始化器,所以initialize的错误只在Class中出现。
2.2 初始化器种类
1)初始化器4原则
Swift 中创建类或结构体实例时,须为其所有存储属性设置⼀个合适的初始值,所以也需提供对应的的指定初始化器。同时,也可以提供多个**便携初始化器,为了访问安全,**所有便携初始化器必须先调用当前类中的指定初始化器,且要加上convenience修饰。
当UIPerson派生出一个子类UIMan 且指定了初始化器,那UIMan的初始化器要在执行Super.init之前,将自己所有属性完成初始化。
类似这样的原则共4条,与之前的一并总结一下,并且按照对应原则,在图中配了相对应的代码示例。
- 指定初始化器须保证在调用⽗类初始化器之前,将其类引⼊的所有属性初始化完成
- 指定初始化器须先调用⽗类初始化器之后,才能为继承自父类的属性设置新值。否则,赋予的新值会被⽗类中的初始化器所覆盖**(编译器报错)**
- 便捷初始化器必须先执行同类中的其它初始化器,然后才可对属性赋新值(包括同类⾥定义的属性)。否则,便捷构初始化器赋予的新值将被⾃⼰类中其它指定初始化器所覆盖**(编译器报错)**
- 初始化器在第⼀阶段初始化完成之前,不能调⽤任何实例⽅法、不能读取任何实例属性的值,也不能引⽤ self 作为值。
2)可失败 初始化器
如果当前因为参数的不合法或外部条件不满⾜,可能会初始化失败。Swift 中可失败初始化器写
return nil来表明在何种情况下会触发初始化失败。
class UIPersonC{
var age: Int
var name: String
init?(_ age:Int, _ name: String ){
if age < 18 {return nil};
self.age = age
self.name = name
}
}
3)必要 初始化器
在初始化器前面添加
required修饰,标示该父类所有子类必须实现该初始化器。否则编译器报错,'required' initializer 'init(_:_:)'
class UIPersonC{
var age: Int
var name: String
required init(_ age:Int, _ name: String ){
self.age = age
self.name = name
}
}
class UIMan:UIPersonC{
var hight: Int
required init(_ age: Int, _ name: String) { //子类必须实现
self.hight = 190
super.init(16, "ll")
}
}
三、浅探Swift中类的结构
3.1 下载Swift 源码
Apple-Swift官网对Swift有简单的介绍说明,也可查看到Swift是开源的:Swift is developed in the open at``Swift.org,网站中包含了Swift有关的各种资源,也包括Swift源码-Release,下载后就可以对Swift方法进行底层的分析了。
3.2 分析Class创建
- 通过以下简单的代码进行查看汇编(Debug——> Always Show Disassembly),然后一步步
Setp Into可以追到Swift类创建时调用的符号名(swift_allocObject),再根据符号名在到Swift 源码中进行查看分析。
- 在源码中全局搜索
swift_allocObject,最终是在Heap.cpp中找到了方法的实现,然后对代码的简单分析整理出类的alloc调用流程及类在底层的数据结构
- 调用路径:
__allocating_init--->swift_allocObject--->_swift_allocObject_--->swift_slowAlloc--->Malloc- 数据结构:返回值
HeapObject*,包含两个属性:Metadata、RefCount,默认占用16字节的大小
- 然后对于
HeapObject的属性metadata继续深入,metadata的类型是HeapMetadata const,搜索筛选后HeapMetadata =TargetHeapMetadata,进入后就可以得到父类TargetMetadata,并且发现有对Objc的兼容,那这个metadata就相当于OC类中isa。
- 进入
TargetMetadata似乎是到瓶颈了,因为它已经没有父类,是最终类了。不过冷静分析一下TargetMetadata的实现逻辑,其中出现了很多MetadataKind的数据类型,应该是在什么位置根据这个Kind进行分发分发方法。然后对MetadataKind进行了搜索,几经分析发现一些端倪,在方法getTypeContextDescriptor对有对kind操作的switch。并且在case中有对应于Class类型TargetClassMetadata。
- 一步一步搜索找到其父类
TargetAnyClassMetadata,然后是TargetHeapMetadata:TargetMetadata。闭环了。
- 一路搜索分析下来,基本把Class底层的结构体的继承链分析完成了,汇总做一张继承图
3.3 数据结构汇总
前面的链路梳理,加上过程中找的的结构体上的注释,可以得出Swift中的类的最终结构体是在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
}