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

1.2 swift的編譯詳細流程
接著我們再詳細看看swift的編譯流程,如下圖??

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

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

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

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

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

運行項目,查看匯編??

我們找到了對象的初始化入口??函數(shù)__allocating_init(),接著我們在__allocating_init()這一行打上斷點,按住control按鍵 stepinto進入查看??

定位到了swift_allocObject,同理還是在swift_allocObject這行加上斷點,stepinto進入查看??

沒找到實用的信息,那么換種思路,添加符號斷點swift_allocObject,再次run??

接著,添加符號斷點swift_slowAlloc??

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

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

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

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

2.2.1 類的大小
接下來我們具體看看,類的大小size是怎么算出來的?
首先,我們先在lldb中打印查看一下大小,打開Xcode,新建一個swift項目,加入代碼
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é)大小是什么?
之前我們在源碼調(diào)試的時候,也發(fā)現(xiàn)類LGPerson初始化實例對象時的requiredSize空間大小值也是40,如下圖??

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

結構體大小主要看成員變量,HeapObject結構體有兩個成員變量元數(shù)據(jù)metadata 和 引用計數(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結構體。接著看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
這時我們注意到,getKind()返回值是MetadataKind類型,我們進入源碼看看,里面有一個#include "MetadataKind.def",點擊進入,其中記錄了所有類型的元數(shù)據(jù),所以kind種類總結如下:
| 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結構體定義中,找方法getClassObject,在該方法中去匹配kind返回值是TargetClassMetadata類型??


如果是Class,則直接對this(當前指針,即metadata)強轉為ClassMetadata。所以,TargetMetadata 和 TargetClassMetadata 本質上是一樣的,因為在內(nèi)存結構中,可以直接進行指針的轉換,那么我們可以這么認為??結構體其實就是TargetClassMetadata。
接著,我們再看看TargetClassMetadata里還有哪些成員??
template <typename Runtime>
struct TargetClassMetadata : public TargetAnyClassMetadata<Runtime> {
...
//swift特有的標志
ClassFlags Flags;
//實力對象內(nèi)存大小
uint32_t InstanceSize;
//實例對象內(nèi)存對齊方式
uint16_t InstanceAlignMask;
//運行時保留字段
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;
...
}
至此,當metadata的kind為Class時,有如下繼承鏈:

- 類class在底層中的實際類型是
TargetClassMetadata,而TargetMetaData中只有一個屬性kind,TargetAnyClassMetaData中有4個屬性,分別是kind, superclass,cacheData、data(圖中未標出) - 當前Class在內(nèi)存中所存放的屬性由
TargetClassMetadata屬性 + TargetAnyClassMetaData屬性 + TargetMetaData屬性構成,所以得出的metadata的數(shù)據(jù)結構體如下所示
struct swift_class_t: NSObject{
void *kind;//相當于OC中的isa,kind的實際類型是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是個指針,占8字節(jié)大小。
綜上所述
- swift類本質是
HeapObjectHeapObject默認大小為16字節(jié): metadata(struct)8字節(jié)和refCounts(class)8字節(jié)- LGPerson的
age(Int)占8字節(jié),name(String)占16字節(jié),加上上面的,所以LGPerson的size為40字節(jié)
總結
本篇文章首先大致講述了Swift的編譯流程,與OC最大的不同在于,swift在編譯過程中會生成SIL中間代碼,通過對中間代碼的分析,我明知道了Swift實例對象的初始化會調(diào)用__allocating_init()方法,接著我們通過LGPerson樣例分析了Swift類的底層結構HeapObject,其默認包含了metadata(struct) 和refCounts(class),共16字節(jié)大小。
