在這個系列的前兩篇文章中, 我們講了 Swift指針的使用, Mirror 的原理, 這些其實都是為接下來的幾篇文章做鋪墊. 這個系列我并不打算將 HandJSON 的每個細節(jié)都講到, 主要圍繞如何通過 strcut / class 對象實現(xiàn)反序列化, 最后實現(xiàn)一個 Swift 版的 KVC.
在這篇文章里, 我們將主要關(guān)注 struct 對象實現(xiàn)反序列化.
在開始之前, 先梳理一下 HandJSON 的結(jié)構(gòu).

HandyJSON大致結(jié)構(gòu)圖
HandyJSON 的初代版本和 Reflection 相似, 如果你也對反射感興趣, 可以去看一下這個項目.
回顧一下, 如何在內(nèi)存上為實例的屬性賦值呢?
- 獲取到屬性的名稱和類型.
- 找到實例在內(nèi)存中的 headPointer, 通過屬性的類型計算內(nèi)存中的偏移值, 確定屬性在內(nèi)存中的位置.
- 在內(nèi)存中為屬性賦值.
那么 HandyJSON 內(nèi)部是怎么處理的呢?
廢話少說, 直接上代碼, 我將 HandyJSON 中的代碼做了最簡化處理.
第 0 步: 定義 Model
struct Person {
var isBoy: Bool = true
var age: Int = 0
var height: Double = 130.1
var name: String = "jack"
}
第 1 步: 獲取屬性數(shù)量, 以及屬性偏移矢量
struct _StructContextDescriptor {
var flags: Int32
var parent: Int32
var mangledName: Int32
var fieldTypesAccessor: Int32
var numberOfFields: Int32
var fieldOffsetVector: Int32
}
var personType = Person.self as Any.Type
// 類型轉(zhuǎn)化
let pointer = unsafeBitCast(personType, to: UnsafePointer<Int>.self)
let base = pointer.advanced(by: 1) // contextDescriptorOffsetLocation
// 相對指針偏移值
let relativePointerOffset = base.pointee - Int(bitPattern: base)
以 _StructContextDescriptor 類型訪問數(shù)據(jù)
let descriptor = UnsafeRawPointer(base).advanced(by: relativePointerOffset).assumingMemoryBound(to: _StructContextDescriptor.self)
print(descriptor.pointee)
// _StructContextDescriptor(flags: 262225, parent: -24, mangledName: -16, fieldTypesAccessor: 54167296, numberOfFields: 4, fieldOffsetVector: 2)
- 以
_StructContextDescriptor類型訪問內(nèi)存數(shù)據(jù), 得到屬性數(shù)量 numberOfFields, 屬性偏移矢量 fieldOffsetVector, 通過這兩個參數(shù)可以獲取每個屬性的偏移值. -
_StructContextDescriptor的內(nèi)部結(jié)構(gòu)來源于 Swift 源碼中TargetContextDescriptor, TargetTypeContextDescriptor 這兩個類. 大致如下
// 所有上下文描述符的基類。 struct TargetContextDescriptor { // 描述上下文的標(biāo)志,包括其種類和格式版本 ContextDescriptorFlags Flags; // 父上下文,如果這是頂級上下文,則為null RelativeContextPointer<Runtime> Parent; } struct TargetExtensionContextDescriptor final : TargetContextDescriptor<Runtime> { RelativeDirectPointer<const char> ExtendedContext; // MangledName StringRef getMangledExtendedContext() const { return Demangle::makeSymbolicMangledNameStringRef(ExtendedContext.get()); } } class TargetTypeContextDescriptor : public TargetContextDescriptor<Runtime> { // type 的名字 TargetRelativeDirectPointer<Runtime, const char, /*nullable*/ false> Name; int32_t getGenericArgumentOffset() const; const TargetMetadata<Runtime> * const *getGenericArguments( const TargetMetadata<Runtime> *metadata) const { } }
- 在
TargetContextDescriptor,TargetTypeContextDescriptor中我們能發(fā)現(xiàn)很多相對指針relative pointer, 這些指針實際上是指向被引用數(shù)據(jù)的偏移量,相對于存儲指針的位置. 這還意味著可以重新定位數(shù)據(jù)而無需重寫任何值.
第 2 步: 獲取屬性內(nèi)存偏移值
extension UnsafePointer {
init<T>(_ pointer: UnsafePointer<T>) {
self = UnsafeRawPointer(pointer).assumingMemoryBound(to: Pointee.self)
}
}
let contextDescriptor = descriptor.pointee
let numberOfFields = Int(contextDescriptor.numberOfFields)
let fieldOffsetVector = Int(contextDescriptor.fieldOffsetVector)
// 成員變量的偏移值
let fieldOffsets = (0..<numberOfFields).map {
return Int(UnsafePointer<Int32>(pointer)[fieldOffsetVector * 2 + $0])
}
print(fieldOffsets)
// [0, 8, 16, 24]
第 3 步: 獲取屬性名字和類型并進行包裝
struct PropertyDescription {
public let key: String
public let type: Any.Type
public let offset: Int
}
// 類對象
let selfType = unsafeBitCast(pointer, to: Any.Type.self) // Person
// 屬性的包裝
var propertyDescriptions: [PropertyDescription] = []
class NameAndType {
var name: String?
var type: Any.Type?
}
// 下面這是編譯器特性
// 可跳過橋接文件和.h頭文件與C代碼交互
@_silgen_name("swift_getFieldAt")
func _getFieldAt(
_ type: Any.Type,
_ index: Int,
_ callback: @convention(c) (UnsafePointer<CChar>, UnsafeRawPointer, UnsafeMutableRawPointer) -> Void,
_ ctx: UnsafeMutableRawPointer
)
for i in 0..<numberOfFields {
// 屬性name, type 的包裝類
var nameAndType = NameAndType()
// 獲取屬性name, type
_getFieldAt(selfType, i, { (namePointer, typePointer, nameTypePointer) in
let name = String(cString: namePointer)
let type = unsafeBitCast(typePointer, to: Any.Type.self)
let nameType = nameTypePointer.assumingMemoryBound(to: NameAndType.self).pointee
nameType.name = name
nameType.type = type
}, &nameAndType)
// 將name , type, offset進行包裝
if let name = nameAndType.name, let type = nameAndType.type {
propertyDescriptions.append(PropertyDescription(key: name, type: type, offset: fieldOffsets[i]))
}
}
print(propertyDescriptions)
- 在這部分代碼中, 看到了我們比較熟悉的
_getFieldAt方法, 這個方法曾今在Mirror被使用過, 獲取字段信息. 在 Swift 代碼中要訪問 C++ 代碼, 需要加上@_silgen_name
在
testAdd.c文件中, 定義如下方法.#include <stdio.h> int add(int a, int b) { return a + b; } int mul(int a, int b) { return a * b; }在
testAdd.Swift中要使用testAdd.c中的add,mul方法, 我們可以這么做.@_silgen_name("add") func c_add(i:Int32,j:Int32)->Int32 @_silgen_name("mul") func c_mul(i:Int32,times:Int32)->Int32 extension ViewController { // 不使用橋接文件或者.h文件直接調(diào)用.c 文件的函數(shù) func testCBridge(){ print(c_add(i: 10, j: 20)) // 30 print(c_mul(i: 10, times: 20)) // 200 } }
第 4 步: 將 JSON 數(shù)據(jù)進行解析, 反序列化到實例
// 獲取頭指針
func headPointerOfStruct<T>(instance: inout T) -> UnsafeMutablePointer<Int8> {
return withUnsafeMutablePointer(to: &instance) {
return UnsafeMutableRawPointer($0).bindMemory(to: Int8.self, capacity: MemoryLayout<T>.stride)
}
}
// 獲取頭指針
var personStruct = Person()
let rawPointer = headPointerOfStruct(instance: &personStruct)
// 獲取數(shù)據(jù)
let dict: [String: Any] = ["isBoy": true, "name": "lili", "age": 18, "height": 100.123]
// 遍歷屬性
for property in propertyDescriptions {
let propAddr = rawPointer.advanced(by: property.offset)
if let rawValue = dict[property.key] {
extensions(of: property.type).write(rawValue, to: propAddr)
}
}
print("\n person \n", personStruct)
// Person(isBoy: true, age: 18, height: 100.123, name: "lili")
// 寫入數(shù)據(jù)成功
protocol AnyExtensions {}
extension AnyExtensions {
public static func write(_ value: Any, to storage: UnsafeMutableRawPointer) {
guard let this = value as? Self else {
print("類型轉(zhuǎn)換失敗, \(type(of: value))無法轉(zhuǎn)為\(Self.self)")
return
}
storage.assumingMemoryBound(to: self).pointee = this
}
}
func extensions(of type: Any.Type) -> AnyExtensions.Type {
struct Extensions : AnyExtensions {}
var extensions: AnyExtensions.Type = Extensions.self
withUnsafePointer(to: &extensions) { pointer in
UnsafeMutableRawPointer(mutating: pointer).assumingMemoryBound(to: Any.Type.self).pointee = type
}
return extensions
}
這段代碼有一個位置比較有意思, 在第一篇 Swift中指針的使用 這一篇文章中我們就提到
// 將內(nèi)存臨時重新綁定到其他類型進行訪問.
let namePtr = pStructHeadRawP.advanced(by: offset).assumingMemoryBound(to: String.self)
// 設(shè)置屬性值
namePtr.pointee = "lily"
- 拿到頭指針后, 我們可以直接根據(jù)實例的頭指針以及每個屬性的偏移值, 獲取到每個屬性在內(nèi)存中的位置,
- 再將其重新綁定到指定的屬性類型進行訪問, 就可以獲取到屬性的指針,
- 通過這個指針就可以為屬性賦值.
在本例子中, 我們可以直接采用下面這種方式賦值, 但問題是我們從 JSON 數(shù)據(jù)中獲取到的值是Any類型的, 在這其中必須將其轉(zhuǎn)化為對應(yīng)屬性類型, 如果手動轉(zhuǎn)就比較麻煩了.
propAddr.assumingMemoryBound(to: String.self).pointee = rawValue as! String
文中將這個類型直接傳給第三方來處理, 通過判斷傳入的數(shù)據(jù)類型與屬性的類型是否匹配, 來進行賦值, 無需強轉(zhuǎn)數(shù)據(jù)類型, 這就比較方便了.