「这是我参加2022首次更文应战的第3天,活动详情检查:2022首次更文应战」。

  • 本文首要介绍类的生命周期剖析 咱们知道在iOS中,app开发经过编译器把咱们的代码编译成机器可辨认的代码mach-o文件,编译器分为前端编译器后端编译器,oc中前端编译器为clang,swift中为swift编译器。经过前端编译器进行词法剖析语法剖析检查语法是否正确生成中间代码IR之后经过中间层进行代码优化,交给后端llvm进行处理,生成mach-o文件

流程如下:

swfit进阶-02-类与结构体(二)
具体流程能够参阅我之前的文章llvm流程

咱们接下来看下前端编译器swift的命令,相似咱们oc中clang的命令句子

// 剖析输出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

1. SIL文件剖析

根据上面的指令咱们生成main的sil文件,咱们新建一个CommandLineTool工程,在main界说一个Person类

swfit进阶-02-类与结构体(二)
在终端输入下面的代码会生成sil文件,并在终端打印生成的main.sil文件

swiftc main.swift -emit-sil

swfit进阶-02-类与结构体(二)

上面的 person就是咱们的类,包括属性的getset办法,以及deinitinit办法。@main是进口函数。

咱们能够经过> 在当前目录下生成main.sil 文件

swiftc main.swift -emit-sil > ./main.sil
// main 相当于oc中main的进口
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
alloc_global @$s4main1pAA6PersonCvp // id: %2 分配一个person的全局变量
%3 = global_addr @$s4main1pAA6PersonCvp : $*Person // user: %7 把person全局变量给%3寄存器
%4 = metatype $@thick Person.Type // user: %6 %4 赋值为Person.Type 的元类型
// function_ref Person.__allocating_init()//执行init办法函数
%5 = function_ref @$s4main6PersonCACycfC : $@convention(method) (@thick Person.Type) -> @owned Person // user: %6 
//%5相当于办法函数的指针地址
%6 = apply %5(%4) : $@convention(method) (@thick Person.Type) -> @owned Person // user: %7 
// 经过请求 init办法中传入Person.Type的类型 生成实例变量 赋值给%6
store %6 to %3 : $*Person // id: %7
//store 存储,把咱们实例的目标 咱们之前界说的全局变量%3中 只能*Person 
%8 = integer_literal $Builtin.Int32, 0 // user: %9
%9 = struct $Int32 (%8 : $Builtin.Int32) // user: %10
//$Int32 值相当于 0 ,相似咱们oc中 main return 0
return %9 : $Int32 // id: %10
} // end sil function 'main'

上面的%0,%1等是虚拟的寄存器。在SIL文件中,你会看到许多不懂的关键词,你能够检查GitHub上的官方文档查阅

swfit进阶-02-类与结构体(二)

  • 脚本编译翻开 咱们也能够增加脚本运转翻开
    1.增加target

swfit进阶-02-类与结构体(二)
增加运转脚本

swfit进阶-02-类与结构体(二)

增加脚本,这里需求在当前main地点的文件夹

swfit进阶-02-类与结构体(二)

swiftc -emit-sil ${SRCROOT}/*项目文件夹*/main.swift | xcrun swift-demangle >
 ./main.sil && open main.sil

报错的话,或者说无法翻开,你能够先翻开vscode,设置main.sil的翻开方式

swfit进阶-02-类与结构体(二)

2 汇编剖析流程

咱们运用汇编进行剖析

swfit进阶-02-类与结构体(二)
咱们在Person类初始化前打断点,然后在__allocating_init()处打断点

swfit进阶-02-类与结构体(二)
setp into 当前的办法 ,按住control 点击进入

swfit进阶-02-类与结构体(二)

进入后会调用init办法进行初始化

swfit进阶-02-类与结构体(二)

进入init

swfit进阶-02-类与结构体(二)

3.源码剖析

接下来咱们来看一下源码。源码能够去苹果官网下-swift源码下载地址。用 VSCode 翻开下载好的 swift 源码,全局搜索swift_allocObject这个函数。

swfit进阶-02-类与结构体(二)

咱们就像看下_swift_allocObject_的实现,首要传3个参数,根据分配的内存巨细经过swift_slowAlloc创建内存空间,之后把元数据关联内存空间,最终回来这个实例目标。

swfit进阶-02-类与结构体(二)

点击进入swift_slowAlloc检查

swfit进阶-02-类与结构体(二)

对齐方式默许巨细

swfit进阶-02-类与结构体(二)
上面咱们最终生成一个heapObject的目标,相似我没OC中类承继与objc_class一样

  • HeapObject

swfit进阶-02-类与结构体(二)

首要有2个初始化的办法,都包括2个参数别离时HeapMetadata类型的metaDataInlineRefCounts类型的refcounts

  • HeapMetadata

swfit进阶-02-类与结构体(二)

检查TargetHeapMetadata

swfit进阶-02-类与结构体(二)

兼容了oc中的类,数据元为isa;swift类的话默许MetaDataKind类型的数据

swfit进阶-02-类与结构体(二)
检查#include "MetadataKind.def"得到对应的值

name Value
Class 0x0
Struct 0x200
Enum 0x201
Optional 0x202
ForeignClass 0x203
ForeignClass 0x203
Opaque 0x300
Tuple 0x301
Function 0x302
Existential 0x303
Metatype 0x304
ObjCClassWrapper 0x305
ExistentialMetatype 0x306
HeapLocalVariable 0x400
HeapGenericLocalVariable 0x500
ErrorObject 0x501
LastEnumerated 0x7FF

持续检查TargetHeapMetadata承继的TargetMetadata,在C++中结构体能够承继

swfit进阶-02-类与结构体(二)

检查TargetMetadata里面有许多办法属性,咱们关注下getTypeContextDescriptor的办法

swfit进阶-02-类与结构体(二)

根据MetaDataKind的类型获取不同的des,当kind是一个Class的时分,会拿到一个名为TargetClassMetadata的指针,咱们看看TargetClassMetadata的实现:

swfit进阶-02-类与结构体(二)

承继TargetAnyClassMetadata,点击检查

swfit进阶-02-类与结构体(二)

如果是oc类,这个TargetAnyClassMetadata这个结构和咱们oc中类的结构相似isasuperClasscache,bits(data)。

所以咱们总结下swift的数据结构

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
}

前面咱们剖析可知初始化生成一个heapObject的目标,咱们能够仿照这个结构体进行自界说

swfit进阶-02-类与结构体(二)

class  Person{
var name = "fish"
var age = 3
}
struct HeapObject{
    var metadata: UnsafeRawPointer
    var refCounts: UInt32
}
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
}
let p = Person()
let objcRawPtr = Unmanaged.passUnretained(p as AnyObject).toOpaque()//获取实例目标的指针
let objcPtr = objcRawPtr.bindMemory(to: HeapObject.self
    , capacity: 1)//把HeapObject转换咱们自界说的MetaData结构
print(objcPtr.pointee);

咱们运用lldb验证下

swfit进阶-02-类与结构体(二)

打印下metadata结构体内存说明类实质是一个结构体

swfit进阶-02-类与结构体(二)

4.总结

  1. swift中的类实质是heapObject类型的结构体包括metadata引证计数,我么oc中的类实质是objc_class 类型的结构体。
  2. swift中实例目标存贮的是metadata引证计数成员变量。oc中实例变量存储的是isa成员变量
  3. swift中metadata中包括类的信息经过getTypeContextDescriptor获取描述的类型把数据存在对应的结构体中。
  4. 大致流程

swfit进阶-02-类与结构体(二)