重學Swift第一篇:類結構探索
前言
內容主要是通過對Swift原始碼和SIL程式碼來學習Swift中底層的實現。
一、Swift編譯過程
一個swift檔案的編譯過程如下:
Swift在編譯過程中使用的LLVM前端編譯器是swiftc
,可通過swiftc -h
檢視了解各命令的作用。
二、SIL分析
在main.swift中定義Test類
class Test {}
var test = Test()
通過swiftc -emit-sil main.swift >> main.sil && open -a Xcode main.sil
把swift檔案編譯成sil檔案並開啟
class Test { //Test類
@objc deinit
init()
}
@_hasStorage @_hasInitialValue var test: Test { get set }
// test
//可通過xcrun swift-demangle s4main4testAA4TestCvp還原函式符號
sil_global hidden @$s4main4testAA4TestCvp : $Test
// main
sil @main : $@convention(c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
alloc_global @$s4main4testAA4TestCvp // id: %2
%3 = global_addr @$s4main4testAA4TestCvp : $*Test // user: %7
%4 = metatype $@thick Test.Type // user: %6
// function_ref Test.__allocating_init()
%5 = function_ref @$s4main4TestCACycfC : $@convention(method) (@thick Test.Type) -> @owned Test // user: %6
%6 = apply %5(%4) : $@convention(method) (@thick Test.Type) -> @owned Test // user: %7
store %6 to %3 : $*Test // 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'
// Test.__allocating_init()
sil hidden [exact_self_class] @$s4main4TestCACycfC : $@convention(method) (@thick Test.Type) -> @owned Test {
// %0 "$metatype"
bb0(%0 : $@thick Test.Type):
%1 = alloc_ref $Test // user: %3
// function_ref Test.init()
%2 = function_ref @$s4main4TestCACycfc : $@convention(method) (@owned Test) -> @owned Test // user: %3
%3 = apply %2(%1) : $@convention(method) (@owned Test) -> @owned Test // user: %4
return %3 : $Test // id: %4
} // end sil function '$s4main4TestCACycfC'
// Test.init()
sil hidden @$s4main4TestCACycfc : $@convention(method) (@owned Test) -> @owned Test {
// %0 "self" // users: %2, %1
bb0(%0 : $Test):
debug_value %0 : $Test, let, name "self", argno 1 // id: %1
return %0 : $Test // id: %2
} // end sil function '$s4main4TestCACycfc'
可以看到在main函式中呼叫了Test.__allocating_init()
,在__allocating_init()中通過alloc_ref生成了Test的例項
,接著呼叫init方法
返回了自身。
可以在VSCode或Xcode中打一個__allocating_init符號斷點
,我這裡原始碼是用VSCode開啟的,所以在VSCode中打上斷點執行後,單步除錯進入了swift_allocObject
函式
HeapObject *swift::swift_allocObject(HeapMetadata const *metadata,
size_t requiredSize,
size_t requiredAlignmentMask) {
CALL_IMPL(swift_allocObject, (metadata, requiredSize, requiredAlignmentMask));
}
CALL_IMPL是一個巨集,接著單步除錯,進入_swift_allocObject_
函式
static HeapObject *_swift_allocObject_(HeapMetadata const *metadata,
size_t requiredSize,
size_t requiredAlignmentMask) {
auto object = reinterpret_cast<HeapObject *>(
swift_slowAlloc(requiredSize, requiredAlignmentMask)); //分配記憶體,
new (object) HeapObject(metadata); //建立HeapObject物件
......
return object;
}
通過swift_slowAlloc方法分配了記憶體,然後通過new (object) HeapObject(metadata)
在object的記憶體上就地建立HeapObject物件。
struct HeapObject {
/// This is always a valid pointer to a metadata object.這始終是指向後設資料物件的有效指標
HeapMetadata const *metadata; //指標8bytes
InlineRefCounts refCounts; //RefCounts類,類中有一個refCounts成員變數,型別為RefCountBitsT,類RefCountBitsT中只有一個uint64_t的bits成員變數,所以最後也佔用8bytes
......
constexpr HeapObject(HeapMetadata const *newMetadata)
: metadata(newMetadata)
, refCounts(InlineRefCounts::Initialized)
{ }
};
可以看出物件是HeapObject *型別,它有兩個屬性,一個是metadata,一個是refCounts,預設佔用16bytes。
三、類結構探索
從上面SIL分析中瞭解到類的結構體為HeapObject,HeapObject中metadata包含了後設資料資訊,refCounts用來表示引用計數。下面主要來看metadata,它的型別HeapMetadata原始碼如下:
using HeapMetadata = TargetHeapMetadata<InProcess>;
template <typename Runtime>
struct TargetHeapMetadata : TargetMetadata<Runtime> { ... }
template <typename Runtime>
struct TargetMetadata {
using StoredPointer = typename Runtime::StoredPointer;
constexpr TargetMetadata() : Kind(static_cast<StoredPointer>(MetadataKind::Class)) {}
......
StoredPointer Kind; //InProcess::StoredPointer就是unsinged long long
......
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);
}
......
}
}
可以看出HeapMetadata中沒有屬性,而它繼承自TargetMetadata,而TargetMetadata有一個Kind屬性
。這個Kind屬性可以標識元類資料的型別,具體如下
而TargetMetadata還有一個getClassObject()
函式,可以根據kind拿到對應的metadata,接著來看ClassMetadata。
using ClassMetadata = TargetClassMetadata<InProcess>;
struct TargetClassMetadata : public TargetAnyClassMetadata<Runtime> {
ClassFlags Flags; //swift類標記,這些標誌僅在isTypeMetadata()時有效。
uint32_t InstanceAddressPoint; //例項地址點
/// 'InstanceAddressPoint' bytes go before the address point;
/// 'InstanceSize - InstanceAddressPoint' bytes go after it.
uint32_t InstanceSize; //例項所需大小
uint16_t InstanceAlignMask; //例項對齊掩碼
uint16_t Reserved; //保留
uint32_t ClassSize; //類物件的總大小,包括字首和字尾範圍。
uint32_t ClassAddressPoint; //類物件內地址點的偏移量。
private:
TargetSignedPointer<Runtime, const TargetClassDescriptor<Runtime> * __ptrauth_swift_type_descriptor> Description;
public:
TargetSignedPointer<Runtime, ClassIVarDestroyer * __ptrauth_swift_heap_object_destructor> IVarDestroyer;
}
template <typename Runtime>
struct TargetAnyClassMetadata : public TargetHeapMetadata<Runtime> {
ConstTargetMetadataPointer<Runtime, swift::TargetClassMetadata> Superclass; //超類的後設資料。根類為null。ObjC類沒有後設資料頭
TargetPointer<Runtime, void> CacheData[2]; //用於動態查詢,它歸執行時所有,通常需要與Objective-C的使用進行互操作。
StoredSize Data; //unsigned long
}
綜上可知ClassMetadata結構體可大致表示為
struct ClassMetadata {
void* Kind; //表示型別,相當於isa的功能
void* Superclass; //超類
void* CacheData; //快取資料
void* Data; //外聯後設資料, 用低位標誌為Swift metatype,存在型別後設資料頭
uint32_t Flags; //swift類標記,這些標誌僅在isTypeMetadata()時有效。
uint32_t InstanceAddressPoint; //例項地址點
uint32_t InstanceSize; //例項所需大小
uint16_t InstanceAlignMask; //例項對齊掩碼
uint16_t Reserved; //保留
uint32_t ClassSize; //類物件的總大小,包括字首和字尾範圍。
uint32_t ClassAddressPoint; //類物件內地址點的偏移量。
void* Description;
void* IVarDestroyer;
......
}
類比objc中原始碼
struct swift_class_t : objc_class {
//isa_t isa;
//Class superclass;
//cache_t cache;
//class_data_bits_t bits;
uint32_t flags;
uint32_t instanceAddressOffset;
uint32_t instanceSize;
uint16_t instanceAlignMask;
uint16_t reserved;
uint32_t classSize;
uint32_t classAddressOffset;
void *description;
// ...
void *baseAddress() {
return (void *)((uint8_t *)this - classAddressOffset);
}
};
總結
- 通過
swiftc -emit-sil main.swift >> main.sil
可以把swift原始檔轉為sil檔案。 - 通過
xcrun swift-demangle
可把符號還原 - HeapObjec有一個HeapMetadata型別的metadata用來提供類的資料,通過
metadata->getKind()
可知後設資料型別,通過metadata->getClassObject()
可以拿到後設資料。還有一個refCounts用來管理引用計數。
相關文章
- Swift 類與結構體Swift結構體
- Swift,結構體與類Swift結構體
- Swift-類和結構體Swift結構體
- iOS探索 類的結構分析iOS
- Swift4類和結構(二)Swift
- swift物件導向特性——類和結構體Swift物件結構體
- iOS Swift結構體與類的方法排程iOSSwift結構體
- 《從零開始學Swift》學習筆記(Day 25)——類和結構體定義Swift筆記結構體
- Swift列舉,結構體,類,擴充套件,協議Swift結構體套件協議
- 重學資料結構(二、棧)資料結構
- 重學資料結構之棧資料結構
- 重學資料結構(七、圖)資料結構
- 《從零開始學Swift》學習筆記(Day 30)——選擇類還是結構體呢?Swift筆記結構體
- 重學資料結構之佇列資料結構佇列
- 重學資料結構(八、查詢)資料結構
- 重學資料結構(三、佇列)資料結構佇列
- Swift學習筆記(二十五)——迴圈結構Swift筆記
- 資料結構 & 演算法 in Swift (一):Swift基礎和資料結構資料結構演算法Swift
- 索引內部結構探索索引
- JVM學習--Class類檔案結構JVM
- 資料結構第一篇 棧資料結構
- 程式碼重構:類重構的 8 個小技巧
- [譯]Swift 結構體指標Swift結構體指標
- sklearn學習 第一篇:knn分類KNN
- Swift 3學習筆記第一篇(語法概覽)Swift筆記
- AI落地場景探索:醫學報告單結構化AI
- Swift Json解析探索SwiftJSON
- 重學資料結構之哈夫曼樹資料結構
- 類和結構
- iOS學習筆記44 Swift(四)列舉和結構體iOS筆記Swift結構體
- Swift 使用JSON資料結構SwiftJSON資料結構
- 資料結構 堆排序 c Swift資料結構排序Swift
- 類檔案結構_class類檔案的的結構
- iOS學習筆記43 Swift(三)類iOS筆記Swift
- JS版資料結構第一篇(棧)JS資料結構
- 資料結構-第一篇:線性表資料結構
- Laravel 探索之 Session 與 Cookie 結構LaravelSessionCookie
- [swift 進階]讀書筆記-第五章:結構體和類 C5P3_結構體(struct)Swift筆記結構體Struct