Swift 中类用class修饰,结构体用struct修饰,
class ClassTeacher{
var age:Int
var name:String
init(age:Int, name:String) {
self.age = age
self.name = name
}
deinit{
}
}
struct StructTeacher{
var age:Int
var name:String
init(age:Int, name:String) {
self.age = age
self.name = name
}
}
结构体和类的相同点不同点
相同点
- 定义存储值的属性
- 定义方法
- 定义下标以使用下标语法提供对其值的访问
- 定义初始化器
- 可以使用 extension 来拓展功能
- 遵循协议来提供某种功能
主要的不同点
- 类有继承的特性,而结构体没有
- 类型转换使您能够在运行时检查和解释类实例的类型
- 类有析构函数用来释放其分配的资源
- 引用计数允许对一个类实例有多个引用
类和结构体最本质的区别
类是引用类型 结构体是值类型
我们可以通过lldb 指令来查看当前变量的内存结构
po:只会输出对应的值
p:返回值的类型以及命令结果 的引用名
x/8g:读取内存中的值(8g: 8字节格式输出)
frame variable -L 打印对象的内存布局
- 类类型的引用 一个类类型的变量并不直接存储具体的实例对象,是对当前存储具体实例内存地址的引用,如图所示
ct 和 ct1 的地址是相同的,且两个变量引用对象的内存地址完全相同符合引用类型的结果。
- 结构体是值类型
结构体是一种值类型,内存分配在栈中。复制后新的实例和值类型成员属性的地址空间都是重新分配的,引用类型的成员属性仍然指向之前的地址空间
ct 和 ct1 的地址是不相同的,且每个变量的内存地址中存储的属性也是不同的地址,说明两个指向的是两个实例对象
存储区域的不同
引用类型和值类型还有一个最直观的区别就是存储的位置不同:一般情况,值类型存储的在栈上,引用类型存储在堆上。
通过cat address打印地址所在的位置可以得对象在内存中的位置
-
对于类的内存分配
-
在栈上开辟 8 字节内存空间存储 ct 变量,ct 变量中存储 ClassTeacher 的地址
-
在堆上,会寻找合适的内存区域,开辟内存,存储 ClassTeacher的实例对象
-
函数结束时,会销毁栈上的指针,查找并回收堆上的示例变量。
-
-
对于结构体的内存分配
在栈上直接分配结构体内存大小的空间存储结构体的值
结构体和类的时间分配
类的内存分配比较繁琐,而且还会有引用计数等操作。 通过 github 上 StructVsClassPerformance 这个案例来直观的测试当前结构体和类的时间分配。结果是结构体速度明显快于类。
初始化器
初始化器是为了可以完整地初始化实例,所以在初始化器中必须完成对存储属性的赋值
默认初始化器
- 编译器不会自动提供类的初始化器,需要自己提供一个指定初始化器
- 结构体来说编译器会提供默认的初始化方法(前提是我们自己没有指定初始化器)!
指定初始化器
- 每个类至少有一个指定初始化器,指定初始化器是类的主要初始化器,类偏向于少量指定初始化器,一个类通常只有一个指定初始化器
- 指定初始化器必须保证在向上委托给父类初始化器之前,其所在类引入的所有属性都要初始化完成
- 指定初始化器必须先向上委托父类初始化器,然后才能为继承的属性设置新值
类的便利初始化器
便捷初始化器必须先委托同类中的其它初始化器,然后再为任意属性赋新值(包括 同类里定义的属性)
可失败初始化器
当前因为参数的不合法或者外部条件的不满足,存在初始化失败的情况。
必要初始化器
在类的初始化器前添加 required 修饰符来表明所有该类的子类都必须实现该初始化器
类的生命周期
Swift 的 编译
iOS开发的语言不管是OC还是Swift后端都是通过LLVM进行编译的,如下图所示:
- OC 通过 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
SIL 文件分析
示例代码
class ClassTeacher{
var age: Int = 10
var name: String = "XX"
}
var t = ClassTeacher()
生成 SIL 文件
这里输入,然后运行
swiftc -emit-sil ${SRCROOT}/SwiftTest/main.swift > ./main.sil && open main.sil
class ClassTeacher {
@_hasStorage @_hasInitialValue var age: Int { get set }
@_hasStorage @_hasInitialValue var name: String { get set }
@objc deinit
init()
}
@_hasStorage @_hasInitialValue var t: ClassTeacher { get set }
// t
sil_global hidden @$s4main1tAA12ClassTeacherCvp : $ClassTeacher
// main 函数入口
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
// 声明一个全局变量 s4main1tAA12ClassTeacherCvp 也就 t
alloc_global @$s4main1tAA12ClassTeacherCvp // id: %2
// 获取全局变量的地址
%3 = global_addr @$s4main1tAA12ClassTeacherCvp : $*ClassTeacher // user: %7
// 获取 ClassTeacher 的源类型
%4 = metatype $@thick ClassTeacher.Type // user: %6
// 获取 s4main12ClassTeacherCACycfC 函数的引用 也就是 __allocating_init 指针
// function_ref ClassTeacher.__allocating_init()
%5 = function_ref @$s4main12ClassTeacherCACycfC : $@convention(method) (@thick ClassTeacher.Type) -> @owned ClassTeacher // user: %6
// 使用 __allocating_init 函数 传入 元类型 得到一个实例对象
%6 = apply %5(%4) : $@convention(method) (@thick ClassTeacher.Type) -> @owned ClassTeacher // user: %7
// 存储 实例对象到 全局变量 t
store %6 to %3 : $*ClassTeacher // id: %7
// Int 是结构体 构建一个 Int 类型 的 0
%8 = integer_literal $Builtin.Int32, 0 // user: %9
%9 = struct $Int32 (%8 : $Builtin.Int32) // user: %10
// main函数返回
return %9 : $Int32 // id: %10
} // end sil function 'main'
。。。。。。。。
// ClassTeacher.__allocating_init()
// 需要一个元类型的参数 (@thick ClassTeacher.Type) 暂时理解为 isa
sil hidden [exact_self_class] @$s4main12ClassTeacherCACycfC : $@convention(method) (@thick ClassTeacher.Type) -> @owned ClassTeacher {
// %0 "$metatype"
bb0(%0 : $@thick ClassTeacher.Type):
// 去堆区申请内存空间
//Allocates an object of reference type T. The object will be initialized with retain count 1;
//its state will be otherwise uninitialized.
//The optional objc attribute indicates that the object should be allocated using Objective-C's allocation methods (+allocWithZone:).
%1 = alloc_ref $ClassTeacher // user: %3
// 调用 init 函数
// 获取init 函数指针 function_ref ClassTeacher.init()
%2 = function_ref @$s4main12ClassTeacherCACycfc : $@convention(method) (@owned ClassTeacher) -> @owned ClassTeacher // user: %3
// 调用 init函数生成变量
%3 = apply %2(%1) : $@convention(method) (@owned ClassTeacher) -> @owned ClassTeacher // user: %4
return %3 : $ClassTeacher // id: %4
} // end sil function '$s4main12ClassTeacherCACycfC'
解读SIL
@ 标识符
%0 ,%1,%2 虚拟寄存器 ,复制后不能更改
- @_hasInitialValue 标识这个类的结构有初始化的存储属性 age,name 和 默认的 init 和 析构函数
-
@main 入口 main 函数
@$s4main1tAA12ClassTeacherCvp混淆的变量名通过xcrun swift-demangle s4main1tAA12ClassTeacherCvp可以得到变量真是名称
-
类的实例对象的创建
-
alloc_ref 申请内存 swift 调用 swift_allocObject oc 会调用 allocWithZone
- 通过 汇编代码 进入到__allocating_init() 可以得到
-
Allocates an object of reference type T. The object will be initialized with retain count 1;its state will be otherwise uninitialized.The optional objc attribute indicates that the object should be allocated using Objective-C's allocation methods (+allocWithZone:).
汇编分析 类实例对象创建过程
纯 swift 类
class ClassTeacher{
var age: Int = 10
var name: String = "XX"
}
var t = ClassTeacher()
- 断点在
__allocating_init函数出
-
lldb指令si进入__allocating_init函数内部可以得出__allocating_init内部会调用swift_allocObject和init函数
继承 NSObject 的 类
class ClassPerson: NSObject{
var age: Int = 10
var name: String = "XX"
}
var t1 = ClassPerson()
- 进入
__allocating_init后可以看出 内部调用了allocWithZone方法然后通过 msgsend 发送init消息
Swfit 源码 分析
搜索 HeapObject.cpp 文件 找到 swift_allocObject 函数 可以看到 内部调用了 swift_slowAlloc 函数
进入swift_slowAlloc 函数 可以看到 调用了 malloc
由此可以得出 swift对象创建时经过如下的过程分配内存的
__allocating_init -> swift_allocObject -> _swift_allocObject_ -> swift_slowAlloc -> malloc
Swift 对象的内存结构 HeapObject (OC objc_object) ,有两个属性: 一个是 Metadata ,一个是 RefCount ,默认占用 16 字节大小
类的结构窥探
在OC 中类的只有一个 isa 指针,但是在 swift 中有 HeapMetadata 和 refCounts 两个属性
Metadata 源码分析
Metadata 的继承关系及结构简述
查看 Metadata 的源码可以看到 Metadata 是 HeapMetadata 类型 这是 TargetHeapMetadata 定义的别名
查看 TargetHeapMetadata 可得它继承自 TargetMetadata,同时也可以知道 其初始化时如果是 纯swift类则传入 MetadataKind kind 如果是和 OC 交互的类传入 isa
在TargetMetadata 中可得到一个 kind 的成员变量
MetadataKind 的解析
查看 MetadataKind 可得它是一个 uint32_t的类型
在 swift 中
MetadataKind有如下类型
类的结构分析
源码分析猜想
上面说到了 Metadata 最终继承自 TargetMetadata,且其成员中的 kind 对应多种类型,所以猜测 Metadata 最终会在这里完成创建 通过源码分析可以看到下面的这个函数
可以看出 这里会根据 kind 的种类 将TargetMetadata 转换为其他类型的 Metadata ,所以 TargetMetadata 可能就是所有类型的元类型的基类
当 kind 的 是Class 时会将 this 强转为 TargetClassMetadata 类型 这个类型可能就是类的最终结构,所以分析它的结构就应该可以得到类的大致结构
case MetadataKind::Class: {
const auto cls = static_cast<const TargetClassMetadata<Runtime> *>(this);
进入 TargetClassMetadata 其继承自TargetAnyClassMetadata
最终通过对对源码的追踪可以发现类的结构由如下继承关系 TargetClassMetadata : TargetAnyClassMetadata :TargetHeapMetadata : TargetMetadata
那么 对于类的最终结构 就是这些结构体所有的成员的集合 经过以上分析 可以大致得出 swift 类的结构如下
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 有两个属性: 一个是 Metadata ,一个是 RefCount
所以我们自定义一个结构体用来将我们的实例对象强转为该类型,如果转换成功则说明分析正确
结果说明上面分析 类的实例对象的结构为 HeapObject 没毛病
那么对于 metadata 结构分析正确与否,只需验证 metadata 是否可以转换成上述分析的 Metadata 类型即可
结果很明显 上述分析的结构 就是类的数据结构