对于iOS开发,OC语言前端使用Clang
编译器,swift
语言前端使用swift编译器swiftc
,这两个编译器将我们写的代码编译生成IR中间代码
,后端都是通过LLVM进行优化,接着交给代码生成器生成机器语言
,最终形成.o机器执行文件
。
在研究Swift之前,先来探究一下Swift类结构。
一、Swift类初始化
先写一段简单的代码:
class Person {
var age: Int = 33
var name: String = "chen"
}
let t = LGPerson()
生成SIL
(Swift intermediate language)命令:swiftc -emit-sil main.swift | xcrun swift-demangle >> ./main.sil && open main.sil
xcrun swift-demangle
用于还原变量,下文未用。
class Person {
@_hasStorage @_hasInitialValue var age: Int { get set }
@_hasStorage @_hasInitialValue var name: String { get set }
@objc deinit
init()
}
@_hasStorage @_hasInitialValue let t: Person { get }
// t
sil_global hidden [let] @$s4main1tAA6PersonCvp : $Person
// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
alloc_global @$s4main1tAA6PersonCvp // id: %2
%3 = global_addr @$s4main1tAA6PersonCvp : $*Person // user: %7
%4 = metatype $@thick Person.Type // user: %6
// function_ref Person.__allocating_init()
%5 = function_ref @$s4main6PersonCACycfC : $@convention(method) (@thick Person.Type) -> @owned Person // user: %6
%6 = apply %5(%4) : $@convention(method) (@thick Person.Type) -> @owned Person // user: %7
store %6 to %3 : $*Person // id: %7
%8 = integer_literal $Builtin.Int32, 0 // user: %9
%9 = struct $Int32 (%8 : $Builtin.Int32) // user: %10
return %9 : $Int32 // id: %10
} // end sil function 'main'
@main
标识当前swift文件的入口函数,SIL标识
符号名称以@
作为前缀
。%0
,%1
...在SIL中也叫寄存器
,类似代码中的常量,一旦赋值后不可修改。如果SIL中还要继续使用,就需要使用新的寄存器。
SIL代码解读:
alloc_global @$s4main1tAA6PersonCvp :
@$s4main1tAA6PersonCvp反混淆出来就是Person,创建全局变量Person
%3 = global_addr @$s4main1tAA6PersonCvp : $*Person :读取全局变量Person地址,赋值给%3
%4 = metatype $@thick Person.Type :读取Person的Type,赋值给%4
%5 = function_ref @$s4main6PersonCACycfC : $@convention(method) (@thick Person.Type) -> @owned Person : 定义一个function_ref即函数,就是%5,这个函数入参是Person.Type
%6 = apply %5(%4) : $@convention(method) (@thick Person.Type) -> @owned Person
:调用函数%5,入参就是%4,将返回结果赋给%6
store %6 to %3 : $*Person :将%6的结果存储到%3,%3是LGPerson的地址
%8 = integer_literal $Builtin.Int32, 0 和 %9 = struct $Int32 (%8 : $Builtin.Int32)
:就是构建一个Int值
return %9 : $Int32: 最终返回值
总结一下,main函数,通过Type
返回了一个全局变量Person
。
实例化过程__allocating_init
// Person.__allocating_init()
sil hidden [exact_self_class] @$s4main6PersonCACycfC : $@convention(method) (@thick Person.Type) -> @owned Person {
// %0 "$metatype"
bb0(%0 : $@thick Person.Type):
%1 = alloc_ref $Person // user: %3
// function_ref Person.init()
%2 = function_ref @$s4main6PersonCACycfc : $@convention(method) (@owned Person) -> @owned Person // user: %3
%3 = apply %2(%1) : $@convention(method) (@owned Person) -> @owned Person // user: %4
return %3 : $Person // id: %4
} // end sil function '$s4main6PersonCACycfC'
代码解读
%1 = alloc_ref $Person :读取Person的alloc_ref方法地址,给%1
%2 = function_ref @$s4main6PersonCACycfc : $@convention(method) (@owned Person) -> @owned Person : 读取Person.init()函数地址,给%2
%3 = apply %2(%1) : $@convention(method) (@owned Person) -> @owned Person // user: %4 : 调用alloc_ref创建一个Person实例对象,给%3
return %3 : $Person :返回%3的实例对象
总结一下就是,调用alloc_ref
、init
方法,返回对象。
init
方法主要用于初始化变量,和OC
中是一致的。这个过程用Swift代码会更简洁:Person() == [[Perosn alloc] init]
。
初始化过程,在底层是怎么实现的,通过__allocating_init
断点,我们可以跟进去
swift实例对象的创建流程
__allocating_init
->swift_allocObject
->_swift_allocObject_
->swift_slowAlloc
->malloc_zone_malloc
具体实现,参考Swift源码,最终对象会被转为HeapObject
类型。
二、Swift类的结构
2.1 HeapObject结构体
HeapObject
结构体有两个成员变量:元数据metadata 和 引用计数refCounts,总共16字节
。
2.2 metadata的解析
HeapMetedata
—> TargetHeapMetadata
—> TargetMetadata
,他们都是struct类型
,TargetMetadata
里面只有一个成员变量kind
。
通过注释
和getKind()
找到如下代码:
/// Get the metadata kind.
MetadataKind getKind() const {
return getEnumeratedMetadataKind(Kind);
}
/// Try to translate the 'isa' value of a type/heap metadata into a value
/// of the MetadataKind enum.
inline MetadataKind getEnumeratedMetadataKind(uint64_t kind) {
if (kind > LastEnumeratedMetadataKind)
return MetadataKind::Class;
return MetadataKind(kind);
}
getKind
是调用getEnumeratedMetadataKind
,而入参kind
是uint64_t
类型,占8字节
大小,所以元数据metadata
是8字节
大小。
kind类型如下:
name | value |
---|---|
Class | 0x0 |
Struct | 0x200 |
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 |
metadata的数据结构体
:
struct swift_class_t: NSObject{
void *kind;//相当于OC中的isa,kind的实际类型是unsigned long。兼容oc,swift对象转换为oc时,即为isa指针。
void *superClass;
void *cacheData;
void *data;
uint32_t flags; //4字节
uint32_t instanceAddressOffset;//4字节
uint32_t instanceSize;//4字节
uint16_t instanceAlignMask;//2字节
uint16_t reserved;//2字节
uint32_t classSize;//4字节
uint32_t classAddressOffset;//4字节
void *description;
...
}
2.3 refCounts
接着我们看看refCounts
,refCounts
是InlineRefCounts
类型,搜索InlineRefCounts
typedef RefCounts<InlineRefCountBits> InlineRefCounts;
InlineRefCounts
是RefCounts
类型
RefCounts
是class类型
,确切的说,refCounts是个指针,占8字节大小
。
综上所述
swift类本质是HeapObject
- HeapObject默认大小为16字节:
metadata(struct)8字节
和refCounts(class)8字节
所以,上文,Person的size应该为40字节 = age(Int)占8字节 + name(String)占16字节 + HeapObject(16字节)
。
验证一下
class Person {
var age: Int = 33
var name: String = "chen"
}
let t = Person()
print("Int32 大小: \(MemoryLayout<Int32>.size)")
print("Int64 大小: \(MemoryLayout<Int64>.size)")
print("Int 大小: \(MemoryLayout<Int>.size)")
print("Srtring 大小: \(MemoryLayout<String>.size)")
print("Person 大小: \(class_getInstanceSize(Person.self))")
打印结果是: