类与结构体
首先我们必须明确一点,类是引用类型,而结构体是值类型,一般情况下,类是存储在堆上,类类型的变量中存储的是地址,而结构体是在栈上,存储的是具体的实例。类通过一段具体的代码我们就可以清楚的认识他们:
可以清楚的看到,
t和t1指向的是同一个地址,(实例对象分配过程:首先栈上会分配8字节的空间存储实例对象的引用,接着在堆上寻找合适的内存区域并把内存地址返回回来,把当前value拷贝到内存空间中,接着把栈上的内存地址指向堆区,从而完成实例对象的内存分配) 而值类型则不然,同样以一段代码,观察:
从代码中,可以清晰看到st.age改变不影响stu.age的改变,所以st相当于是对stu的一份拷贝。我们也可以查看下它的内存分布,通过 frame variable -L xxx :
上图我们很明显可以看到,st和stu在两个不同的内存地址上,而且都在栈区。
内存插件:libfooplugin.dylib,lldab调试:cat address 内存地址(如0x00ff)
接下来,我们需要对内存区域有个基本的认知,可以看下这张图:
- Heap:存储所有对象;
- Global:存储全局变量;常量;代码区;
- Segment & Section: 我们常研究的内存结构就是
Mach-o文件,Mach-o文件有多个段(Segment),每个段有不同的功能,每个段又分为很多小的Section;TEXT.text: 机器码TEXT.cstring:硬编码的字符串TEXT.const: 初始化过的常量DATA.data: 初始化过的可变的(静态/全局)数据DATA.const: 没有初始化过的常量DATA.bss: 没有初始化的(静态/全局)变量DATA.common: 没有初始化过的符号声明 我们可以通过几段示例验证下:
上图明确看到,初始化过的变量存放在DATA.data
上图可以看到,硬编码的字符串,放在TEXT.cstring
类的生命周期
ios开发的语言不论是OC还是swift,后端都是通过llvm进行编译的,如下图:
OC通过clang编译器,编译成IR,再生成可执行文件.O,Swift则通过Swift编译器生成IR,再生成可执行文件,具体流程如下图:
常用编译命令:
- swiftc main.swift -dump-parse //分析输出AST
- swiftc main.swift -dump-ast //分析并检查类型输出AST
- swiftc main.swift -emit-silgen //生成中间体语言(SIL),为优化的
- swiftc main.swift -emit-sil //生成中间体语言,优化后的
- swiftc main.swift -emit-ir //生成llvm中间体语言(.ll文件)
- swiftc main.swift -emit-bc //生成llvm中间体语言(.bc文件)
- swiftc main.swift -emit-assembly //生成汇编
- swiftc -o main.o main.swift //编译生成可执行.out文件
接下来,我们看下生成SIL文件的示例:
通过把相关编译命令写在脚本里,然后运行生成并自动打开SIL文件
生成的SIL文件:
xcrun swift-demangle 还原混写名称
SIL部分代码解析:alloc_global分配一个全局变量,global_addr拿到CTPerson这个全局变量给到%3,metatype $thick CTPerson.Type,获取CTPerson.Type的元类型,function_ref @CTPerson.__allocating_init():获取到__allocating_init()函数指针 @main: 入口函数 @是一种标识
%x:寄存器(虚拟),一旦赋值,不可被更改
通过断点调试我们能知道Swift内存分配的前两步__allocating_init()->swift_allocObject:
接下来我们去源码中看详细的步骤,直接在
HeapObject.cpp中搜swift_allocObject,我们可以看到
源码中可以看到,最终swift_allocObject调用的_swift_allocObject_,_swift_allocObject_调用swift_slowAlloc,最终调用malloc分配堆空间。
对象分配流程:__allocating_init()-->swift_allocObject-->swift_allocObject-->swift_slowAlloc-->malloc
从源码中,可以看到swift对象的内存结构:
HeapObject,以及它的两个属性:metadata(HeapMetadata const *__ptrauth_objc_isa_pointer metadata;)、refCounts(64位的位域信息相当于8字节),默认占16字节大小。
进一步跟进源码,看HeapMetadata的具体信息,它是TargetHeapMetadata的别名:
而
TargetHeapMetadata继承于TargetMetadata,在swift下传入的是个MetadataKind,而oc下传入的是isa,这里其实只是为了兼容oc,本质上没什么区别,下图源码:
源码中
MetadataKind只是个uint32的数据类型,并没有其他信息,我们想看类的具体信息,只能去父类TargetMetadata中寻找:
我们会发现一个关键的函数
getTypeContextDescriptor(),通过观察它基本可以确定TargetMetadata是所有元类的基类:
MetadataKind::Class:{ const auto cls = static_cast<const TargetClassMetadata *>(this); }
当前MetadataKind是Class时,当前的this指针会被强转为TargetClassMetadata类型,TargetClassMetadata会不会是我们要找的类型呢?我们看下它具体的内容
根据这个
TargetClassMetadata类的内容,我们基本可以窥探到metadata所有的内容了,从而得出swift类的数据结构:
struct Metadata {
var kind:Int
var superClass:Any.Type
var cacheData:(Int,Int)
var data:Int
var classFlags:Int32
var instanceAddressPoint:UInt32
var instancSize:UInt32
var instanceAlignmentMask:UInt16
var reserved:UInt16
var classSize:UInt32
var classAddressPoint:UInt32
var typeDescriptor:UnsafeMutableRawPointer
var iVarDestroyer:UnsafeRawPointer
}
根据源码中观察到的类的数据结构,我们用代码做了最终还原:
可以清楚看到,相关对应的值都已赋值成功。