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

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

  • 本文主要介绍类的生命周期分析

我们知道在iOS中,app开发通过编译器把我们的代码编译成机器可识别的代码mach-o文件,编译器分为前端编译器后端编译器,oc中前端编译器为clang,swift中为swift编译器。通过前端编译器 进行词法分析语法分析检查语法是否正确生成中间代码IR之后通过中间层进行代码优化,交给后端llvm进行处理,生成mach-o文件

流程如下:

image.png 具体流程可以参考我之前的文章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类

image.png 在终端输入下面的代码会生成sil文件,并在终端打印生成的main.sil文件

swiftc main.swift -emit-sil
复制代码

image.png

上面的 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上的官方文档查阅

image.png

  • 脚本编译打开

我们也可以添加脚本运行打开
1.添加target

image.png 添加运行脚本

image.png

添加脚本,这里需要在当前main所在的文件夹

image.png

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

报错的话,或者说无法打开,你可以先打开vscode,设置main.sil的打开方式 image.png

2 汇编分析流程

我们使用汇编进行分析

image.png 我们在Person类初始化前打断点,然后在__allocating_init()处打断点

image.png setp into 当前的方法 ,按住control 点击进入

image.png

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

image.png

进入init

image.png

3.源码分析

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

image.png

我们就像看下 _swift_allocObject_的实现,主要传3个参数,根据分配的内存大小通过swift_slowAlloc创建内存空间,之后把元数据关联内存空间,最后返回这个实例对象。

image.png

点击进入swift_slowAlloc查看

image.png

对齐方式默认大小

image.png 上面我们最终生成一个heapObject的对象,类似我没OC中类继承与objc_class一样

  • HeapObject

image.png

主要有2个初始化的方法,都包含2个参数分别时HeapMetadata类型的metaDataInlineRefCounts类型的refcounts

  • HeapMetadata

image.png

查看TargetHeapMetadata

image.png

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

image.png 查看#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++中结构体可以继承

image.png

查看TargetMetadata里面有很多方法属性,我们关注下getTypeContextDescriptor的方法

image.png

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

image.png

继承TargetAnyClassMetadata,点击查看

image.png

如果是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的对象,我们可以仿照这个结构体进行自定义

image.png

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验证下

image.png

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

image.png

4.总结

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

image.png

猜你喜欢

转载自juejin.im/post/7055224921986711560
02-