Swift編譯
Swift編譯究竟是一個(gè)怎樣的過程呢?從Swift語言到cpu能夠識(shí)別的機(jī)器碼這之間究竟經(jīng)過了哪些步驟呢?
我們先寫一個(gè)簡單的HotpotCat類如下:
import Foundation
class HotpotCat {
var name: String = "Hotpot"
var age: Int = 1
}
var hotpot = HotpotCat()
通過默認(rèn)的初始化器我們構(gòu)造了一個(gè)實(shí)例對(duì)象hotpot,類比OC在這個(gè)過程中alloc(內(nèi)存分配) init(初始化)。那么Swift這個(gè)默認(rèn)的初始化器到底做了什么操作?這里我們需要了解SIL(Swift intermediate language),再閱讀SIL之前,先了解下什么是SIL。從字面意思理解它是一門中間語言。
iOS開發(fā)的語言不管是OC還是Swift后端都是通過LLVM進(jìn)行編譯的,編譯過程如下:

OC通過clang編譯器,編譯成IR,然后生成可執(zhí)行文件.o(也就會(huì)機(jī)器碼);
Swift通過Swift編譯器編譯成IR,然后生成可執(zhí)行文件。
那么一個(gè)Swift文件編譯的整個(gè)過程中都經(jīng)歷了什么呢?

- 1.Parse :解析器是一個(gè)簡易的遞歸下降解析器(在 lib/Parse 中實(shí)現(xiàn)),并帶有完整手動(dòng)編碼的詞法分析器。通過parse進(jìn)行詞法分析;
- 2.Semantic Analysis: 語義分析階段(在 lib/Sema 中實(shí)現(xiàn))負(fù)責(zé)獲取已解析的 AST(抽象語法樹)并將其轉(zhuǎn)換為格式正確且類型檢查完備的 AST,以及在源代碼中提示出現(xiàn)語義問題的警告或錯(cuò)誤。語義分析包含類型推斷,如果可以成功推導(dǎo)出類型,則表明此時(shí)從已經(jīng)經(jīng)過類型檢查的最終 AST 生成代碼是安全的;
- 3.Clang Importer:Clang 導(dǎo)入器(Clang Importer):Clang 導(dǎo)入器(在 lib/ClangImporter 中實(shí)現(xiàn))負(fù)責(zé)導(dǎo)入 Clang 模塊,并將導(dǎo)出的 C 或 Objective-C API 映射到相應(yīng)的 Swift API 中。最終導(dǎo)入的 AST 可以被語義分析引用。
- 4.SIL 生成(SIL Generation):Swift 中間語言(Swift Intermediate Language,SIL)是一門高級(jí)且專用于 Swift 的中間語言,適用于對(duì) Swift 代碼的進(jìn)一步分析和優(yōu)化。SIL 生成階段(在 lib/SILGen 中實(shí)現(xiàn))將經(jīng)過類型檢查的 AST 弱化為所謂的「原始」SIL。SIL 的設(shè)計(jì)在 docs/SIL.rst 有所描述。這個(gè)過程生成RAW SIL(原生SIL,代碼量很大,不會(huì)進(jìn)行類型檢查,代碼優(yōu)化)
- 5.SIL 保證轉(zhuǎn)換(SIL Guaranteed Transformations):SIL 保證轉(zhuǎn)換階段(在 lib/SILOptimizer/Mandatory中實(shí)現(xiàn))負(fù)責(zé)執(zhí)行額外且影響程序正確性的數(shù)據(jù)流診斷(比如使用未初始化的變量)。這些轉(zhuǎn)換的最終結(jié)果是「規(guī)范」SIL。
- 6.SIL 優(yōu)化(SIL Optimizations):SIL 優(yōu)化階段(在 lib/Analysis、lib/ARC、lib/LoopTransforms 以及 lib/Transforms 中實(shí)現(xiàn))負(fù)責(zé)對(duì)程序執(zhí)行額外的高級(jí)且專用于 Swift 的優(yōu)化,包括(例如)自動(dòng)引用計(jì)數(shù)優(yōu)化、去虛擬化、以及通用的專業(yè)化
通過-emit-sil命令生成優(yōu)化過后的 SIL Opt Canonical SIL。這個(gè)也是我們一般閱讀的SIL代碼; - 7.LLVM IR 生成(LLVM IR Generation):IR 生成階段(在 lib/IRGen 中實(shí)現(xiàn))將 SIL 弱化為 LLVM LR,此時(shí) LLVM 可以繼續(xù)優(yōu)化并生成機(jī)器碼。
通過IRGen生成 IR; - 8.然后生成機(jī)器碼交給機(jī)器進(jìn)行識(shí)別。
Swift和OC的區(qū)別也就是中間SIL生成的這一部分。我們一般閱讀經(jīng)過優(yōu)化后的SIL文件。
swift在編譯的過程中使用的前段編譯器是swiftc,和OC中使用的Clang是有區(qū)別的??梢酝ㄟ^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
從以上可以看出這兩個(gè)都生成了抽象語法樹,-dump-ast多了類型檢查。我們摘錄一段對(duì)比就可以看出一個(gè)bind Swift String,一個(gè)為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

-emit-sil
Emit canonical SIL file(s),經(jīng)過優(yōu)化處理的SIL文件
swiftc -emit-sil main.swift >> ./main.sil

對(duì)比兩者可以看到經(jīng)過優(yōu)化的SIL多了引用計(jì)數(shù)相關(guān)的操作,那么猜測引用計(jì)數(shù)是在SIL優(yōu)化這一步進(jìn)行處理的。
-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),生成字節(jié)碼二進(jìn)制
swiftc -emit-bc main.swift >> ./main.bc
-o
生成可執(zhí)行的二進(jìn)制文件
swiftc -o main.o main.swift
這里main.swift文件是一個(gè)只依賴了Foundation庫的文件,直接使用-emit就可以了,這么直接編譯iOS項(xiàng)目中的Swift源文件會(huì)報(bào)錯(cuò),因?yàn)橐蕾嚵薝IKit或其它庫,需要額外指定-target和-sdk。
- sdk 直接指定Xcode對(duì)應(yīng)的SDK.
//模擬器 /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk` //真機(jī) /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可以通過命令代替真實(shí)路徑,當(dāng)然真實(shí)路徑也沒問題,以下分別對(duì)應(yīng)模擬器、真機(jī)、mac 路徑:
xcrun --show-sdk-path --sdk iphonesimulator/iphoneos/macosx
- target (
指令集-apple-ios版本/指令集-apple-ios版本-simulator)
這里在操作時(shí)用mac/iphone代替apple也是可以的,分別指代mac和iphone平臺(tái)。當(dāng)然TV和watch也同理。
對(duì)應(yīng)目標(biāo)平臺(tái)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 //真機(jī) 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
更詳細(xì)的介紹可以觀看官方講解視頻
SIL分析
接著上面的HotpotCat例子,我們直接在終端生成SIL文件并保存為main.sil查看。(當(dāng)然也可以嘗試其它命令-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有兩個(gè)存儲(chǔ)屬性,有一個(gè)init方法,有一個(gè)objc標(biāo)識(shí)的deinit方法。
main分析
繼續(xù)看sil文件會(huì)找到一個(gè)main函數(shù),這個(gè)main函數(shù)其實(shí)就是swift隱藏的main函數(shù),swift不過進(jìn)行了省略而已。
// 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中以@作為標(biāo)識(shí)符名稱前綴
- @mian 標(biāo)識(shí)這是swift入口函數(shù);
- @convention(c)標(biāo)識(shí)這是一個(gè)C函數(shù),有兩個(gè)參數(shù)Int32和UnsafeMutablePointer的指針,返回Int32;
- alloc_global分配一個(gè)全局變量s4main6hotpotAA9HotpotCatCvp,這個(gè)變量也就是變量
hotpot,名字是經(jīng)過混寫之后的(name manager)??梢酝ㄟ^xcrun去還原查看。
xcrun swift-demangle s4main6hotpotAA9HotpotCatCvp
$s4main6hotpotAA9HotpotCatCvp ---> main.hotpot : main.HotpotCat
- %3~%9表示當(dāng)前寄存器,這里的寄存器和
register read讀取的寄存器不是一個(gè)東西,這里寄存器是虛擬的會(huì)一直遞增,可以理解為常量,經(jīng)過賦值之后不會(huì)再改變。在運(yùn)行的時(shí)候會(huì)直接對(duì)應(yīng)到真實(shí)的寄存器。 - global_addr 拿到全局變量
hotpot的地址賦值給%3。 - metatype 拿到HotpotCat的 Metadata 賦值給%4。也就是元類型。
- function_ref 拿到函數(shù)
s4main9HotpotCatCACycfC的地址給到%5。這個(gè)函數(shù)我們xcrun看一下其實(shí)是$s4main9HotpotCatCACycfC ---> main.HotpotCat.__allocating_init() -> main.HotpotCat。當(dāng)然SIL里面也有注釋。 - apply %5(%4)就是調(diào)用__allocating_init,參數(shù)是我們的元類型。返回值為我們要的實(shí)例變量。
- store 將得到的實(shí)例變量%6給到%3,也就是將實(shí)例變量地址放到全局變量中。
- 最后也就是構(gòu)建一個(gè)0返回,相當(dāng)于main函數(shù)的 retern 0。
Swift實(shí)例對(duì)象
__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 創(chuàng)建HotpotCat實(shí)例對(duì)象(在堆上分配內(nèi)存空間),引用計(jì)數(shù)為1。alloc_ref
官方原文:
Allocates an object of reference type T. The object will be initialized with retain count 1;
- function_ref 獲取init方法,這里注意
s4main9HotpotCatCACycfc與s4main9HotpotCatCACycfC最后一個(gè)字母不同,xcrun驗(yàn)證一下確實(shí)是init()方法。 - apply 調(diào)用init方法,初始化實(shí)例變量,參數(shù)是實(shí)例變量地址,最后返回。
xcode斷點(diǎn)驗(yàn)證
我們可以設(shè)置一個(gè)符號(hào)斷點(diǎn)(__allocating_init)驗(yàn)證一下:

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源碼驗(yàn)證
同樣可以通過VSCode調(diào)試源碼,在swift的REPL(命令交互行)

swift_allocObject
在定義變量hotpot敲回車前,我們給_swift_allocObject_加個(gè)斷點(diǎn):
這個(gè)方法最終是創(chuàng)建我們當(dāng)前的實(shí)例對(duì)象
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;
}

敲回車后看一下本地變量:大小需要40字節(jié),8字節(jié)對(duì)齊。
這里對(duì)齊的目的是對(duì)于64位cpu而言,一次能讀取8個(gè)字節(jié)(連續(xù))。在這個(gè)過程當(dāng)中連續(xù)讀8個(gè)字節(jié)最快,對(duì)于創(chuàng)建出來的實(shí)例對(duì)象應(yīng)該是8的倍數(shù),偶數(shù)。8的倍數(shù)是為了在整個(gè)內(nèi)存尋址周期的過程當(dāng)中更加的具有效率,不足8的倍數(shù)會(huì)補(bǔ)足。目的是以空間換時(shí)間來提高訪問存儲(chǔ)效率。這里掩碼為7代表的就是8字節(jié)對(duì)齊。

swift_slowAlloc
接著我們看swift_slowAlloc這個(gè)函數(shù):
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這里可以看到在堆中創(chuàng)建size大小的空間。這個(gè)size存儲(chǔ)我們當(dāng)前的實(shí)例變量
#if defined(__APPLE__)
p = malloc_zone_malloc(DEFAULT_ZONE(), size);
#else
p = malloc(size);
swift_allocObject函數(shù)的返回值是HeapObject,意味著我們創(chuàng)建出來的實(shí)例變量在內(nèi)存當(dāng)中是HeapObject。
new (object) HeapObject(metadata);
創(chuàng)建HeapObject的參數(shù)是metadata,也就是通過元數(shù)據(jù)初始化HeapObject這個(gè)結(jié)構(gòu)體。
HeapObject
HeapObject內(nèi)容如下(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)
{ }
可以看到這函數(shù)需要metadata(元數(shù)據(jù))和refCounts(引用計(jì)數(shù))??梢钥吹絤etadata是一個(gè)指針類型(8字節(jié)),refCounts可以看到是一個(gè)InlineRefCounts。
InlineRefCounts、RefCounts
InlineRefCounts定義如下,RefCounts點(diǎn)進(jìn)去可以看到是一個(gè)class。
typedef RefCounts<InlineRefCountBits> InlineRefCounts;
class RefCounts {
std::atomic<RefCountBits> refCounts;
// Out-of-line slow paths.
所以refCounts也是一個(gè)指針類型(8字節(jié))。上面我們?cè)赩SCode調(diào)試的時(shí)候看到hotpot對(duì)象requiredSize占40字節(jié),那么這里其實(shí)Int占8字節(jié),String占16字節(jié)。
我們也可以通過代碼直接驗(yàn)證
print(MemoryLayout<String>.size)
print(MemoryLayout<Int>.stride)
print(class_getInstanceSize(HotpotCat.self))
16
8
40
那么對(duì)我們的hotpot實(shí)例對(duì)象本質(zhì)是一個(gè)HeapObject結(jié)構(gòu)體(默認(rèn)16字節(jié)大小)。相比于OC實(shí)例對(duì)象本質(zhì)是結(jié)構(gòu)體objc_object,他有一個(gè)isa指針默認(rèn)8字節(jié)。swift相比oc多了一個(gè)refCounted。
- swift 內(nèi)存分配:__allcoating_init—>swift_allocObject—>swift_allocObject—>swift_slowAlloc—>Malloc;
- Swift對(duì)象的內(nèi)存結(jié)構(gòu)為HeapObject,有兩個(gè)屬性:Metadata、RefCount默認(rèn)占用16字節(jié);
- init初始化變量,和OC中是一致的。
類對(duì)象
類結(jié)構(gòu)是HeapMetadata結(jié)構(gòu),OC中類結(jié)構(gòu)是objc_class。
HeapMetadata
using HeapMetadata = TargetHeapMetadata<InProcess>;
從源碼中可以看到HeapMetadata是一個(gè)別名,實(shí)際是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是一個(gè)模板類型,接收一個(gè)參數(shù)InProcess, InProcess定義了一些需要的數(shù)據(jù)結(jié)構(gòu)??碩argetHeapMetadata源碼發(fā)現(xiàn)其中并沒有屬性,只有初始化方法,初始化方法是MetadataKind kind,那么我們看看他的父結(jié)構(gòu)體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);
}
可以看到有一個(gè)StoredPointer Kind
using StoredPointer = typename Runtime::StoredPointer;
再后頭看下InProcess,可以找到
using StoredPointer = uintptr_t;
繼續(xù)看uintptr_t,可以看到是unsigned long類型,那么kind就是unsigned long類型。kind其實(shí)是來區(qū)分是哪種類型的元數(shù)據(jù)。
typedef unsigned long uintptr_t;
TargetHeapMetadata中點(diǎn)開MetadataKind可以看到一個(gè)MetadataKind.def文件,點(diǎn)開.
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)
這里面記錄了我們當(dāng)前所有元數(shù)據(jù)類型。
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,只有一個(gè)kind屬性,在這個(gè)文件中我們往下找看看,706行可以看到有一個(gè)getClassObject方法,返回類型為TargetClassMetadata
const TargetClassMetadata<Runtime> *getClassObject() const;
實(shí)現(xiàn)為:
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返回對(duì)應(yīng)的值,就是上表對(duì)應(yīng)kind的值。如果是class那么把this也就是當(dāng)前指針強(qiáng)轉(zhuǎn)為ClassMetadata.
通過lldb po metadata 驗(yàn)證為class
Stop reason: exec
po metadata->getKind()
Class
po metadata->getClassObject()
0x0000000110effc70
x/8g 0x0000000110effc70
//這里就是元數(shù)據(jù)里面記錄的數(shù)據(jù)了
0x110effc70: 0x0000000110effc38 0x000000011976e420
0x110effc80: 0x00007fff201d3af0 0x0000803000000000
0x110effc90: 0x0000000110f880c2 0x0000000000000002
0x110effca0: 0x0000000700000028 0x00000010000000a8
那么意味著TargetMetadata也就是TargetClassMetadata,那么我們認(rèn)為的內(nèi)存中的結(jié)構(gòu)體也就是TargetClassMetadata。
TargetClassMetadata
那么在TargetClassMetadata中我們可以看到以下數(shù)據(jù)
/// 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;
那么它還有一個(gè)父類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有一個(gè)屬性kind。
經(jīng)過源碼的閱讀我們可以得到
Metadata::Class
TargetClassMetadata(所有的屬性)->TargetAnyClassMetadata(kind,superclass,cacheData)->TargetHeapMetadata->TargetMetadata(kind)
- 如果Metadata是一個(gè)class,那么kind這個(gè)枚舉值返回TargetClassMetadata。
- 而TargetClassMetadata繼承自TargetAnyClassMetadata,anyclass有3個(gè)屬性,繼承過來的kind以及superclass和cacheData
- TargetAnyClassMetadata 繼承了TargetHeapMetadata
- TargetHeapMetadata 繼承了TargetMetadata,他有一個(gè)屬性kind
那么所有的這些就構(gòu)成了我們Class內(nèi)存結(jié)構(gòu)(ClassMetadata + AnyClassMetadata + TargetMetadata),metadata數(shù)據(jù)結(jié)構(gòu)體為:
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屬性
存儲(chǔ)屬性
要么是常量(let修飾),要么是變量(var修飾)
class HotpotCat {
var name: String = "Hotpot"
let age: Int = 1
}
var hotpot = HotpotCat()
對(duì)于HotpotCat中age、name來說都是我們的變量存儲(chǔ)屬性,在SIL中可以看到
class HotpotCat {
@_hasStorage @_hasInitialValue var name: String { get set }
@_hasStorage @_hasInitialValue var age: Int { get set }
@objc deinit
init()
}

0x0000000100008188 存儲(chǔ)的是metadata,0x0000000400000003存儲(chǔ)的是refcount,0x0000746f70746f48 0xe600000000000000 存儲(chǔ)的是String,0x0000000000000001存儲(chǔ)的是Int。
計(jì)算屬性
不占存儲(chǔ)空間,本質(zhì)是get/set方法
如果我們給計(jì)算屬性賦值會(huì)發(fā)生什么呢?
class HotpotCat {
var name: String = "Hotpot"
var age: Int {
get{
3
}
set{
age = newValue
}
}
}
var hotpot = HotpotCat()
hotpot.age = 6
print(hotpot.age)

可以看到發(fā)生了遞歸調(diào)用,自己調(diào)用自己。
那么寫一個(gè)正確的計(jì)算屬性
class HotpotCat {
var width: Double = 10;//8字節(jié)
var area: Double{//不占存儲(chǔ)空間
get{
pow(width, 2)
}
set{
width = sqrt(newValue)
}
}
}
var hotpot = HotpotCat()
print(class_getInstanceSize(HotpotCat.self))
輸出
24
這也就證明計(jì)算屬性確實(shí)不占內(nèi)存空間。
我們生成sil文件看看計(jì)算屬性到底是什么?
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()
}
- 可以看到存儲(chǔ)屬性使用_hasStorage修飾的,計(jì)算屬性沒有。
// 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中的方法存儲(chǔ)在objc_class:Method_List中,swift方法存儲(chǔ)在metadata中。
屬性觀察者
willSet會(huì)在新值賦值之前調(diào)用,didSet會(huì)在新值賦值之后調(diào)用。
class HotpotCat {
var name: String = "hotpot" {
//新值存儲(chǔ)前調(diào)用
willSet{
print("willSet newValue: \(newValue) oldValue: \(name)")
}
//新值存儲(chǔ)后調(diào)用
didSet{
print("didSet oldValue: \(oldValue) newValue: \(name)")
}
}
}
var hotpot = HotpotCat()
hotpot.name = "cat"
willSet newValue: cat oldValue: hotpot
didSet oldValue: hotpot newValue: cat
查看一下SIL文件,我們?cè)谠O(shè)置name的時(shí)候首先調(diào)用set方法,我們直接看SIL文件的name setter方法

這也就解釋了為什么willSet能訪問newValue和self,didSet能訪問oldValue和self。
再繼續(xù)看下willset(
s4main9HotpotCatC4nameSSvw)方法的實(shí)現(xiàn)
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是編譯器自己幫我們?nèi)〉?,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里面已經(jīng)變成我們自己起名的變量名。
那么如果我們?cè)賗nit方法里面調(diào)用self.name,屬性觀察者會(huì)被調(diào)用么?
class HotpotCat {
var name: String = "hotpot" {
//新值存儲(chǔ)前調(diào)用
willSet{
print("willSet newValue: \(newValue) oldValue: \(name)")
}
//新值存儲(chǔ)后調(diào)用
didSet{
print("didSet oldValue: \(oldValue) newValue: \(name)")
}
}
init() {
//不會(huì)調(diào)用屬性觀察者
self.name = "cat"
}
}
var hotpot = HotpotCat()
很明顯控制臺(tái)沒有輸出,為什么呢?
init方法是做初始化用的。在這個(gè)過程中訪問oldValue或者其它屬性會(huì)獲取到未知狀態(tài)的值,所以swift禁止操作,這也就是swift安全的體現(xiàn)。

我們?cè)倏匆欢斡幸馑嫉拇a
class HotpotCat {
var name: String = "hotpot" {
//新值存儲(chǔ)前調(diào)用
willSet {
print("HotpotCat name willSet")
}
//新值存儲(chǔ)后調(diào)用
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()
控制臺(tái)打印
MyHotpotCat name willset
HotpotCat name willSet
HotpotCat name didSet
MyHotpotCat name didSet
MyHotpotCat area willset
HotpotCat area set
MyHotpotCat area didSet
這里要注意下name的調(diào)用順序,可以簡單理解為一個(gè)棧(先進(jìn)后出)。

- 定義的存儲(chǔ)屬性,可以添加屬性觀察者。
- 繼承的存儲(chǔ)屬性,可以添加屬性觀察者。
- 繼承的計(jì)算屬性,可以添加屬性觀察者。
- init方法中自己本身的屬性不會(huì)調(diào)用屬性觀察者,繼承的屬性會(huì)調(diào)用自己和父類的屬性觀察者。(self.init已經(jīng)對(duì)屬性做了初始化操作)
- 計(jì)算屬性本身不能添加屬性觀察者,因?yàn)樽约罕旧硪呀?jīng)實(shí)現(xiàn)了set/get。
配置腳本生成SIL文件
每次跑命令生成SIL文件都比較麻煩,我們可以直接添加一個(gè)Target直接將命令放在腳本中執(zhí)行:
TARGETS -> Other -> Aggregate

Build Phases -> + -> New Run Script Phase
#這里需要注意路徑問題,在icloud里面的路徑會(huì)報(bào)錯(cuò),文件和路徑替換為自己的工程文件和路徑。
rm -rf ${SRCROOT}/main.sil
swiftc -emit-sil ${SRCROOT}/SwiftEnum/main.swift | xcrun swift-demangle >> ./main.sil && open main.sil
附錄
SIL參考文檔
SIL手冊(cè)
OC轉(zhuǎn)Swift
參考閱讀
https://swift.org/swift-compiler/#compiler-architecture
https://www.imooc.com/article/273543
https://blog.csdn.net/qq_41790914/article/details/106457729
http://www.itdecent.cn/p/fb6923e3a7be
https://zhuanlan.zhihu.com/p/101898915
http://www.itdecent.cn/p/440d760a7392?from=singlemessage
https://zhuanlan.zhihu.com/p/112465903
https://blog.csdn.net/wjnhub/article/details/107818429
枚舉解析
LLVM相關(guān)內(nèi)容:
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