深度探究HandyJSON(四) 解析 struct

在这个系列的前两篇文章中, 我们讲了 Swift指针的使用, Mirror 的原理, 这些其实都是为接下来的几篇文章做铺垫. 这个系列我并不打算将 HandJSON 的每个细节都讲到, 主要围绕如何通过 strcut / class 对象实现反序列化, 最后实现一个 Swift 版的 KVC.

在这篇文章里, 我们将主要关注 struct 对象实现反序列化.

在开始之前, 先梳理一下 HandJSON 的结构.


HandyJSON大致结构图

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<Int>.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<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 中我们能发现很多相对指针 relative pointer, 这些指针实际上是指向被引用数据的偏移量,相对于存储指针的位置. 这还意味着可以重新定位数据而无需重写任何值.

第 2 步: 获取属性内存偏移值

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文件直接调用.c 文件的函数
    func testCBridge(){
        print(c_add(i: 10, j: 20))  // 30
        print(c_mul(i: 10, times: 20)) // 200
    }
}

第 4 步: 将 JSON 数据进行解析, 反序列化到实例

// 获取头指针
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)

// 获取数据
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

文中将这个类型直接传给第三方来处理, 通过判断传入的数据类型与属性的类型是否匹配, 来进行赋值, 无需强转数据类型, 这就比较方便了.

推荐阅读更多精彩内容