在这个系列的前两篇文章中, 我们讲了 Swift指针的使用, Mirror 的原理, 这些其实都是为接下来的几篇文章做铺垫. 这个系列我并不打算将 HandJSON 的每个细节都讲到, 主要围绕如何通过 strcut / class 对象实现反序列化, 最后实现一个 Swift 版的 KVC.
在这篇文章里, 我们将主要关注 struct 对象实现反序列化.
在开始之前, 先梳理一下 HandJSON 的结构.
HandyJSON 的初代版本和 Reflection 相似, 如果你也对反射感兴趣, 可以去看一下这个项目.
回顾一下, 如何在内存上为实例的属性赋值呢?
- 获取到属性的名称和类型.
- 找到实例在内存中的 headPointer, 通过属性的类型计算内存中的偏移值, 确定属性在内存中的位置.
- 在内存中为属性赋值.
那么 HandyJSON 内部是怎么处理的呢?
废话少说, 直接上代码, 我将 HandyJSON 中的代码做了最简化处理.
第 0 步: 定义 Model
struct Person {
var isBoy: Bool = true
var age: Int = 0
var height: Double = 130.1
var name: String = "jack"
}
第 1 步: 获取属性数量, 以及属性偏移矢量
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
// 类型转化
let pointer = unsafeBitCast(personType, to: UnsafePointer.self)
let base = pointer.advanced(by: 1) // contextDescriptorOffsetLocation
// 相对指针偏移值
let relativePointerOffset = base.pointee - Int(bitPattern: base)
以 _StructContextDescriptor 类型访问数据
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
类型访问内存数据, 得到属性数量 numberOfFields, 属性偏移矢量 fieldOffsetVector, 通过这两个参数可以获取每个属性的偏移值. -
_StructContextDescriptor
的内部结构来源于 Swift 源码中TargetContextDescriptor, TargetTypeContextDescriptor 这两个类. 大致如下
// 所有上下文描述符的基类。 struct TargetContextDescriptor { // 描述上下文的标志,包括其种类和格式版本 ContextDescriptorFlags Flags; // 父上下文,如果这是顶级上下文,则为null RelativeContextPointer
Parent; } struct TargetExtensionContextDescriptor final : TargetContextDescriptor { RelativeDirectPointer ExtendedContext; // MangledName StringRef getMangledExtendedContext() const { return Demangle::makeSymbolicMangledNameStringRef(ExtendedContext.get()); } } class TargetTypeContextDescriptor : public TargetContextDescriptor { // type 的名字 TargetRelativeDirectPointer Name; int32_t getGenericArgumentOffset() const; const TargetMetadata * const *getGenericArguments( const TargetMetadata *metadata) const { } }
- 在
TargetContextDescriptor
,TargetTypeContextDescriptor
中我们能发现很多相对指针relative pointer
, 这些指针实际上是指向被引用数据的偏移量,相对于存储指针的位置. 这还意味着可以重新定位数据而无需重写任何值.
第 2 步: 获取属性内存偏移值
extension UnsafePointer {
init(_ pointer: UnsafePointer) {
self = UnsafeRawPointer(pointer).assumingMemoryBound(to: Pointee.self)
}
}
let contextDescriptor = descriptor.pointee
let numberOfFields = Int(contextDescriptor.numberOfFields)
let fieldOffsetVector = Int(contextDescriptor.fieldOffsetVector)
// 成员变量的偏移值
let fieldOffsets = (0..(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, UnsafeRawPointer, UnsafeMutableRawPointer) -> Void,
_ ctx: UnsafeMutableRawPointer
)
for i in 0..
- 在这部分代码中, 看到了我们比较熟悉的
_getFieldAt
方法, 这个方法曾今在Mirror
被使用过, 获取字段信息. 在 Swift 代码中要访问 C++ 代码, 需要加上@_silgen_name
在
testAdd.c
文件中, 定义如下方法.#include
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文件直接调用.c 文件的函数 func testCBridge(){ print(c_add(i: 10, j: 20)) // 30 print(c_mul(i: 10, times: 20)) // 200 } }
第 4 步: 将 JSON 数据进行解析, 反序列化到实例
// 获取头指针
func headPointerOfStruct(instance: inout T) -> UnsafeMutablePointer {
return withUnsafeMutablePointer(to: &instance) {
return UnsafeMutableRawPointer($0).bindMemory(to: Int8.self, capacity: MemoryLayout.stride)
}
}
// 获取头指针
var personStruct = Person()
let rawPointer = headPointerOfStruct(instance: &personStruct)
// 获取数据
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")
// 写入数据成功
protocol AnyExtensions {}
extension AnyExtensions {
public static func write(_ value: Any, to storage: UnsafeMutableRawPointer) {
guard let this = value as? Self else {
print("类型转换失败, \(type(of: value))无法转为\(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中指针的使用 这一篇文章中我们就提到
// 将内存临时重新绑定到其他类型进行访问.
let namePtr = pStructHeadRawP.advanced(by: offset).assumingMemoryBound(to: String.self)
// 设置属性值
namePtr.pointee = "lily"
- 拿到头指针后, 我们可以直接根据实例的头指针以及每个属性的偏移值, 获取到每个属性在内存中的位置,
- 再将其重新绑定到指定的属性类型进行访问, 就可以获取到属性的指针,
- 通过这个指针就可以为属性赋值.
在本例子中, 我们可以直接采用下面这种方式赋值, 但问题是我们从 JSON 数据中获取到的值是Any类型的, 在这其中必须将其转化为对应属性类型, 如果手动转就比较麻烦了.
propAddr.assumingMemoryBound(to: String.self).pointee = rawValue as! String
文中将这个类型直接传给第三方来处理, 通过判断传入的数据类型与属性的类型是否匹配, 来进行赋值, 无需强转数据类型, 这就比较方便了.