Swift底层探索(一):编译流程
Swift编译
Swift编译究竟是一个怎样的过程呢?从Swift语言到cpu能够识别的机器码这之间究竟经过了哪些步骤呢?
我们先写一个简单的HotpotCat类如下:
import Foundation
class HotpotCat {
var name: String = "Hotpot"
var age: Int = 1
}
var hotpot = HotpotCat()
通过默认的初始化器我们构造了一个实例对象hotpot,类比OC在这个过程中alloc(内存分配) init(初始化)。那么Swift这个默认的初始化器到底做了什么操作?这里我们需要了解SIL(Swift intermediate language)
,再阅读SIL
之前,先了解下什么是SIL
。从字面意思理解它是一门中间语言。
iOS开发的语言不管是OC还是Swift后端都是通过LLVM进行编译的,编译过程如下:
OC
通过clang编译器
,编译成IR
,然后生成可执行文件.o
(也就会机器码);
Swift
通过Swift编译器
编译成IR
,然后生成可执行文件。
那么一个Swift文件编译的整个过程中都经历了什么呢?
Swift编译过程
- 1.Parse :解析器是一个简易的递归下降解析器(在 lib/Parse 中实现),并带有完整手动编码的词法分析器。通过parse进行词法分析;
- 2.Semantic Analysis: 语义分析阶段(在 lib/Sema 中实现)负责获取已解析的 AST(抽象语法树)并将其转换为格式正确且类型检查完备的 AST,以及在源代码中提示出现语义问题的警告或错误。语义分析包含类型推断,如果可以成功推导出类型,则表明此时从已经经过类型检查的最终 AST 生成代码是安全的;
- 3.Clang Importer:Clang 导入器(Clang Importer):Clang 导入器(在 lib/ClangImporter 中实现)负责导入 Clang 模块,并将导出的 C 或 Objective-C API 映射到相应的 Swift API 中。最终导入的 AST 可以被语义分析引用。
- 4.SIL 生成(SIL Generation):Swift 中间语言(Swift Intermediate Language,SIL)是一门高级且专用于 Swift 的中间语言,适用于对 Swift 代码的进一步分析和优化。SIL 生成阶段(在 lib/SILGen 中实现)将经过类型检查的 AST 弱化为所谓的「原始」SIL。SIL 的设计在 docs/SIL.rst 有所描述。这个过程生成RAW SIL(原生SIL,代码量很大,不会进行类型检查,代码优化)
- 5.SIL 保证转换(SIL Guaranteed Transformations):SIL 保证转换阶段(在 lib/SILOptimizer/Mandatory中实现)负责执行额外且影响程序正确性的数据流诊断(比如使用未初始化的变量)。这些转换的最终结果是「规范」SIL。
- 6.SIL 优化(SIL Optimizations):SIL 优化阶段(在 lib/Analysis、lib/ARC、lib/LoopTransforms 以及 lib/Transforms 中实现)负责对程序执行额外的高级且专用于 Swift 的优化,包括(例如)自动引用计数优化、去虚拟化、以及通用的专业化
通过-emit-sil命令生成优化过后的 SIL Opt Canonical SIL。这个也是我们一般阅读的SIL代码; - 7.LLVM IR 生成(LLVM IR Generation):IR 生成阶段(在 lib/IRGen 中实现)将 SIL 弱化为 LLVM LR,此时 LLVM 可以继续优化并生成机器码。
通过IRGen生成 IR; - 8.然后生成机器码交给机器进行识别。
Swift和OC的区别也就是中间SIL生成的这一部分。我们一般阅读经过优化后的SIL文件。
swift
在编译的过程中使用的前段编译器是swiftc,和OC中使用的Clang是有区别的。可以通过swiftc -h
查看swiftc都能哪些功能:
USAGE: swiftc
MODES:
-dump-ast Parse and type-check input file(s) and dump AST(s)
-dump-parse Parse input file(s) and dump AST(s)
-dump-pcm Dump debugging information about a precompiled Clang module
-dump-scope-maps <expanded-or-list-of-line:column>
Parse and type-check input file(s) and dump the scope map(s)
-dump-type-info Output YAML dump of fixed-size types from all imported modules
-dump-type-refinement-contexts
Type-check input file(s) and dump type refinement contexts(s)
-emit-assembly Emit assembly file(s) (-S)
-emit-bc Emit LLVM BC file(s)
-emit-executable Emit a linked executable
-emit-imported-modules Emit a list of the imported modules
-emit-ir Emit LLVM IR file(s)
-emit-library Emit a linked library
-emit-object Emit object file(s) (-c)
-emit-pcm Emit a precompiled Clang module from a module map
-emit-sibgen Emit serialized AST + raw SIL file(s)
-emit-sib Emit serialized AST + canonical SIL file(s)
-emit-silgen Emit raw SIL file(s)
-emit-sil Emit canonical SIL file(s)
-index-file Produce index data for a source file
-parse Parse input file(s)
-print-ast Parse and type-check input file(s) and pretty print AST(s)
-resolve-imports Parse and resolve imports in input file(s)
-typecheck Parse and type-check input file(s)
简单分析下
-dump-parse
Parse input file(s) and dump AST(s)、分析输出AST
swiftc -dump-parse main.swift >> ./main.parse
-dump-ast
Parse and type-check input file(s) and dump AST(s)。分析并且检查类型输出AST。
swiftc -dump-ast main.swift >> ./main.ast
从以上可以看出这两个都生成了抽象语法树,-dump-ast多了类型检查。我们摘录一段对比就可以看出一个bind Swift String,一个为none。
//-dump-parse
(pattern_binding_decl range=[main.swift:11:5 - line:11:24]
(pattern_typed
(pattern_named 'name')
(type_ident
(component id='String' bind=none)))
//-dump-ast
(pattern_binding_decl range=[main.swift:11:5 - line:11:24]
(pattern_typed type='String'
(pattern_named type='String' 'name')
(type_ident
(component id='String' bind=Swift.(file).String)))
-emit-silgen
Emit raw SIL file(s),简单理解为生成未加工的SIL文件。
swiftc -emit-silgen main.swift >> ./main.silgen
image.png
-emit-sil
Emit canonical SIL file(s),经过优化处理的SIL文件
swiftc -emit-sil main.swift >> ./main.sil
image.png
对比两者可以看到经过优化的SIL多了引用计数相关的操作,那么猜测引用计数是在SIL优化这一步进行处理的。
-emit-ir
Emit LLVM IR file(s), 生成LLVM的IR中间表示层文件
swiftc -emit-ir main.swift >> ./main.ir
-emit-assembly
Emit assembly file(s) (-S),生成汇编语言
swiftc -emit-assembly main.swift >> ./main.assembly
-emit-bc
Emit LLVM BC file(s),生成字节码二进制
swiftc -emit-bc main.swift >> ./main.bc
-o
生成可执行的二进制文件
swiftc -o main.o main.swift
这里main.swift文件是一个只依赖了Foundation库的文件,直接使用-emit就可以了,这么直接编译iOS项目中的Swift源文件会报错,因为依赖了UIKit或其它库,需要额外指定-target和-sdk。
- sdk 直接指定Xcode对应的SDK.
//模拟器 /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk` //真机 /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk` //mac /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk
可以通过命令代替真实路径,当然真实路径也没问题,以下分别对应模拟器、真机、mac 路径:
xcrun --show-sdk-path --sdk iphonesimulator
/iphoneos
/macosx
- target (
指令集-apple-ios版本
/指令集-apple-ios版本-simulator
)
这里在操作时用mac
/iphone
代替apple
也是可以的,分别指代mac
和iphone
平台。当然TV
和watch
也同理。
对应目标平台i386
、x86_64
、armv7
、armv7s
、arm64
、arm64e
等,这里iOS版本最低iOS7。
如:arm64e-apple-ios14.0
、x86_64-apple-ios14-simulator
、armv7-iphone-ios12.0
、arm64-mac-macosx11.0
//模拟器 swiftc -emit-sil -sdk $(xcrun --show-sdk-path --sdk iphonesimulator) -target x86_64-apple-ios14.0-simulator ViewController.swift >> ./ViewController.sil //真机 swiftc -emit-sil -sdk $(xcrun --show-sdk-path --sdk iphoneos) -target arm64e-apple-ios14.0 ViewController.swift >> ./ViewController.sil //mac swiftc -emit-sil -sdk $(xcrun --show-sdk-path --sdk macosx) -target arm64-apple-macosx11.0 ViewController.swift >> ./ViewController.sil
更详细的介绍可以观看官方讲解视频
SIL分析
接着上面的HotpotCat例子,我们直接在终端生成SIL文件并保存为main.sil查看。(当然也可以尝试其它命令-emit-silgen,-dump-ast)。
swiftc -emit-sil main.swift >> ./main.sil
class HotpotCat {
@_hasStorage @_hasInitialValue var name: String { get set }
@_hasStorage @_hasInitialValue var age: Int { get set }
@objc deinit
init()
}
可以看到HotpotCat
有两个存储属性,有一个init
方法,有一个objc标识的deinit
方法。
main分析
继续看sil文件会找到一个main函数,这个main函数其实就是swift隐藏的main函数,swift不过进行了省略而已。
// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
alloc_global @$s4main6hotpotAA9HotpotCatCvp // id: %2
%3 = global_addr @$s4main6hotpotAA9HotpotCatCvp : $*HotpotCat // user: %7
%4 = metatype $@thick HotpotCat.Type // user: %6
// function_ref HotpotCat.__allocating_init()
%5 = function_ref @$s4main9HotpotCatCACycfC : $@convention(method) (@thick HotpotCat.Type) -> @owned HotpotCat // user: %6
%6 = apply %5(%4) : $@convention(method) (@thick HotpotCat.Type) -> @owned HotpotCat // user: %7
store %6 to %3 : $*HotpotCat // 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'
在SIL中以@
作为标识符名称前缀
- @mian 标识这是swift入口函数;
- @convention(c)标识这是一个C函数,有两个参数Int32和UnsafeMutablePointer的指针,返回Int32;
- alloc_global分配一个全局变量s4main6hotpotAA9HotpotCatCvp,这个变量也就是变量
hotpot
,名字是经过混写之后的(name manager)。可以通过xcrun去还原查看。
xcrun swift-demangle s4main6hotpotAA9HotpotCatCvp
$s4main6hotpotAA9HotpotCatCvp ---> main.hotpot : main.HotpotCat
- %3~%9表示当前寄存器,这里的寄存器和
register read
读取的寄存器不是一个东西,这里寄存器是虚拟的会一直递增,可以理解为常量,经过赋值之后不会再改变。在运行的时候会直接对应到真实的寄存器。 - global_addr 拿到全局变量
hotpot
的地址赋值给%3。 - metatype 拿到HotpotCat的 Metadata 赋值给%4。也就是元类型。
- function_ref 拿到函数
s4main9HotpotCatCACycfC
的地址给到%5。这个函数我们xcrun看一下其实是$s4main9HotpotCatCACycfC ---> main.HotpotCat.__allocating_init() -> main.HotpotCat
。当然SIL里面也有注释。 - apply %5(%4)就是调用__allocating_init,参数是我们的元类型。返回值为我们要的实例变量。
- store 将得到的实例变量%6给到%3,也就是将实例变量地址放到全局变量中。
- 最后也就是构建一个0返回,相当于main函数的 retern 0。
Swift实例对象
__allocating_init()分析
那么s4main9HotpotCatCACycfC(__allocating_init)究竟干了什么呢?
// HotpotCat.__allocating_init()
sil hidden [exact_self_class] @$s4main9HotpotCatCACycfC : $@convention(method) (@thick HotpotCat.Type) -> @owned HotpotCat {
// %0 "$metatype"
bb0(%0 : $@thick HotpotCat.Type):
%1 = alloc_ref $HotpotCat // user: %3
// function_ref HotpotCat.init()
%2 = function_ref @$s4main9HotpotCatCACycfc : $@convention(method) (@owned HotpotCat) -> @owned HotpotCat // user: %3
%3 = apply %2(%1) : $@convention(method) (@owned HotpotCat) -> @owned HotpotCat // user: %4
return %3 : $HotpotCat // id: %4
} // end sil function '$s4main9HotpotCatCACycfC'
- alloc_ref 创建HotpotCat实例对象(在堆上分配内存空间),引用计数为1。alloc_ref
官方原文:
Allocates an object of reference type T. The object will be initialized with retain count 1;
- function_ref 获取init方法,这里注意
s4main9HotpotCatCACycfc
与s4main9HotpotCatCACycfC
最后一个字母不同,xcrun验证一下确实是init()
方法。 - apply 调用init方法,初始化实例变量,参数是实例变量地址,最后返回。
xcode断点验证
我们可以设置一个符号断点(__allocating_init)验证一下:
image.png
SwiftBasic`HotpotCat.__allocating_init():
-> 0x100003be0 <+0>: push rbp
0x100003be1 <+1>: mov rbp, rsp
0x100003be4 <+4>: push r13
0x100003be6 <+6>: push rax
0x100003be7 <+7>: mov esi, 0x28
0x100003bec <+12>: mov edx, 0x7
0x100003bf1 <+17>: mov rdi, r13
0x100003bf4 <+20>: call 0x100003d5e ; symbol stub for: swift_allocObject
0x100003bf9 <+25>: mov r13, rax
0x100003bfc <+28>: call 0x100003c40 ; SwiftBasic.HotpotCat.init() -> SwiftBasic.HotpotCat at main.swift:10
0x100003c01 <+33>: add rsp, 0x8
0x100003c05 <+37>: pop r13
0x100003c07 <+39>: pop rbp
0x100003c08 <+40>: ret
Swift源码验证
同样可以通过VSCode调试源码,在swift的REPL(命令交互行)
image.png
swift_allocObject
在定义变量hotpot敲回车前,我们给_swift_allocObject_
加个断点:
这个方法最终是创建我们当前的实例对象
static HeapObject *_swift_allocObject_(HeapMetadata const *metadata,
size_t requiredSize,
size_t requiredAlignmentMask) {
assert(isAlignmentMask(requiredAlignmentMask));
auto object = reinterpret_cast<HeapObject *>(
swift_slowAlloc(requiredSize, requiredAlignmentMask));
// NOTE: this relies on the C++17 guaranteed semantics of no null-pointer
// check on the placement new allocator which we have observed on Windows,
// Linux, and macOS.
new (object) HeapObject(metadata);
// If leak tracking is enabled, start tracking this object.
SWIFT_LEAKS_START_TRACKING_OBJECT(object);
SWIFT_RT_TRACK_INVOCATION(object, swift_allocObject);
return object;
}
image.png
敲回车后看一下本地变量:大小需要40字节,8字节对齐。
image.png这里对齐的目的是对于64位cpu而言,一次能读取8个字节(连续)。在这个过程当中连续读8个字节最快,对于创建出来的实例对象应该是8的倍数,偶数。8的倍数是为了在整个内存寻址周期的过程当中更加的具有效率,不足8的倍数会补足。目的是以空间换时间来提高访问存储效率。这里掩码为7代表的就是8字节对齐。
swift_slowAlloc
接着我们看swift_slowAlloc
这个函数:
void *swift::swift_slowAlloc(size_t size, size_t alignMask) {
void *p;
// This check also forces "default" alignment to use AlignedAlloc.
if (alignMask <= MALLOC_ALIGN_MASK) {
#if defined(__APPLE__)
p = malloc_zone_malloc(DEFAULT_ZONE(), size);
#else
p = malloc(size);
#endif
} else {
size_t alignment = (alignMask == ~(size_t(0)))
? _swift_MinAllocationAlignment
: alignMask + 1;
p = AlignedAlloc(size, alignment);
}
if (!p) swift::crash("Could not allocate memory.");
return p;
}
p这里可以看到在堆中创建size大小的空间。这个size存储我们当前的实例变量
#if defined(__APPLE__)
p = malloc_zone_malloc(DEFAULT_ZONE(), size);
#else
p = malloc(size);
swift_allocObject函数的返回值是HeapObject,意味着我们创建出来的实例变量在内存当中是HeapObject。
new (object) HeapObject(metadata);
创建HeapObject的参数是metadata,也就是通过元数据初始化HeapObject这个结构体。
HeapObject
HeapObject内容如下(c++代码):
#define SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS \
InlineRefCounts refCounts
struct HeapObject {
/// This is always a valid pointer to a metadata object.
HeapMetadata const *metadata;
SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;
#ifndef __swift__
HeapObject() = default;
// Initialize a HeapObject header as appropriate for a newly-allocated object.
constexpr HeapObject(HeapMetadata const *newMetadata)
: metadata(newMetadata)
, refCounts(InlineRefCounts::Initialized)
{ }
可以看到这函数需要metadata(元数据)和refCounts(引用计数)。可以看到metadata是一个指针类型(8字节),refCounts可以看到是一个InlineRefCounts。
InlineRefCounts、RefCounts
InlineRefCounts定义如下,RefCounts点进去可以看到是一个class。
typedef RefCounts<InlineRefCountBits> InlineRefCounts;
class RefCounts {
std::atomic<RefCountBits> refCounts;
// Out-of-line slow paths.
所以refCounts也是一个指针类型(8字节)。上面我们在VSCode调试的时候看到hotpot对象requiredSize占40字节,那么这里其实Int占8字节,String占16字节。
我们也可以通过代码直接验证
print(MemoryLayout<String>.size)
print(MemoryLayout<Int>.stride)
print(class_getInstanceSize(HotpotCat.self))
16
8
40
那么对我们的hotpot实例对象本质是一个HeapObject结构体(默认16字节大小)。相比于OC实例对象本质是结构体objc_object,他有一个isa指针默认8字节。swift相比oc多了一个refCounted。
- swift 内存分配:__allcoating_init—>swift_allocObject—>swift_allocObject—>swift_slowAlloc—>Malloc;
- Swift对象的内存结构为HeapObject,有两个属性:Metadata、RefCount默认占用16字节;
- init初始化变量,和OC中是一致的。
类对象
类结构是HeapMetadata结构,OC中类结构是objc_class。
HeapMetadata
using HeapMetadata = TargetHeapMetadata<InProcess>;
从源码中可以看到HeapMetadata是一个别名,实际是TargetHeapMetadata。
TargetHeapMetadata
template <typename Runtime>
struct TargetHeapMetadata : TargetMetadata<Runtime> {
using HeaderType = TargetHeapMetadataHeader<Runtime>;
TargetHeapMetadata() = default;
constexpr TargetHeapMetadata(MetadataKind kind)
: TargetMetadata<Runtime>(kind) {}
#if SWIFT_OBJC_INTEROP
constexpr TargetHeapMetadata(TargetAnyClassMetadata<Runtime> *isa)
: TargetMetadata<Runtime>(isa) {}
#endif
};
using HeapMetadata = TargetHeapMetadata<InProcess>;
TargetHeapMetadata是一个模板类型,接收一个参数InProcess, InProcess定义了一些需要的数据结构。看TargetHeapMetadata源码发现其中并没有属性,只有初始化方法,初始化方法是MetadataKind kind,那么我们看看他的父结构体TargetMetadata。
TargetMetadata
private:
/// The kind. Only valid for non-class metadata; getKind() must be used to get
/// the kind value.
StoredPointer Kind;
public:
/// Get the metadata kind.
MetadataKind getKind() const {
return getEnumeratedMetadataKind(Kind);
}
/// Set the metadata kind.
void setKind(MetadataKind kind) {
Kind = static_cast<StoredPointer>(kind);
}
可以看到有一个StoredPointer Kind
using StoredPointer = typename Runtime::StoredPointer;
再后头看下InProcess,可以找到
using StoredPointer = uintptr_t;
继续看uintptr_t,可以看到是unsigned long类型,那么kind就是unsigned long类型。kind其实是来区分是哪种类型的元数据。
typedef unsigned long uintptr_t;
TargetHeapMetadata中点开MetadataKind可以看到一个MetadataKind.def文件,点开.
MetadataKind.def
/// A class type.
NOMINALTYPEMETADATAKIND(Class, 0)
/// A struct type.
NOMINALTYPEMETADATAKIND(Struct, 0 | MetadataKindIsNonHeap)
/// An enum type.
/// If we add reference enums, that needs to go here.
NOMINALTYPEMETADATAKIND(Enum, 1 | MetadataKindIsNonHeap)
/// An optional type.
NOMINALTYPEMETADATAKIND(Optional, 2 | MetadataKindIsNonHeap)
/// A foreign class, such as a Core Foundation class.
METADATAKIND(ForeignClass, 3 | MetadataKindIsNonHeap)
/// A type whose value is not exposed in the metadata system.
METADATAKIND(Opaque, 0 | MetadataKindIsRuntimePrivate | MetadataKindIsNonHeap)
/// A tuple.
METADATAKIND(Tuple, 1 | MetadataKindIsRuntimePrivate | MetadataKindIsNonHeap)
/// A monomorphic function.
METADATAKIND(Function, 2 | MetadataKindIsRuntimePrivate | MetadataKindIsNonHeap)
/// An existential type.
METADATAKIND(Existential, 3 | MetadataKindIsRuntimePrivate | MetadataKindIsNonHeap)
/// A metatype.
METADATAKIND(Metatype, 4 | MetadataKindIsRuntimePrivate | MetadataKindIsNonHeap)
/// An ObjC class wrapper.
METADATAKIND(ObjCClassWrapper, 5 | MetadataKindIsRuntimePrivate | MetadataKindIsNonHeap)
/// An existential metatype.
METADATAKIND(ExistentialMetatype, 6 | MetadataKindIsRuntimePrivate | MetadataKindIsNonHeap)
/// A heap-allocated local variable using statically-generated metadata.
METADATAKIND(HeapLocalVariable, 0 | MetadataKindIsNonType)
/// A heap-allocated local variable using runtime-instantiated metadata.
METADATAKIND(HeapGenericLocalVariable,
0 | MetadataKindIsNonType | MetadataKindIsRuntimePrivate)
/// A native error object.
METADATAKIND(ErrorObject,
1 | MetadataKindIsNonType | MetadataKindIsRuntimePrivate)
这里面记录了我们当前所有元数据类型。
kind种类:
name | value |
---|---|
Class | 0x0 |
Struct | 0x200 |
Enum | 0x201 |
Optional | 0x202 |
ForeignClass | 0x202 |
Opaque | 0x300 |
Tuple | 0x301 |
Function | 0x302 |
Existential | 0x303 |
Metatype | 0x304 |
ObjCClassWrapper | 0x305 |
ExistentialMetatype | 0x306 |
HeapLocalVariable | 0x400 |
HeapGenericLocalVariable | 0x500 |
ErrorObject | 0x501 |
LastEnumerated | 0x7FF |
回到TargetMetadata,只有一个kind属性,在这个文件中我们往下找看看,706行可以看到有一个getClassObject方法,返回类型为TargetClassMetadata
const TargetClassMetadata<Runtime> *getClassObject() const;
实现为:
template<> inline const ClassMetadata *
Metadata::getClassObject() const {
switch (getKind()) {
case MetadataKind::Class: {
// Native Swift class metadata is also the class object.
return static_cast<const ClassMetadata *>(this);
}
case MetadataKind::ObjCClassWrapper: {
// Objective-C class objects are referenced by their Swift metadata wrapper.
auto wrapper = static_cast<const ObjCClassWrapperMetadata *>(this);
return wrapper->Class;
}
// Other kinds of types don't have class objects.
default:
return nullptr;
}
}
void *allocateMetadata(size_t size, size_t align);
本身是匹配kind返回对应的值,就是上表对应kind的值。如果是class那么把this也就是当前指针强转为ClassMetadata.
通过lldb po metadata 验证为class
Stop reason: exec
po metadata->getKind()
Class
po metadata->getClassObject()
0x0000000110effc70
x/8g 0x0000000110effc70
//这里就是元数据里面记录的数据了
0x110effc70: 0x0000000110effc38 0x000000011976e420
0x110effc80: 0x00007fff201d3af0 0x0000803000000000
0x110effc90: 0x0000000110f880c2 0x0000000000000002
0x110effca0: 0x0000000700000028 0x00000010000000a8
那么意味着TargetMetadata也就是TargetClassMetadata,那么我们认为的内存中的结构体也就是TargetClassMetadata。
TargetClassMetadata
那么在TargetClassMetadata中我们可以看到以下数据
/// Swift-specific class flags.
ClassFlags Flags;
/// The address point of instances of this type.
uint32_t InstanceAddressPoint;
/// The required size of instances of this type.
/// 'InstanceAddressPoint' bytes go before the address point;
/// 'InstanceSize - InstanceAddressPoint' bytes go after it.
uint32_t InstanceSize;
/// The alignment mask of the address point of instances of this type.
uint16_t InstanceAlignMask;
/// Reserved for runtime use.
uint16_t Reserved;
/// The total size of the class object, including prefix and suffix
/// extents.
uint32_t ClassSize;
/// The offset of the address point within the class object.
uint32_t ClassAddressPoint;
private:
/// An out-of-line Swift-specific description of the type, or null
/// if this is an artificial subclass. We currently provide no
/// supported mechanism for making a non-artificial subclass
/// dynamically.
TargetSignedPointer<Runtime, const TargetClassDescriptor<Runtime> * __ptrauth_swift_type_descriptor> Description;
那么它还有一个父类TargetAnyClassMetadata
TargetAnyClassMetadata
template <typename Runtime>
struct TargetAnyClassMetadata : public TargetHeapMetadata<Runtime> {
using StoredPointer = typename Runtime::StoredPointer;
using StoredSize = typename Runtime::StoredSize;
TargetAnyClassMetadata 继承了TargetHeapMetadata
template <typename Runtime>
struct TargetHeapMetadata : TargetMetadata<Runtime> {
using HeaderType = TargetHeapMetadataHeader<Runtime>;
TargetHeapMetadata() = default;
constexpr TargetHeapMetadata(MetadataKind kind)
: TargetMetadata<Runtime>(kind) {}
#if SWIFT_OBJC_INTEROP
constexpr TargetHeapMetadata(TargetAnyClassMetadata<Runtime> *isa)
: TargetMetadata<Runtime>(isa) {}
#endif
};
using HeapMetadata = TargetHeapMetadata<InProcess>;
TargetHeapMetadata又继承了TargetMetadata,TargetMetadata有一个属性kind。
经过源码的阅读我们可以得到
Metadata::Class
TargetClassMetadata(所有的属性)->TargetAnyClassMetadata(kind,superclass,cacheData)->TargetHeapMetadata->TargetMetadata(kind)
- 如果Metadata是一个class,那么kind这个枚举值返回TargetClassMetadata。
- 而TargetClassMetadata继承自TargetAnyClassMetadata,anyclass有3个属性,继承过来的kind以及superclass和cacheData
- TargetAnyClassMetadata 继承了TargetHeapMetadata
- TargetHeapMetadata 继承了TargetMetadata,他有一个属性kind
那么所有的这些就构成了我们Class内存结构(ClassMetadata + AnyClassMetadata + TargetMetadata),metadata数据结构体为:
struct swift_class_t{
void *kind; //isa, kind(unsigned long)//如果和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;
// ...
};
Swift属性
存储属性
要么是常量(let修饰),要么是变量(var修饰)
class HotpotCat {
var name: String = "Hotpot"
let age: Int = 1
}
var hotpot = HotpotCat()
对于HotpotCat中age、name来说都是我们的变量存储属性,在SIL中可以看到
class HotpotCat {
@_hasStorage @_hasInitialValue var name: String { get set }
@_hasStorage @_hasInitialValue var age: Int { get set }
@objc deinit
init()
}
image.png
0x0000000100008188 存储的是metadata,0x0000000400000003存储的是refcount,0x0000746f70746f48 0xe600000000000000 存储的是String,0x0000000000000001存储的是Int。
计算属性
不占存储空间,本质是get/set方法
如果我们给计算属性赋值会发生什么呢?
class HotpotCat {
var name: String = "Hotpot"
var age: Int {
get{
3
}
set{
age = newValue
}
}
}
var hotpot = HotpotCat()
hotpot.age = 6
print(hotpot.age)
image.png
可以看到发生了递归调用,自己调用自己。
那么写一个正确的计算属性
class HotpotCat {
var width: Double = 10;//8字节
var area: Double{//不占存储空间
get{
pow(width, 2)
}
set{
width = sqrt(newValue)
}
}
}
var hotpot = HotpotCat()
print(class_getInstanceSize(HotpotCat.self))
输出
24
这也就证明计算属性确实不占内存空间。
我们生成sil文件看看计算属性到底是什么?
swiftc -emit-sil main.swift >> ./main.sil && open main.sil
class HotpotCat {
@_hasStorage @_hasInitialValue var width: Double { get set }
var area: Double { get set }
@objc deinit
init()
}
- 可以看到存储属性使用_hasStorage修饰的,计算属性没有。
// HotpotCat.area.getter
sil hidden @$s4main9HotpotCatC4areaSdvg : $@convention(method) (@guaranteed HotpotCat) -> Double {
// %0 "self" // users: %3, %2, %1
bb0(%0 : $HotpotCat):
debug_value %0 : $HotpotCat, let, name "self", argno 1 // id: %1
%2 = class_method %0 : $HotpotCat, #HotpotCat.width!getter : (HotpotCat) -> () -> Double, $@convention(method) (@guaranteed HotpotCat) -> Double // user: %3
%3 = apply %2(%0) : $@convention(method) (@guaranteed HotpotCat) -> Double // user: %7
%4 = float_literal $Builtin.FPIEEE64, 0x4000000000000000 // 2 // user: %5
%5 = struct $Double (%4 : $Builtin.FPIEEE64) // user: %7
// function_ref pow
%6 = function_ref @pow : $@convention(c) (Double, Double) -> Double // user: %7
%7 = apply %6(%3, %5) : $@convention(c) (Double, Double) -> Double // user: %8
return %7 : $Double // id: %8
} // end sil function '$s4main9HotpotCatC4areaSdvg'
// HotpotCat.area.setter
sil hidden @$s4main9HotpotCatC4areaSdvs : $@convention(method) (Double, @guaranteed HotpotCat) -> () {
// %0 "newValue" // users: %5, %2
// %1 "self" // users: %7, %6, %3
bb0(%0 : $Double, %1 : $HotpotCat):
debug_value %0 : $Double, let, name "newValue", argno 1 // id: %2
debug_value %1 : $HotpotCat, let, name "self", argno 2 // id: %3
// function_ref sqrt
%4 = function_ref @sqrt : $@convention(c) (Double) -> Double // user: %5
%5 = apply %4(%0) : $@convention(c) (Double) -> Double // user: %7
%6 = class_method %1 : $HotpotCat, #HotpotCat.width!setter : (HotpotCat) -> (Double) -> (), $@convention(method) (Double, @guaranteed HotpotCat) -> () // user: %7
%7 = apply %6(%5, %1) : $@convention(method) (Double, @guaranteed HotpotCat) -> ()
%8 = tuple () // user: %9
return %8 : $() // id: %9
} // end sil function '$s4main9HotpotCatC4areaSdvs'
- 可以看到就是get和set方法。
- oc中的方法存储在objc_class:Method_List中,swift方法存储在metadata中。
属性观察者
willSet会在新值赋值之前调用,didSet会在新值赋值之后调用。
class HotpotCat {
var name: String = "hotpot" {
//新值存储前调用
willSet{
print("willSet newValue: \(newValue) oldValue: \(name)")
}
//新值存储后调用
didSet{
print("didSet oldValue: \(oldValue) newValue: \(name)")
}
}
}
var hotpot = HotpotCat()
hotpot.name = "cat"
willSet newValue: cat oldValue: hotpot
didSet oldValue: hotpot newValue: cat
查看一下SIL文件,我们在设置name的时候首先调用set方法,我们直接看SIL文件的name setter方法
这也就解释了为什么willSet能访问newValue和self,didSet能访问oldValue和self。
再继续看下willset(
s4main9HotpotCatC4nameSSvw
)方法的实现
sil private @$s4main9HotpotCatC4nameSSvw : $@convention(method) (@guaranteed String, @guaranteed HotpotCat) -> () {
// %0 "newValue" // users: %31, %2
// %1 "self" // users: %48, %3
bb0(%0 : $String, %1 : $HotpotCat):
debug_value %0 : $String, let, name "newValue", argno 1 // id: %2
debug_value %1 : $HotpotCat, let, name "self", argno 2 // id: %3
%4 = integer_literal $Builtin.Word, 1
可以看到newValue
是编译器自己帮我们取得,let类型。didSet同理。
那我们自己指定变量名呢?
sil private @$s4main9HotpotCatC4nameSSvw : $@convention(method) (@guaranteed String, @guaranteed HotpotCat) -> () {
// %0 "myNewValue" // users: %31, %2
// %1 "self" // users: %48, %3
bb0(%0 : $String, %1 : $HotpotCat):
debug_value %0 : $String, let, name "myNewValue", argno 1 // id: %2
debug_value %1 : $HotpotCat, let, name "self", argno 2 // id: %3
%4 = integer_literal $Builtin.Word, 1 // user: %6
可以看到SIL里面已经变成我们自己起名的变量名。
那么如果我们再init方法里面调用self.name,属性观察者会被调用么?
class HotpotCat {
var name: String = "hotpot" {
//新值存储前调用
willSet{
print("willSet newValue: \(newValue) oldValue: \(name)")
}
//新值存储后调用
didSet{
print("didSet oldValue: \(oldValue) newValue: \(name)")
}
}
init() {
//不会调用属性观察者
self.name = "cat"
}
}
var hotpot = HotpotCat()
很明显控制台没有输出,为什么呢?
init方法是做初始化用的。在这个过程中访问oldValue或者其它属性会获取到未知状态的值,所以swift禁止操作,这也就是swift安全的体现。
image.png
我们再看一段有意思的代码
class HotpotCat {
var name: String = "hotpot" {
//新值存储前调用
willSet {
print("HotpotCat name willSet")
}
//新值存储后调用
didSet {
print("HotpotCat name didSet")
}
}
var width: Double = 10
var area: Double {
get {
print("HotpotCat area get")
return pow(width, 2)
}
set {
print("HotpotCat area set")
width = sqrt(newValue)
}
}
}
class MyHotpotCat: HotpotCat {
override var name: String {
willSet {
print("MyHotpotCat name willset")
}
didSet {
print("MyHotpotCat name didSet")
}
}
override var area: Double {
willSet {
print("MyHotpotCat area willset")
}
didSet {
print("MyHotpotCat area didSet")
}
}
override init() {
super.init()
self.name = "cat"
self.area = 100
}
}
var myHotpot = MyHotpotCat()
控制台打印
MyHotpotCat name willset
HotpotCat name willSet
HotpotCat name didSet
MyHotpotCat name didSet
MyHotpotCat area willset
HotpotCat area set
MyHotpotCat area didSet
这里要注意下name的调用顺序,可以简单理解为一个栈(先进后出)。
image.png
- 定义的存储属性,可以添加属性观察者。
- 继承的存储属性,可以添加属性观察者。
- 继承的计算属性,可以添加属性观察者。
- init方法中自己本身的属性不会调用属性观察者,继承的属性会调用自己和父类的属性观察者。(self.init已经对属性做了初始化操作)
- 计算属性本身不能添加属性观察者,因为自己本身已经实现了set/get。
配置脚本生成SIL文件
每次跑命令生成SIL文件都比较麻烦,我们可以直接添加一个Target
直接将命令放在脚本中执行:
TARGETS -> Other -> Aggregate
Build Phases -> + -> New Run Script Phase
image.png
#这里需要注意路径问题,在icloud里面的路径会报错,文件和路径替换为自己的工程文件和路径。
rm -rf ${SRCROOT}/main.sil
swiftc -emit-sil ${SRCROOT}/SwiftEnum/main.swift | xcrun swift-demangle >> ./main.sil && open main.sil
附录
SIL参考文档
SIL手册
OC转Swift
参考阅读
https://swift.org/swift-compiler/#compiler-architecture
https://www.imooc.com/article/273543
https://blog.csdn.net/qq_41790914/article/details/106457729
https://www.jianshu.com/p/fb6923e3a7be
https://zhuanlan.zhihu.com/p/101898915
https://www.jianshu.com/p/440d760a7392?from=singlemessage
https://zhuanlan.zhihu.com/p/112465903
https://blog.csdn.net/wjnhub/article/details/107818429
枚举解析
LLVM相关内容:
https://zhuanlan.zhihu.com/p/102028114
https://zhuanlan.zhihu.com/p/102250532
https://zhuanlan.zhihu.com/p/102270840
https://zhuanlan.zhihu.com/p/102716482
https://zhuanlan.zhihu.com/p/103674744