本文不涉及如何使用,僅對齊實現(xiàn)原理作一個記錄凡纳。
前置條件
Swift中,一個類實例的內(nèi)存布局是有規(guī)律的:
- 32位機器上帝蒿,類前面有4+8個字節(jié)存儲meta信息荐糜,64位機器上,有8+8個字節(jié)葛超;
- 內(nèi)存中暴氏,字段從前往后有序排列;
- 如果該類繼承自某一個類绣张,那么父類的字段在前答渔;
- Optional會增加一個字節(jié)來存儲.None/.Some信息;
- 每個字段需要考慮內(nèi)存對齊侥涵;
這方面尚未從官方的資料找到參考沼撕,上述規(guī)律一些是從網(wǎng)上其他大神的總結(jié)中收集,一些從Clang的一些說明文檔中挖掘芜飘,加上自己的反復驗證得到
具體步驟:
- 獲取它的起始指針务豺,移動到有效起點;
- 通過Mirror獲取每一個字段的字段名和字段類型燃箭;
- 根據(jù)字段名在JSON中取值,轉(zhuǎn)換為和字段一樣的類型舍败,通過指針寫入招狸;
- 根據(jù)本字段類型的占位大小和下一個字段類型計算下一個字段的對齊起點;
-
移動指針邻薯,繼續(xù)處理裙戏;
步驟圖
流程總結(jié)
HandyJSON 是強依賴 metadata 結(jié)構(gòu)的,如果 metadata 有大規(guī)模的改動可能直接導致這個庫完全不能用厕诡。隨著Swift語言的版本升級累榜。metadata的結(jié)構(gòu)也有多次變動。
Swift 4.2 以前(不包含4.2)

struct _NominalTypeDescriptor {
var mangledName: Int32
var numberOfFields: Int32
var fieldOffsetVector: Int32
var fieldNames: Int32
var fieldTypesAccessor: Int32
}
Swift 4.2
Swift 4.2 對 nominal type descriptor 做了調(diào)整灵嫌,struct 和 class 結(jié)構(gòu)變得有所不同壹罚,乍看沒有少什么東西,其實對 fieldTypesAccessor 這個函數(shù)做了修改寿羞,不再符合 c 的 calling convention猖凛,因此不可以再從 nominal type descriptor 獲取類型信息。
struct _StructContextDescriptor: _ContextDescriptorProtocol {
var flags: Int32
var parent: Int32
var mangledName: Int32
var fieldTypesAccessor: Int32
var numberOfFields: Int32
var fieldOffsetVector: Int32
}
struct _ClassContextDescriptor: _ContextDescriptorProtocol {
var flags: Int32
var parent: Int32
var mangledName: Int32
var fieldTypesAccessor: Int32
var superClsRef: Int32
var reservedWord1: Int32
var reservedWord2: Int32
var numImmediateMembers: Int32
var numberOfFields: Int32
var fieldOffsetVector: Int32
}
盡管蘋果希望我們用 Mirror 來做反射绪穆,但是其實 Mirror 至今為止都不包含屬性的類型的信息辨泳,因此蘋果留了一個臨時接口 swift_getFieldAt
來幫助我們獲取類型信息:
@_silgen_name("swift_getFieldAt")
func _getFieldAt(
_ type: Any.Type,
_ index: Int,
_ callback: @convention(c) (UnsafePointer<CChar>, UnsafeRawPointer, UnsafeMutableRawPointer) -> Void,
_ ctx: UnsafeMutableRawPointer
)
為什么說是臨時的呢晃琳,因為 Swift 5 的時候就發(fā)現(xiàn)這個接口沒了壶熏。。。灸芳。
Swift 5.0
到了 Swift 5.0 的時候,前面已經(jīng)說過了獲取類型的那個接口沒了倔丈,那么我們只好翻出 Swift 的源碼來找找思路了灰嫉,
找到 TypeContextDescriptorBuilderBase 類的 layout() 方法:
void layout() {
asImpl().computeIdentity();
super::layout();
asImpl().addName();
asImpl().addAccessFunction();
asImpl().addReflectionFieldDescriptor();
asImpl().addLayoutInfo();
asImpl().addGenericSignature();
asImpl().maybeAddResilientSuperclass();
asImpl().maybeAddMetadataInitialization();
}
按源碼寫出 nominal type descriptor 的結(jié)構(gòu)如下:
struct _StructContextDescriptor: _ContextDescriptorProtocol {
var flags: Int32
var parent: Int32
var mangledNameOffset: Int32
var fieldTypesAccessor: Int32
var reflectionFieldDescriptor: Int32
var numberOfFields: Int32
var fieldOffsetVector: Int32
}
struct _ClassContextDescriptor: _ContextDescriptorProtocol {
var flags: Int32
var parent: Int32
var mangledNameOffset: Int32
var fieldTypesAccessor: Int32
var reflectionFieldDescriptor: Int32
var superClsRef: Int32
var metadataNegativeSizeInWords: Int32
var metadataPositiveSizeInWords: Int32
var numImmediateMembers: Int32
var numberOfFields: Int32
var fieldOffsetVector: Int32
}
雖然 fieldTypesAccessor
還是無法調(diào)用,但是我們發(fā)現(xiàn)這里多了一個 reflectionFieldDescriptor
指針耍共,直覺告訴我辦法應(yīng)該在這個東西里面烫饼,所以先看下這個東西是什么結(jié)構(gòu):
void addReflectionFieldDescriptor() {
....
B.addRelativeAddress(IGM.getAddrOfReflectionFieldDescriptor(
getType()->getDeclaredType()->getCanonicalType()));
}
邏輯基本就是拿到 ReflectionFieldDescriptor
的地址,然后把地址放到相應(yīng)的內(nèi)存里试读,需要注意的是這里放的是一個相對的地址杠纵,RelativePointer 的注釋中寫道:
// A reference can be absolute or relative: // // - An absolute reference is a pointer to the object. // // - A relative reference is a (signed) offset from the address of the // reference to the address of its direct referent.
相對引用指的是相對當前引用指針地址的偏移量,于是我們有了獲取 ReflectionFieldDescriptor 地址的方法:
var reflectionFieldDescriptor: FieldDescriptor? {
guard let contextDescriptor = self.contextDescriptor else {
return nil
}
let pointer = UnsafePointer<Int>(self.pointer)
let base = pointer.advanced(by: contextDescriptorOffsetLocation)
let offset = contextDescriptor.reflectionFieldDescriptor
let address = base.pointee + 4 * 4 // (4 properties in front) * (sizeof Int32)
guard let fieldDescriptorPtr = UnsafePointer<_FieldDescriptor>(bitPattern: address + offset) else {
return nil
}
return FieldDescriptor(pointer: fieldDescriptorPtr)
}
拿到了地址钩骇,我們還需要知道 FieldDescriptor
這個結(jié)構(gòu)是什么樣子的比藻,我們找到 FieldDescriptor
這個類:
// Field descriptors contain a collection of field records for a single
// class, struct or enum declaration.
class FieldDescriptor {
const FieldRecord *getFieldRecordBuffer() const {
return reinterpret_cast<const FieldRecord *>(this + 1);
}
const RelativeDirectPointer<const char> MangledTypeName;
const RelativeDirectPointer<const char> Superclass;
public:
FieldDescriptor() = delete;
const FieldDescriptorKind Kind;
const uint16_t FieldRecordSize;
const uint32_t NumFields;
using const_iterator = FieldRecordIterator;
....
}
FieldDescriptor 的結(jié)構(gòu)里有一個 FieldRecord 的數(shù)組,從名字看里面應(yīng)該保存了類型信息倘屹,我們再翻出 FieldRecord 的源碼:
class FieldRecord {
const FieldRecordFlags Flags;
const RelativeDirectPointer<const char> MangledTypeName;
const RelativeDirectPointer<const char> FieldName;
....
}
很遺憾 FieldRecord
并沒有直接保存類型信息银亲,只有一個 MangledTypeName
,問題不大纽匙,我們還有一個叫 swift_getTypeByMangledNameInContext
的函數(shù)务蝠,這個函數(shù)背后調(diào)用的 swift_getTypeByMangledName
函數(shù)與之前的 getFieldAt
內(nèi)部調(diào)用的是同一個函數(shù),返回是 Any.Type
:
@_silgen_name("swift_getTypeByMangledNameInContext")
public func _getTypeByMangledNameInContext(
_ name: UnsafePointer<UInt8>,
_ nameLength: Int,
genericContext: UnsafeRawPointer?,
genericArguments: UnsafeRawPointer?)
-> Any.Type?
參考: