1、类与结构体的区别
相同点:
- 定义成员属性、方法
- 定义下标以使用下标语法提供对其值的访问
- 定义初始化器
- 可以使用extension来拓展功能
- 遵循协议来提供某种功能
不同点:
- 类可以继承
- 类型转换使你能够在 运行时检查和解释类实例的类型
- 类有析构函数用来释放其分配的资源
- 引用函数允许 对一个类实例有多个引用
归纳:
- 类是
引用类型。也就是说一个类类型的变量并不直接存储具体的实例对象,是对当前存储具体实例内存地址的引用(通过地址修改是可以改变具体实例的内容的)。而结构体是值类型,存储的是具体的值,但是一套拷贝的值(修改值不影响原有具体实例)。简单说,对一个具体实例,类是指针,结构体是copy一份它的值(枚举也是值类型)- po一下类:
- po一下结构体:
可以看到,只改了LZPerson的类型,打印结果一个是指针,一个是具体的值
- po一下类:
- 简单介绍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_allocObjectswift_slowAlloc:执行malloc开辟内存空间
内存分配流程总结
-
__allocating_init(编译器为每个实例对象生成) -->swift_allocObject-->_swift_allocObject_(私有函数) -->swift_slowAlloc-->Malloc -
Swift对象的内存结构为
HeapObject(OC中为objc_object),有两个属性:MetadataRefCount- 因此默认占用
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:访问指针