前言
本篇文章會(huì)大致分析一下swift的編譯流程,這個(gè)作為了解即可,然后會(huì)重點(diǎn)分析一下swift中類的結(jié)構(gòu),這個(gè)知識(shí)點(diǎn)我們需要掌握。
一、swift編譯流程
1.1 LLVM
在了解swift編譯流程之前,我們最好清楚
LLVM是什么?請(qǐng)參考??LLVM編譯流程
LLVM是架構(gòu)編譯器的框架系統(tǒng),以C++編寫而成,用于優(yōu)化任意程序語言編寫的程序的編譯時(shí)間(compile-time)、鏈接時(shí)間(link-time)、運(yùn)行時(shí)間(run-time)以及空閑時(shí)間(idle-time)。對(duì)開發(fā)者保持開放,并兼容已有腳本。
對(duì)于我們iOS系統(tǒng),OC語言前端使用Clang編譯器,而swift語言前端使用swift編譯器,這兩個(gè)編譯器將我們寫的代碼編譯生成IR中間代碼,交給LLVM優(yōu)化器進(jìn)行優(yōu)化,接著交給代碼生成器生成機(jī)器語言,最終形成.o機(jī)器執(zhí)行文件。整個(gè)過程如下圖??

1.2 swift的編譯詳細(xì)流程
接著我們?cè)僭敿?xì)看看swift的編譯流程,如下圖??

大致分為以下幾步??
- swift源碼經(jīng)過
parse解析、ast編譯,生成AST語法樹。相關(guān)指令??
swiftc -dump-ast LGPerson.swift >> ast.swift
- 通過
SIL生成器生成SIL源碼。相關(guān)指令??
swiftc -emit-sil LGPerson.swift >> ./LGPerson.sil
- 生成
IR中間代碼。相關(guān)指令??
swiftc -emit-ir LGPerson.swift >> ir.swift
- 輸出
.o機(jī)器文件。相關(guān)指令??
swiftc -emit-object LGPerson.swift
1.3 示例查看編譯流程
在示例查看之前,我們可以在【終端】中swiftc -h查看swiftc的所有相關(guān)指令??

常用的一些指令的含義??
-dump-ast 語法和類型檢查,打印AST語法樹
-dump-parse 語法檢查,打印AST語法樹
-dump-pcm 轉(zhuǎn)儲(chǔ)有關(guān)預(yù)編譯Clang模塊的調(diào)試信息
-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 輸出一個(gè)LLVM的BC文件
-emit-executable 輸出一個(gè)可執(zhí)行文件
-emit-imported-modules 展示導(dǎo)入的模塊列表
-emit-ir 展示IR中間代碼
-emit-library 輸出一個(gè)dylib動(dòng)態(tài)庫
-emit-object 輸出一個(gè).o機(jī)器文件
-emit-pcm Emit a precompiled Clang module from a module map
-emit-sibgen 輸出一個(gè).sib的原始SIL文件
-emit-sib 輸出一個(gè).sib的標(biāo)準(zhǔn)SIL文件
-emit-silgen 展示原始SIL文件
-emit-sil 展示標(biāo)準(zhǔn)的SIL文件
-index-file 為源文件生成索引數(shù)據(jù)
-parse 解析文件
-print-ast 解析文件并打印(漂亮/簡(jiǎn)潔的)語法樹
-resolve-imports 解析import導(dǎo)入的文件
-typecheck 檢查文件類型
首先,我們創(chuàng)建一個(gè)swift demo項(xiàng)目,定義一個(gè)類LGPerson.swift類
class LGPerson {
var age: Int = 18
var name: String = "luoji"
}
let t = LGPerson()
接著,打開【終端】,進(jìn)入到項(xiàng)目的目錄

- 查看抽象語法樹:
swiftc -dump-ast LGPerson.swift??

-
生成SIL文件:swiftc -emit-sil LGPerson.swift >> ./LGPerson.sil
可以使用VSCode打開它看看??代碼量巨多

附:可以在.zshrc中做了如下配置,這樣就能在終端中指定軟件打開相應(yīng)文件
// 打開.zshrc,如果提示打不開,則使用touch .zshrc 創(chuàng)建一個(gè)
$ open .zshrc
// 添加以下別名
alias vscode='/Applications/Visual\ Studio\ Code.app/Contents/Resources/app/bin/code'
// 如果之前是創(chuàng)建的,那么還需執(zhí)行
source .zshrc
//使用
$ swiftc -emit-sil LGPerson.swift >> ./LGPerson.sil && vscode LGPerson.sil
- 如果想SIL文件高亮,需要安裝插件:
VSCode SIL??

再次打開LGPerson.sil?? 沒有那么全白了

觀察LGPerson.sil代碼,可以發(fā)現(xiàn)很多都是經(jīng)過混淆處理的,可以通過下面的指令反混淆
xcrun swift-demangle 你的混淆的代碼

1.4 SIL分析
SIL: Swift intermediate language --> swift中間語言
1.4.1 main函數(shù)入口
// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
alloc_global @$s8LGPerson1tA2ACvp // id: %2
%3 = global_addr @$s8LGPerson1tA2ACvp : $*LGPerson // user: %7
%4 = metatype $@thick LGPerson.Type // user: %6
// function_ref LGPerson.__allocating_init()
%5 = function_ref @$s8LGPersonAACABycfC : $@convention(method) (@thick LGPerson.Type) -> @owned LGPerson // user: %6
%6 = apply %5(%4) : $@convention(method) (@thick LGPerson.Type) -> @owned LGPerson // user: %7
store %6 to %3 : $*LGPerson // 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標(biāo)識(shí)當(dāng)前LGPerson.swift文件的入口函數(shù),SIL標(biāo)識(shí)符號(hào)名稱以@作為前綴。%0,%1...在SIL中也叫寄存器,類似代碼中的常量,一旦賦值后不可修改。如果SIL中還要繼續(xù)使用,就需要使用新的寄存器。
我們一句句的看
-
alloc_global @$s8LGPerson1tA2ACvp
s8LGPerson1tA2ACvp反混淆出來就是LGPerson,那么就是創(chuàng)建全局變量LGPerson -
%3 = global_addr @$s8LGPerson1tA2ACvp : $*LGPerson這句很簡(jiǎn)單,讀取全局變量LGPerson地址,賦值給%3 -
%4 = metatype $@thick LGPerson.Type讀取LGPerson的Type,賦值給%4 -
%5 = function_ref @$s8LGPersonAACABycfC : $@convention(method) (@thick LGPerson.Type) -> @owned LGPerson定義一個(gè)function_ref即函數(shù),就是%5,這個(gè)函數(shù)入?yún)⑹荓GPerson.Type -
%6 = apply %5(%4) : $@convention(method) (@thick LGPerson.Type) -> @owned LGPersonapply調(diào)用函數(shù)%5,入?yún)⒕褪?4,將返回結(jié)果賦給%6 -
store %6 to %3 : $*LGPerson將%6的結(jié)果存儲(chǔ)到%3,%3是LGPerson的地址 -
%8 = integer_literal $Builtin.Int32, 0和%9 = struct $Int32 (%8 : $Builtin.Int32)就是構(gòu)建一個(gè)Int值,最終返回return %9 : $Int32
綜上所述,main函數(shù)就是構(gòu)建了一個(gè)全局變量LGPerson,并對(duì)其Type值做了一個(gè)返回處理,那么,這個(gè)Type代表什么意思呢?后面我們會(huì)仔細(xì)分析swift類的底層結(jié)構(gòu)。
1.4.2 實(shí)例化相關(guān)代碼
// LGPerson.__allocating_init()
sil hidden [exact_self_class] @$s8LGPersonAACABycfC : $@convention(method) (@thick LGPerson.Type) -> @owned LGPerson {
// %0 "$metatype"
bb0(%0 : $@thick LGPerson.Type):
%1 = alloc_ref $LGPerson // user: %3
// function_ref LGPerson.init()
%2 = function_ref @$s8LGPersonAACABycfc : $@convention(method) (@owned LGPerson) -> @owned LGPerson // user: %3
%3 = apply %2(%1) : $@convention(method) (@owned LGPerson) -> @owned LGPerson // user: %4
return %3 : $LGPerson // id: %4
} // end sil function '$s8LGPersonAACABycfC'
同樣一句句的看
-
%1 = alloc_ref $LGPerson讀取LGPerson的alloc_ref方法地址,給%1 -
%2 = function_ref @$s8LGPersonAACABycfc : $@convention(method) (@owned LGPerson) -> @owned LGPerson讀取LGPerson.init()函數(shù)地址,給%2 -
%3 = apply %2(%1) : $@convention(method) (@owned LGPerson) -> @owned LGPerson調(diào)用alloc_ref創(chuàng)建一個(gè)LGPerson實(shí)例對(duì)象,給%3 -
return %3 : $LGPerson返回%3的實(shí)例對(duì)象
這個(gè)__allocating_init()也不難,其實(shí)就是一個(gè)簡(jiǎn)單的方法調(diào)用過程。對(duì)于這個(gè)SIL代碼,我們平時(shí)可以自己多看多分析,習(xí)慣就好了,哈哈!
二、swift類的結(jié)構(gòu)
帶著上面的問題??LGPerson中的Type 就是存儲(chǔ)的是什么值,代表什么意思?我們現(xiàn)在來看看swift的類class在底層中的結(jié)構(gòu)是什么樣的。
大家都知道,如果要?jiǎng)?chuàng)建一個(gè)對(duì)象,OC 與 swift的寫法是這樣的??
- OC:
[[LGPerosn alloc] init]
一般alloc申請(qǐng)內(nèi)存空間并創(chuàng)建對(duì)象,init對(duì)其進(jìn)行統(tǒng)一初始化處理。 - Swift:
LGPerson()
直接()就完成了對(duì)象的創(chuàng)建。
2.1 找入口
我們現(xiàn)在就來看看LGPerson()在匯編層是調(diào)用了哪些函數(shù)?
新建一個(gè)SwiftDemo項(xiàng)目工程,在ViewController.swift中添加下面的代碼,并打上斷點(diǎn)??

運(yùn)行項(xiàng)目,查看匯編??

我們找到了對(duì)象的初始化入口??函數(shù)__allocating_init(),接著我們?cè)?code>__allocating_init()這一行打上斷點(diǎn),按住control按鍵 stepinto進(jìn)入查看??

定位到了swift_allocObject,同理還是在swift_allocObject這行加上斷點(diǎn),stepinto進(jìn)入查看??

沒找到實(shí)用的信息,那么換種思路,添加符號(hào)斷點(diǎn)swift_allocObject,再次run??

接著,添加符號(hào)斷點(diǎn)swift_slowAlloc??

最終來到了比較熟悉的malloc_zone_malloc。
至此,swift實(shí)例對(duì)象的創(chuàng)建流程??
__allocating_init -> swift_allocObject -> _swift_allocObject_ -> swift_slowAlloc -> malloc_zone_malloc
2.2 查看源碼驗(yàn)證流程
首先,用VSCode打開swift源碼項(xiàng)目,搜索__allocating_init,打上斷點(diǎn)??

接著,run項(xiàng)目,在終端輸入代碼??
class LGPerson {
var age: Int = 18
var name: String = "luoji"
}
回車,再輸入var t = LGPerson(),會(huì)觸發(fā)斷點(diǎn)??

我們可以看左上角,發(fā)現(xiàn)requiredSize空間大小值是40,requiredAlignmentMask字節(jié)對(duì)齊值是7。
接著我們順著流程來到swift_slowAlloc??

然后返回申請(qǐng)空間后的地址,即返回指針p。再回到_swift_allocObject_,那么接著執(zhí)行
-
auto object = reinterpret_cast<HeapObject *>( swift_slowAlloc(requiredSize, requiredAlignmentMask));
通過reinterpret_cast將指針p轉(zhuǎn)換成HeapObject類型,然后執(zhí)行 -
new (object) HeapObject(metadata);
因?yàn)閛bject是強(qiáng)轉(zhuǎn)的HeapObject類型,其里面值仍是一個(gè)指向內(nèi)存空間的對(duì)象指針,所以這里執(zhí)行HeapObject(metadata)做一個(gè)初始化操作,這樣object就是真正的HeapObject結(jié)構(gòu)了。
整體流程圖??

2.2.1 類的大小
接下來我們具體看看,類的大小size是怎么算出來的?
首先,我們先在lldb中打印查看一下大小,打開Xcode,新建一個(gè)swift項(xiàng)目,加入代碼
import UIKit
class LGPerson {
var age: Int = 18
var name: String = "luoji"
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
print("Int32 大小: \(MemoryLayout<Int32>.size)")
print("Int64 大小: \(MemoryLayout<Int64>.size)")
print("Int 大小: \(MemoryLayout<Int>.size)")
print("Srtring 大小: \(MemoryLayout<String>.size)")
print("LGPerson 大小: \(class_getInstanceSize(LGPerson.self))")
}
}
run??

我們通過MemoryLayout得知Int大小是8,String大小是16,但是通過class_getInstanceSize得知類LGPerson的大小卻是40,40-16-8=16,這額外的16字節(jié)大小是什么?
之前我們?cè)谠创a調(diào)試的時(shí)候,也發(fā)現(xiàn)類LGPerson初始化實(shí)例對(duì)象時(shí)的requiredSize空間大小值也是40,如下圖??

額外的16字節(jié)從何而來?
我們進(jìn)入源碼工程,查看HeapObject結(jié)構(gòu)體??

結(jié)構(gòu)體大小主要看成員變量,HeapObject結(jié)構(gòu)體有兩個(gè)成員變量元數(shù)據(jù)metadata 和 引用計(jì)數(shù)refCounts。
metadata
是 HeapMetedata類型,還是查看源碼??
template <typename Target> struct TargetHeapMetadata;
using HeapMetadata = TargetHeapMetadata<InProcess>;
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
};
TargetHeapMetadata->TargetMetadata,也是struct結(jié)構(gòu)體。接著看TargetMetadata??

最后我們看看StoredPointer源碼,發(fā)現(xiàn)搜不到,但是根據(jù)注釋
/// The kind. Only valid for non-class metadata; getKind() must be used to get
/// the kind value.
StoredPointer Kind;
我們發(fā)現(xiàn), 可以通過 getKind()方法入?yún)⒅??
/// 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是調(diào)用getEnumeratedMetadataKind,而入?yún)?code>kind是uint64_t類型,占8字節(jié)大小,所以元數(shù)據(jù)metadata是8字節(jié)大小。
MetadataKind
這時(shí)我們注意到,getKind()返回值是MetadataKind類型,我們進(jìn)入源碼看看,里面有一個(gè)#include "MetadataKind.def",點(diǎn)擊進(jìn)入,其中記錄了所有類型的元數(shù)據(jù),所以kind種類總結(jié)如下:
| 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 |
接著我們回到TargetMetaData結(jié)構(gòu)體定義中,找方法getClassObject,在該方法中去匹配kind返回值是TargetClassMetadata類型??


如果是Class,則直接對(duì)this(當(dāng)前指針,即metadata)強(qiáng)轉(zhuǎn)為ClassMetadata。所以,TargetMetadata 和 TargetClassMetadata 本質(zhì)上是一樣的,因?yàn)樵趦?nèi)存結(jié)構(gòu)中,可以直接進(jìn)行指針的轉(zhuǎn)換,那么我們可以這么認(rèn)為??結(jié)構(gòu)體其實(shí)就是TargetClassMetadata。
接著,我們?cè)倏纯?code>TargetClassMetadata里還有哪些成員??
template <typename Runtime>
struct TargetClassMetadata : public TargetAnyClassMetadata<Runtime> {
...
//swift特有的標(biāo)志
ClassFlags Flags;
//實(shí)力對(duì)象內(nèi)存大小
uint32_t InstanceSize;
//實(shí)例對(duì)象內(nèi)存對(duì)齊方式
uint16_t InstanceAlignMask;
//運(yùn)行時(shí)保留字段
uint16_t Reserved;
//類的內(nèi)存大小
uint32_t ClassSize;
//類的內(nèi)存首地址
uint32_t ClassAddressPoint;
...
}
然后我們看看繼承鏈?? TargetClassMetadata繼承TargetAnyClassMetadata繼承TargetHeapMetadata。
template <typename Runtime>
struct TargetAnyClassMetadata : public TargetHeapMetadata<Runtime> {
...
ConstTargetMetadataPointer<Runtime, swift::TargetClassMetadata> Superclass;
TargetPointer<Runtime, void> CacheData[2];
StoredSize Data;
...
}
至此,當(dāng)metadata的kind為Class時(shí),有如下繼承鏈:

- 類class在底層中的實(shí)際類型是
TargetClassMetadata,而TargetMetaData中只有一個(gè)屬性kind,TargetAnyClassMetaData中有4個(gè)屬性,分別是kind, superclass,cacheData、data(圖中未標(biāo)出) - 當(dāng)前Class在內(nèi)存中所存放的屬性由
TargetClassMetadata屬性 + TargetAnyClassMetaData屬性 + TargetMetaData屬性構(gòu)成,所以得出的metadata的數(shù)據(jù)結(jié)構(gòu)體如下所示
struct swift_class_t: NSObject{
void *kind;//相當(dāng)于OC中的isa,kind的實(shí)際類型是unsigned long
void *superClass;
void *cacheData;
void *data;
uint32_t flags; //4字節(jié)
uint32_t instanceAddressOffset;//4字節(jié)
uint32_t instanceSize;//4字節(jié)
uint16_t instanceAlignMask;//2字節(jié)
uint16_t reserved;//2字節(jié)
uint32_t classSize;//4字節(jié)
uint32_t classAddressOffset;//4字節(jié)
void *description;
...
}
refCounts
接著我們看看refCounts,refCounts是InlineRefCounts類型,搜索InlineRefCounts??
typedef RefCounts<InlineRefCountBits> InlineRefCounts;
InlineRefCounts是RefCounts類型,搜索??

RefCounts是class類型,確切的說,refCounts是個(gè)指針,占8字節(jié)大小。
綜上所述
- swift類本質(zhì)是
HeapObjectHeapObject默認(rèn)大小為16字節(jié): metadata(struct)8字節(jié)和refCounts(class)8字節(jié)- LGPerson的
age(Int)占8字節(jié),name(String)占16字節(jié),加上上面的,所以LGPerson的size為40字節(jié)
總結(jié)
本篇文章首先大致講述了Swift的編譯流程,與OC最大的不同在于,swift在編譯過程中會(huì)生成SIL中間代碼,通過對(duì)中間代碼的分析,我明知道了Swift實(shí)例對(duì)象的初始化會(huì)調(diào)用__allocating_init()方法,接著我們通過LGPerson樣例分析了Swift類的底層結(jié)構(gòu)HeapObject,其默認(rèn)包含了metadata(struct) 和refCounts(class),共16字節(jié)大小。
