美文网首页
深度探究HandyJSON(四) 解析 struct

深度探究HandyJSON(四) 解析 struct

作者: Lin__Chuan | 来源:发表于2018-12-25 00:28 被阅读47次

    在这个系列的前两篇文章中, 我们讲了 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
    

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

    相关文章

      网友评论

          本文标题:深度探究HandyJSON(四) 解析 struct

          本文链接:https://www.haomeiwen.com/subject/ropvkqtx.html