Swift 5 出了,主要是 ABI 穩定了,從 ABI Dashboard 來看爲了解決 ABI 穩定問題,對 type metadata 也有很多改動。衆所周知,咱們 App 的 JSON 庫 HandyJSON 是強依賴 metadata 結構的,若是 metadata 有大規模的改動可能直接致使這個庫徹底不能用,本着早發現早治療的心態我趕快下載了 Xcode 10.2 beta,一跑果真編不過了,沒辦法只好本身着手來解決問題了。git
爲了便於理解,先畫個圖看一下 metadata 的具體結構,每一格代碼一個指針長度,這是 64 位系統下的 metadata 結構,32 位系統下 nominal type descriptor 的偏移在 11 個指針長度的位置,官方文檔裏有詳細的說明。
github
在 Swift 4.2 (不包括 4.2)之前的結構是這樣的:swift
![](https://user-gold-cdn.xitu.io/2019/2/26/16929595b0015018?w=490&h=734&f=png&s=61393)
struct _NominalTypeDescriptor {
var mangledName: Int32
var numberOfFields: Int32
var fieldOffsetVector: Int32
var fieldNames: Int32
var fieldTypesAccessor: Int32
}
複製代碼
nominal type descriptor 包含了屬性的名字和訪問屬性的類型信息的函數,HandyJSON 最初的原理就是從 nominal type descriptor 中取得屬性的類型信息而後把 JSON 字串裏的相應值賦進去,因爲 fieldTypeAccessor 符合 c 的 calling convention,把指針強轉一下就能得到類型信息:數組
var fieldTypes: [Any.Type]? {
guard let nominalTypeDescriptor = self.nominalTypeDescriptor else {
return nil
}
guard let function = nominalTypeDescriptor.fieldTypesAccessor else { return nil }
return (0..<nominalTypeDescriptor.numberOfFields).map {
return unsafeBitCast(function(UnsafePointer<Int>(pointer)).advanced(by: $0).pointee, to: Any.Type.self)
}
}
複製代碼
Swift 4.2 對 nominal type descriptor 作了調整,struct 和 class 結構變得有所不一樣,乍看沒有少什麼東西,其實對 fieldTypesAccessor 這個函數作了修改,再也不符合 c 的 calling convention,所以不能夠再從 nominal type descriptor 獲取類型信息。bash
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
來幫助咱們獲取類型信息:app
@_silgen_name("swift_getFieldAt")
func _getFieldAt( _ type: Any.Type, _ index: Int, _ callback: @convention(c) (UnsafePointer<CChar>, UnsafeRawPointer, UnsafeMutableRawPointer) -> Void,
_ ctx: UnsafeMutableRawPointer
)
複製代碼
爲何說是臨時的呢,由於 Swift 5 的時候就發現這個接口沒了。。。。函數
到了 Swift 5.0 的時候,前面已經說過了獲取類型的那個接口沒了,那麼咱們只好翻出 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 的結構以下:ui
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 仍是沒法調用,可是咱們發現這裏多了一個 reflectionFieldDescriptor 指針,直覺告訴我辦法應該在這個東西里面,因此先看下這個東西是什麼結構:this
void addReflectionFieldDescriptor() {
....
B.addRelativeAddress(IGM.getAddrOfReflectionFieldDescriptor(
getType()->getDeclaredType()->getCanonicalType()));
}
複製代碼
邏輯基本就是拿到 ReflectionFieldDescriptor 的地址,而後把地址放到相應的內存裏,須要注意的是這裏放的是一個相對的地址,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 這個結構是什麼樣子的,咱們找到 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 的結構裏有一個 FieldRecord 的數組,從名字看裏面應該保存了類型信息,咱們再翻出 FieldRecord 的源碼:
class FieldRecord {
const FieldRecordFlags Flags;
const RelativeDirectPointer<const char> MangledTypeName;
const RelativeDirectPointer<const char> FieldName;
....
}
複製代碼
很遺憾 FieldRecord 並無直接保存類型信息,只有一個 MangledTypeName ,問題不大,咱們還有一個叫 swift_getTypeByMangledNameInContext 的函數,這個函數背後調用的 swift_getTypeByMangledName 函數與以前的 getFieldAt 內部調用的是同一個函數,返回是 Any.Type:
@_silgen_name("swift_getTypeByMangledNameInContext")
public func _getTypeByMangledNameInContext(
_ name: UnsafePointer<UInt8>,
_ nameLength: Int,
genericContext: UnsafeRawPointer?,
genericArguments: UnsafeRawPointer?)
-> Any.Type?
複製代碼
至此咱們解決了由 Swift 5.0 metadata 變更致使的災難性編譯問題,順便把 metadata 結構梳理了一下,代碼已經提交到了 dev_for_swift5.0 分支。
Mirror 是官方支持的反射工具,使用 Metadata 這種辦法算是一種非主流的作法,可是蘋果也意識到 Mirror 裏面有部分信息沒法提供,聽說是技術上有一點困難因此暫時無法把類型信息等放到 Mirror 裏面,因此纔在 Metadata 裏增長了用於反射的信息,ABI Dashboard 裏也說 ABI 穩定的優先級高於完整的反射功能,可見 Metadata 這一部分的結構暫時不會大改了,可是遠期來看蘋果仍是會在 Mirror 裏面完整支持反射功能。
swift.org/abi-stabili…
github.com/apple/swift…
github.com/apple/swift…
github.com/alibaba/Han…