深度探究HandyJSON(四) 解析 struct

在這個系列的前兩篇文章中, 我們講了 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ù)類型, 這就比較方便了.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 2,067評論 0 9
  • importUIKit classViewController:UITabBarController{ enumD...
    明哥_Young閱讀 4,194評論 1 10
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,663評論 1 32
  • 我們常常會聽說 Objective-C 是一門動態(tài)語言,那么這個「動態(tài)」表現(xiàn)在哪呢?我想最主要的表現(xiàn)就是 Obje...
    Ethan_Struggle閱讀 2,339評論 0 7
  • 上午去宜家家居逛了一圈,國民對這家來自北歐的家具喜愛之情無以言表。其實,宜家只是歐美國家的中低檔品牌,卻在中國大放...
    云在風(fēng)中誰在聽閱讀 267評論 0 0

友情鏈接更多精彩內(nèi)容