前言
本篇文章會大致分析一下swift的編譯流程
,這個(gè)作為了解
即可贝乎,然后會重點(diǎn)
分析一下swift中類的結(jié)構(gòu)
柴淘,這個(gè)知識點(diǎn)我們需要掌握
。
一示括、swift編譯流程
1.1 LLVM
在了解swift編譯流程之前铺浇,我們最好清楚
LLVM是什么
?請參考??LLVM編譯流程
LLVM是架構(gòu)編譯器的框架系統(tǒng)
例诀,以C++
編寫而成随抠,用于優(yōu)化
任意程序語言編寫的程序的編譯時(shí)間(compile-time)
、鏈接時(shí)間(link-time)
繁涂、運(yùn)行時(shí)間(run-time)
以及空閑時(shí)間(idle-time)
拱她。對開發(fā)者保持開放,并兼容已有腳本扔罪。
對于我們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ì)流程
接著我們再詳細(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)儲有關(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動態(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 解析文件并打印(漂亮/簡潔的)語法樹
-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)識當(dāng)前LGPerson.swift文件的入口函數(shù)
,SIL標(biāo)識符號名稱以@作為前綴
岂傲。%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
定義一個(gè)function_ref即函數(shù)谍憔,就是%5,這個(gè)函數(shù)入?yún)⑹荓GPerson.Type -
%6 = apply %5(%4) : $@convention(method) (@thick LGPerson.Type) -> @owned LGPerson
apply調(diào)用函數(shù)%5主籍,入?yún)⒕褪?4习贫,將返回結(jié)果賦給%6 -
store %6 to %3 : $*LGPerson
將%6的結(jié)果存儲到%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
,并對其Type值
做了一個(gè)返回處理幸海,那么蜡歹,這個(gè)Type代表什么意思呢屋厘?后面我們會仔細(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í)例對象议纯,給%3 -
return %3 : $LGPerson
返回%3的實(shí)例對象
這個(gè)__allocating_init()
也不難父款,其實(shí)就是一個(gè)簡單的方法調(diào)用過程。對于這個(gè)SIL代碼瞻凤,我們平時(shí)可以自己多看多分析
憨攒,習(xí)慣就好了,哈哈阀参!
二肝集、swift類的結(jié)構(gòu)
帶著上面的問題??LGPerson中的Type 就是存儲的是什么值,代表什么意思蛛壳?
我們現(xiàn)在來看看swift的類class在底層中的結(jié)構(gòu)是什么樣的杏瞻。
大家都知道,如果要創(chuàng)建一個(gè)對象衙荐,OC 與 swift的寫法是這樣的??
- OC:
[[LGPerosn alloc] init]
一般alloc
申請內(nèi)存空間并創(chuàng)建對象捞挥,init
對其進(jìn)行統(tǒng)一初始化處理。 - Swift:
LGPerson()
直接()
就完成了對象的創(chuàng)建忧吟。
2.1 找入口
我們現(xiàn)在就來看看LGPerson()
在匯編層是調(diào)用了哪些函數(shù)砌函?
新建一個(gè)SwiftDemo項(xiàng)目工程,在ViewController.swift中添加下面的代碼溜族,并打上斷點(diǎn)??
運(yùn)行項(xiàng)目讹俊,查看匯編??
我們找到了對象的初始化入口??函數(shù)__allocating_init()
,接著我們在__allocating_init()
這一行打上斷點(diǎn)煌抒,按住control按鍵 stepinto
進(jìn)入查看??
定位到了swift_allocObject
仍劈,同理還是在swift_allocObject
這行加上斷點(diǎn),stepinto
進(jìn)入查看??
沒找到實(shí)用的信息摧玫,那么換種思路耳奕,添加符號斷點(diǎn)swift_allocObject
,再次run??
接著诬像,添加符號斷點(diǎn)swift_slowAlloc
??
最終來到了比較熟悉的malloc_zone_malloc
屋群。
至此,swift實(shí)例對象的創(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()
,會觸發(fā)斷點(diǎn)??
我們可以看左上角否纬,發(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轉(zhuǎn)換成HeapObject
類型膜廊,然后執(zhí)行 -
new (object) HeapObject(metadata);
因?yàn)閛bject是強(qiáng)轉(zhuǎn)的HeapObject類型乏沸,其里面值仍是一個(gè)指向內(nèi)存空間的對象指針
,所以這里執(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é)
大小是什么荐吵?
之前我們在源碼調(diào)試的時(shí)候,也發(fā)現(xiàn)類LGPerson
初始化實(shí)例對象時(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
肿男,則直接對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
。
接著舌镶,我們再看看TargetClassMetadata
里還有哪些成員??
template <typename Runtime>
struct TargetClassMetadata : public TargetAnyClassMetadata<Runtime> {
...
//swift特有的標(biāo)志
ClassFlags Flags;
//實(shí)力對象內(nèi)存大小
uint32_t InstanceSize;
//實(shí)例對象內(nèi)存對齊方式
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ì)是
HeapObject
HeapObject
默認(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在編譯過程中會生成SIL中間代碼
学搜,通過對中間代碼的分析娃善,我明知道了Swift實(shí)例對象的初始化會調(diào)用__allocating_init()
方法论衍,接著我們通過LGPerson樣例分析了Swift類的底層結(jié)構(gòu)HeapObject
,其默認(rèn)包含了metadata(struct)
和refCounts(class)
聚磺,共16字節(jié)
大小坯台。