前言:swift已经发展了好几年了,目前版本趋于稳定,iOS之后的语言将会从Objective-c逐渐切换到swift,抱着学习的态度来研究下swift语言。
从一小段代码切入
SIL分析
class Person{
var age = 18
var height = 180
var name = "Kevin"
}
var t = Person()
很简单的声明了一个Person类,并且得到一个实例化的Person对象,那么从SIL的角度来分析一下发生了什么,使用命令swiftc -emit-sil main.swift >> ./main.sil && open main.sil得到一个SIL文件,接下来看看这个文件里发生了什么。
这里可以看到上面是我们生命的
Person的类,下面有一个main就是我们main.swift的入口,alloc_global创建一个全局变量,那么后面这个字符串是经过编码的一段东西,这里使用命令xcrun swift-demangle s4main6PersonCACycfC对s4main6PersonCACycfC解码得到s4main6PersonCACycfC ---> main.Person.__allocating_init() -> main.Person,可以清晰的看到这个就是Person的__allocating_init函数,之后通过函数调用得到Person的实例对象。
大体的概括就是这样,接下来对每一步进行描述:
alloc_gobal创建一个名为t的Person类的全局变量,s4main1tAA6PersonCvp---->s4main1tAA6PersonCvp ---> main.t : main.Personglobal_addr拿到全局变量的地址给到3%metatype拿到Person的MetaData赋值给4%apply调用__allocating_init并将结果赋值给全局变量t
__allocating_init()的过程中会发生什么呢,回到main.swift文件,借助符号断点来看看会发生什么
可以看到实际分配内存空间的函数叫做swift_allocObject,但是在之前SIL文件中并不能找到它的身影,接下来切换到swift源码中进行探索。
swift_allocObject
在swift源码中开始调试,很快可以确认到_swift_allocObject_这个函数,并对其打上断点开始调试
tips:
requiredSize是指需要分配的内存空间为48字节requiredAlignmentMask是指以8字节进行内存对齐
同时对于分配的内存空间是否为48字节,同样可以进行验证,通过class_getInstanceSize(Person.self)得到结果48
在内部会调用swift_slowAlloc函数
swift_slowAlloc
malloc_zone_malloc会在堆空间分配一块size大小的内存空间
总结
以上我们就可以得到swift在内存分配的过程的流程:
__allocating_init -----> swift_allocObject -----> _ swift_allocObject_ -----> swift_slowAlloc -----> malloc_zone_malloc
实例对象的本质
通过对swift_allocObject的分析在分配到内存空间后,此函数的返回类型为HeapObject,这里是通过元数据metaData来初始化HeapObject
HeapObject
constexpr HeapObject(HeapMetadata const *newMetadata)
: metadata(newMetadata)
, refCounts(InlineRefCounts::Initialized)
{ }
这里是HeapObject的初始化函数,这里包含两个数据
- metadata:元数据
- refCounts:引用计数,同样是通过
ARC来管理内存
metadata是一个指针类型,占用8字节空间
通过源码可以看到
refCounts是一个类的指针类型,故也占8字节空间
结论
HeapObject默认占用内存空间为metadata类型指针8字节 + refCounts8字节
总计16字节
所以对于开篇引入的例子Person对象占用的内存空间为:8+8+8+8+16 合计48字节
类结构
刚刚分析了实例对象,在HeapObject存在metadata元数据,它记录的就是类的信息。
跟进源码中发现metadata不过是一个别名,其真正是TargetHeapMetadata
TargetHeapMetadata是一个模板类,在初始化方法中看到有一个参数kind应该是来自于父类,切换至TargetMetadata中
kind是用来区分当前是何种类型的元数据,区分文件是名为MetadataKind.def,所有的元数据类型都在此文件中。
kind代表的所有类型的元数据整理如下
| name | value |
|---|---|
| Class | 0x0 |
| Struct | 0x20 |
| Enum | 0x201 |
| Optional | 0x202 |
| ForeignClass | 0x203 |
| Opaque | 0x300 |
| Tuple | 0x301 |
| Function | 0x302 |
| Existential | 0x303 |
| Metatype | 0x304 |
| ObjcClassWrapper | 0x305 |
| ExistentialMetatype | 0x306 |
| HeapLocalVariable | 0x400 |
| HeapGenericLocalVariable | 0x500 |
| ErrorObject | 0x501 |
| LastEnumerated | 0x7ff |
我们当前代码中Person是一个类,那么找到了一个针对于类的方法
可以看到在判断类型过后发现当前元数据是类的类型时,直接做了强转,此时跳转到
ClassMetadata中
在其中看到很多属性,再到
ClassMetadata的父类TargetAnyClassMetadata中
最后发现父类是TargetHeapMetadata它只有一个属性kind
结论 当前类的结构整理后如下:
struct swift_class_t {
void * kind;
void * superClass;
void * cacheData;
void * data;
uint32_t flags;
uint32_t instanceAddressOffset;
uint32_t instanceSize;
uint16_t instanceAlignMask;
uint16_t reserved;
uint32_t classSize;
uint32_t classAddressOffset;
void *description;
}
如果该类与Objective-C交互,则此时kind相当于isa。