1、类与结构体的区别
相同点:
- 定义成员属性、方法
- 定义下标以使用下标语法提供对其值的访问
- 定义初始化器
- 可以使用extension来拓展功能
- 遵循协议来提供某种功能
不同点:
- 类可以继承
- 类型转换使你能够在 运行时检查和解释类实例的类型
- 类有析构函数用来释放其分配的资源
- 引用函数允许 对一个类实例有多个引用
归纳:
- 类是
引用类型
。也就是说一个类类型的变量并不直接存储具体的实例对象,是对当前存储具体实例内存地址的引用
(通过地址修改是可以改变具体实例的内容
的)。而结构体是值类型
,存储的是具体的值,但是一套拷贝的值(修改值不影响原有具体实例)。简单说,对一个具体实例,类是指针,结构体是copy一份它的值
(枚举也是值类型)- po一下类:
- po一下结构体: 可以看到,只改了LZPerson的类型,打印结果一个是指针,一个是具体的值
- 简单介绍4个命令:
po
和p
: p 和 po 的区别在于使用po只输出对应的值
,而p则会返回值的类型以及命令结果的引用名
。x/8g
: 读取内存中的值(8g: 8字节格式输出)withUnsafePointer(to: T, body: (UnsafePointer<T>) throws -> Result)
:Swift中定义了一些特定类型的指针,每个类型都有他们的作用和目的,使用适当的指针类型可以防止错误的发生,并且更清晰地表达开发者的意图,防止未定义行为的产生。通过指针类型的名称,我们可以知道这是一个什么类型的指针:可变/不可变
、原生(raw)/有类型
、是否是缓冲类型(buffer)
,大致有以下8种类型:- unsafe:不安全的
- Write Access:可写入
- Collection:像一个容器,可添加数据
- Strideable:指针可使用 advanced 函数移动
- Typed:是否需要指定类型(范型) 扯远了...打印t和t1的值看一下效果:
frame variable
:按某种规则查看某个变量的内存布局,具体规则使用help frame variable
查看//断点后,lldb中执行命令 frame variable -L 变量名
- 引用类型 和 值类型 还有个很大的区别是储存位置的区别:一般情况,
引用类型存储在堆上
,而值类型存储在栈上
-
栈(stack)
:局部变量和函数运行过程中的上下文编译时确定
,是静态的
,由系统分配,所以速度快
栈从高地址向低地址扩展
- 函数调用是在栈上的,不同函数中的栈数据不能被另一个函数调用,多线程中每个线程启动的时候是调用一个函数,所以各线程有各自的栈
- 数据 size可以确定时使用栈 ,因为效率高、速度快
-
堆(heap)
:存储所有对象运行时确定
,是动态的
,由程序员分配,所以速度慢
堆由低地址向高地址扩展
- 堆是在进程中 的,进程中所有的线程都可以访问 堆数据
- 数据 size不确定时使用堆 ;使用 非常庞大的数据时使用堆,因为使用完需要抓紧释放
-
全局(global):存储全局变量、常量、代码区
-
又扯远了..好吧,所以我们平时代码可以 尽量使用结构体和枚举来定义内容 ,因为效率会高
-
1.1、初始化器
- Swift是类型安全的,要求对创建的类和结构体的成员属性赋值,不进行赋值的需要创建初始化器,对类来说 需要手动创建初始化器 ,而 结构体系统会默认创建一个初始化器
1.1.1、指定初始化器
-
无赋值情况,自己定义的初始化器
-
继承的类中,要按
新增加的属性初始化 -> 调用父类super的初始化器 -> 对继承的属性重新赋值
的顺序
1.1.2、便捷初始化器
- 使用
convenience
修饰 init ;通过预初始化,来实现缺参初始化类或结构体
,因此便捷初始化器必须从 相同的类里 调用另一个初始化器来先进行预初始化; 预初始化完成前,调用受限,甚至self都无法使用
1.1.3、可失败初始化器
- init后添加可选标识? ;当前因为参数的不合法或者外部条件的不满足,存在初始化失败的情况; 对输入的参数进行条件筛选 ,并且要注意,
指定初始化器如果就是可选的,那么便捷初始化也必须是可选的
1.1.4、必要初始化器
- 在类的初始化器前添加
required
修饰符来表明所有该类的子类都必须实现该初始化器
2、类的生命周期
- iOS开发语言,不论是OC还是Swift,都是通过
LLVM
进行编译的,最终生成.o
文件 - OC 通过 clang 编译器,编译成
IR
,然后再生成可执行文件.o
(这里也就是我们的机器码) - Swift 则是通过 Swift编译器 编译成 IR ,然后在生成可执行文件
2.1、Swift编译流程
-
Swift Code 经过
-dump-parse
进行语义分析解析成Parse
(抽象语法树) -
Parse 经过
-dump-ast
进行语义分析分析语法是否正确,是否安全 -
Seam
之后会把 Swift Code 会降级变成SILGen
(Swift中间语言Swift intermediate language
),对于 SILGen 又分为Raw SIL
(原生的,没有开启优化选项)和SIL Opt Canonical SIL
(经过优化的) -
优化完成的
SIL
会由 LLVM 降级成为IR
,降级成 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
- 对比OC,Swift代码编译过程中添加了 SIL ,对代码进行更严格的安全检查,举例如下,因为
int8_t
只有一个字节,其运算结果已经溢出,OC打印错误数据,而Swift会直接报错
2.2、查看SIL中间代码
-
先看个别的简单 SIL 代码熟悉下,抛个砖
-
再来查看我们自己的SIL代码,我们需要先获得SIL文件,可以创建一段JS脚本后运行这段脚本生成(File --> New --> target)
swiftc
:Swift编译器;后边为要编译的文件路径与名称
-
以这个简单代码分析一下:
-
定义的类
如果源Swift代码拿掉实例化调用,而是加上 print("end") ,得到以下 SIL ,这个一下复杂很多,本人尽量以自己的角度进行了理解可能并不很到位,要更好的理解全部还是需要对照下边官方SIL文档
-
__allocating_init()
:文中为__allocating_init(LZAge:_:_isMale)
,创建实例对象,该函数需要一个 LZPerson.Type 的元类型 -
alloc_ref
会创建对应的实例变量,其引用计数初始化为1
; alloc_ref 实际上也就是去堆区
申请内存空间;如果标识为objc
的Swift类
将会使用Objective-C
的+allocWithZone:
初始化方法
-
SIL指令
-
@main
: 入口函数 -
%0、%1、%2...
: 寄存器,类似于常量
,一旦赋值就不能更改,只能通过增加角标再赋值、地址通过store赋予可以,虚拟的,跑在真机上才使用真的寄存器 -
@$
:后边跟的是经过混写之后的名称,可以在终端通过xcrun swift-demangle
方法解析出来 -
integer_literal
:创建一个系统内置整数类型 -
tuple_extract
:与元组中提取的元素同类型%1 = tuple_extract %0 : $(T...), 123 // %0 must be of a loadable tuple type $(T...) // %1 will be of the type of the selected element of %0
-
print(_:separator:terminator:)
:是一个⽤来输出一个或多个值到适当输出区的全局函数 -
begin_access
:开始访问目标内存。vtable
:虚表,将可能进行动态调用的目标记录在虚表中跟踪
- SIL 表示使用
class_method
、super_method
、objc_method
和objc_super_method
指令对类方法的动态调度。
-
class_method 和 super_method 的潜在目标在每个类类型的
sil_vtable
声明中进行跟踪.该声明包含从类的每个方法(包括从其基类继承的方法)到实现该类方法的 SIL 函数的映射:
2.3、Swift对象内存分配流程:
-
对Swift代码打断点,走汇编调试
-
下载Swift源码查看
HeapObject.cpp
:swift_allocObject
swift_slowAlloc
:执行malloc开辟内存空间
内存分配流程总结
-
__allocating_init
(编译器为每个实例对象生成) -->swift_allocObject
-->_swift_allocObject_
(私有函数) -->swift_slowAlloc
-->Malloc
-
Swift对象的内存结构为
HeapObject
(OC中为objc_object),有两个属性:Metadata
RefCount
- 因此默认占用
16字节大小
(OC中objc_object内为isa,因此是8字节)objc_object{ isa }
3、Swift对象内存结构
-
上边已经说到两个属性,那么我们继续来分析一下
Metadata
是个什么东西 -
看到了是
HeapMetadata
类型,继续跳转进去查看 -
原来 HeapMetadata 是
TargetHeapMetadata
这个类型的别名,那么就再分析 TargetHeapMetadata -
TargetHeapMetadata
继承自TargetMetadata
,MetadataKind
只是个int32分析不出东西,没办法,向上找父类TargetMetadata
代码 -
在该函数中通过
getKind
返回的类型来区分当前类
的类型;而TargetClassMetadata
就是所有类型元类
的最终基类
;- 如果是 Class, 那么将会把当前指针
this
通过static_cast
强转为TargetClassMetadata
类型; - TargetClassMetadata 继承自
TargetAnyClassMetadata
; TargetAnyClassMetadata
的数据结构含有Superclass
,CacheData[2]
,Data
等属性,这与OC
类的结构很相似
- 如果是 Class, 那么将会把当前指针
-
结论: Metadata 基本相当于 OC 中的 isa ,其内存结构为:
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 }
Swift方法拓展
Unmanaged.passUnretained(实例 as AnyObject).toOpaque()
:获取实例对象的指针objcRawPtr.bindMemory(to: *T.type*, capacity: *Int*)
:绑定内存.pointee
:访问指针