iOS-关于HandyJSON的个人浅析

一个最基本的遵循HandyJSON的模型的实现如下:

...
class TestModel: HandyJSON {

    private var name:String?
    
    required init() {}
}
...

    if let data = TestModel.deserialize(from: "") {}
        
...

需要的数个步骤分别为遵循HandyJSON协议,根据协议实现init方法,以及最后的调用协议方法解析JSON字符串填充模型数据。
进入HandyJSON源码的Deserializer文件,我们可在首行看到,Deserializer文件中实现了HandyJSON协议的扩展,这个扩展主要用于与外部调用层的衔接,将外部使用层传入的数据传入下层,如果对数据格式有一定要求,则适时对传入的数据结构进行重组。


public static func deserialize(from dict: [String: Any]?, designatedPath: String? = nil) -> Self? {
        return JSONDeserializer.deserializeFrom(dict: dict, designatedPath: designatedPath)
    }

    public static func deserialize(from json: String?, designatedPath: String? = nil) -> Self? {
        return JSONDeserializer.deserializeFrom(json: json, designatedPath: designatedPath)
    }
    

将传入的数据及关键的调用协议函数的“对象”交给JSONDeserializer类进行下一步操作。

在这里就能看出来,虽然HandyJSON的外部调用声明了数个deserialize接口,分别接收不同的入参(NSDictionary[String: Any]String),但在JSONDeserializer类的内部都会通过各种方式转化为[String: Any]这种Swift推荐的类型进行下一步操作。

//所有不同类型的JSON入参最终都会抵达该函数
public static func deserializeFrom(dict: [String: Any]?, designatedPath: String? = nil) -> T? {
        var targetDict = dict
        if let path = designatedPath {
            targetDict = getInnerObject(inside: targetDict, by: path) as? [String: Any]
        }
        if let _dict = targetDict {
            return T._transform(dict: _dict) as? T
        }
        return nil
    }

接着,getInnerObject函数接收两个入参,字典及designatedPath,很容易就明白,这一步的操作是根据外部提供的路径正确地对JSON进行切割以获得模型需要的JSON数据。

fileprivate func getInnerObject(inside object: Any?, by designatedPath: String?) -> Any? {
    var result: Any? = object
    var abort = false
    if let paths = designatedPath?.components(separatedBy: "."), paths.count > 0 {
        var next = object as? [String: Any]
        paths.forEach({ (seg) in
            if seg.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines) == "" || abort {
                return
            }
            if let _next = next?[seg] {
                result = _next
                next = _next as? [String: Any]
            } else {
                abort = true
            }
        })
    }
    return abort ? nil : result
}

这一步不用多说,通过for循环切割后的路径字符串,并根据路径字符串将原始字典替换为需要的字典数据返还。

此刻,JSONDeserializer的任务已经完成了,开始进入数据插入环节,之前也看到,JSONDeserializer持有遵循HandyJSON协议的泛型类,通过T._transform(dict: _dict) as? T调用HandyJSON协议的父协议_ExtendCustomModelType的实现函数。
_transform函数的主体实现部分比较庞大,因此分段解析。

static func _transform(dict: [String: Any], to instance: inout Self) {
        guard let properties = getProperties(forType: Self.self) else {
            InternalLogger.logDebug("Failed when try to get properties from type: \(type(of: Self.self))")
            return
        }

        // do user-specified mapping first
        let mapper = HelpingMapper()
        instance.mapping(mapper: mapper)

        // get head addr
        let rawPointer = instance.headPointer()
        InternalLogger.logVerbose("instance start at: ", Int(bitPattern: rawPointer))

        // process dictionary
        let _dict = convertKeyIfNeeded(dict: dict)

        let instanceIsNsObject = instance.isNSObjectType()
        let bridgedPropertyList = instance.getBridgedPropertyList()

        for property in properties {
            let isBridgedProperty = instanceIsNsObject && bridgedPropertyList.contains(property.key)

            let propAddr = rawPointer.advanced(by: property.offset)
            InternalLogger.logVerbose(property.key, "address at: ", Int(bitPattern: propAddr))
            if mapper.propertyExcluded(key: Int(bitPattern: propAddr)) {
                InternalLogger.logDebug("Exclude property: \(property.key)")
                continue
            }

            let propertyDetail = PropertyInfo(key: property.key, type: property.type, address: propAddr, bridged: isBridgedProperty)
            InternalLogger.logVerbose("field: ", property.key, "  offset: ", property.offset, "  isBridgeProperty: ", isBridgedProperty)

            if let rawValue = getRawValueFrom(dict: _dict, property: propertyDetail, mapper: mapper) {
                if let convertedValue = convertValue(rawValue: rawValue, property: propertyDetail, mapper: mapper) {
                    assignProperty(convertedValue: convertedValue, instance: instance, property: propertyDetail)
                    continue
                }
            }
            InternalLogger.logDebug("Property: \(property.key) hasn't been written in")
        }
    }


一、获取属性列表

guard let properties = getProperties(forType: Self.self) else {
            InternalLogger.logDebug("Failed when try to get properties from type: \(type(of: Self.self))")
            return
        }

第一步,获取类型的属性列表,这是最关键也是最重要的一步。

func getProperties(forType type: Any.Type) -> [Property.Description]? {
    if let structDescriptor = Metadata.Struct(anyType: type) {
        return structDescriptor.propertyDescriptions()
    } else if let classDescriptor = Metadata.Class(anyType: type) {
        return classDescriptor.propertyDescriptions()
    } else if let objcClassDescriptor = Metadata.ObjcClassWrapper(anyType: type),
        let targetType = objcClassDescriptor.targetType {
        return getProperties(forType: targetType)
    }
    return nil
}

无论是Metadata.Struct还是Metadata.Class亦或是Metadata.ObjcClassWrapper都是结构体,他们遵循ContextDescriptorType协议,而ContextDescriptorType继承于父协议MetadataType,MetadataType则继承于PointerType协议。

当如Metadata.Struct(anyType: type)这样进行结构体的初始化时,会调用MetadataType协议扩展中的初始化函数。

init?(anyType: Any.Type) {
        self.init(pointer: unsafeBitCast(anyType, to: UnsafePointer.self))
        if let kind = type(of: self).kind, kind != self.kind {
            return nil
        }
    }

首先,将类对象转化为指针类型然后调用父协议PointerType扩展的初始化函数。

...
protocol PointerType : Equatable {
    associatedtype Pointee
    var pointer: UnsafePointer { get set }
}
...
init(pointer: UnsafePointer) {
        func cast(_ value: T) -> U {
            return unsafeBitCast(value, to: U.self)
        }
        self = cast(UnsafePointer(pointer))

    }

将指针所指向的数据塞入遵循协议的self内,而Pointee是协议定义的无意义对象,其作用在于,当子协议声明同样的属性时Pointee会被替代成相应的类型,例如,如果selfMetadata.Struct结构体时,因为Metadata.Structpointer属性是UnsafePointer<_Metadata._Struct>类型,所以这里实际上执行的代码是:

 self = cast(UnsafePointer<_Metadata._Struct>(pointer))

指针的pointee数据会被映射到_Metadata._Struct结构体中。
到此,结构体初始化完成。

var kind: Metadata.Kind {
        return Metadata.Kind(flag: UnsafePointer(pointer).pointee)
    }
    
if let kind = type(of: self).kind, kind != self.kind {
            return nil
        }

接着判断初始化为结构体的类型与指针实际所代表的类型是否相同,通过这样来依次判断传入的类对象应该通过Metadata.Struct还是Metadata.Class亦或是Metadata.ObjcClassWrapper进行下一步操作。
由此,这一步完成了关键性的操作,选择正确的容器将类对象映射到容器中,为接下来获取属性列表的操作预先配置好需要的数据。

接着,我们先来看如何获取结构体的属性列表:

func propertyDescriptions() -> [Property.Description]? {
            guard let fieldOffsets = self.fieldOffsets else {
                return []
            }
            var result: [Property.Description] = []
            let selfType = unsafeBitCast(self.pointer, to: Any.Type.self)
            class NameAndType {
                var name: String?
                var type: Any.Type?
            }
            for i in 0..

self.fieldOffsets包含了属性列表的偏移量,它作为一个计算性属性,内部如下:

guard let contextDescriptor = self.contextDescriptor else {
            return nil
        }
        let vectorOffset = contextDescriptor.fieldOffsetVector
        guard vectorOffset != 0 else {
            return nil
        }
        if self.kind == .class {
            return (0..(pointer)[vectorOffset + $0]
            }
        } else {
            return (0..(pointer)[vectorOffset * (is64BitPlatform ? 2 : 1) + $0])
            }
        }

contextDescriptor内部实现了通过获取遵循协议的结构体获取属性数量, 以及属性偏移矢量,其核心在于将指针地址根据已知的上下文描述符偏移量进行偏移(结构体为1,而类根据不同位数的硬件而有所不同,分别是8或11),然后计算相对指针偏移值,最后通过assumingMemoryBound函数将值映射到_StructContextDescriptor或者_ClassContextDescriptor结构体中,由此获得属性数量 numberOfFields, 属性偏移矢量fieldOffsetVector,在获得这两个参数之后,便可获取每个属性的偏移值。

if self.kind == .class {
            return (0..(pointer)[vectorOffset + $0]
            }
        } else {
            return (0..(pointer)[vectorOffset * (is64BitPlatform ? 2 : 1) + $0])
            }
        }

在获得偏移值数组后根据属性数量遍历循环获取属性名及属性类型。

@_silgen_name("swift_getFieldAt")
func _getFieldAt(
    _ type: Any.Type,
    _ index: Int,
    _ callback: @convention(c) (UnsafePointer, UnsafeRawPointer, UnsafeMutableRawPointer) -> Void,
    _ ctx: UnsafeMutableRawPointer
)

swift_getFieldAt是属于Swift源码的函数,而Xcode编译器的特性可以通过这种方式直接访问源码函数,该函数功能为接收类型及属性下标以及一个实例指针,在回调中获取属性名及属性类型并将数据插入到实例对象中。

由此,获得包含属性名、属性类型及属性偏移量的属性列表。

Class的属性列表获取大致与结构体相同,需要特别注明的一点是:

let instanceStart = pointer.pointee.class_rw_t()?.pointee.class_ro_t()?.pointee.instanceStart

Metadata.ClassMetadata.Struct相似,在初始化时将值映射到_Metadata._Class中,然后通过_Class结构体的函数:

func class_rw_t() -> UnsafePointer<_class_rw_t>? {
            if MemoryLayout.size == MemoryLayout.size {
                let fast_data_mask: UInt64 = 0x00007ffffffffff8
                let databits_t: UInt64 = UInt64(self.databits)
                return UnsafePointer<_class_rw_t>(bitPattern: UInt(databits_t & fast_data_mask))
            } else {
                return UnsafePointer<_class_rw_t>(bitPattern: self.databits & 0xfffffffc)
            }
        }

通过对databits进行位运算以获得类的隐藏属性_class_rw_t的值并映射到准备好的结构体_class_rw_t中,这个函数中使用的�与databits进行位运算的值来自于objc-runtime.h文件中,如何通过databits进行位运算以获取_class_rw_t是苹果设计好的规则。
接着通过映射到_class_rw_t结构体的ro值获取class_ro_t,同样进行映射,由此获得instanceStart,在获得起始值后以同样的方式依次获取属性偏移量、属性名及属性类型以及通过递归获取父类的相关参数。
由此,无论是结构体还是类,都获得了关键的属性列表数据。

二、属性赋值

let rawPointer = instance.headPointer()

在Swift3中,苹果官方提供了Unmanaged.passUnretained(AnyObject).toOpaque()的方式获取引用类型变量指向的内存地址,因此引用类型可以通过该函数获取头地址用于根据属性偏移量获取属性的内存地址,以对属性赋值。
而值类型的结构体则可以直接使用withUnsafeMutablePointer(to: &T,body: (UnsafeMutablePointer) throws -> Result(UnsafeMutablePointer) throws -> Result>)获取内存地址转化为UnsafeMutablePointer类型数据。

在获得头地址后开始遍历属性列表,根据头地址及偏移量获取每个属性的内存地址。

let propAddr = rawPointer.advanced(by: property.offset)

依次从属性的内存地址绑定到指定的属性类型进行访问,获取到属性的指针,从而进行赋值。
这里比较精细的一点是,通常我们从JSON数据得到的值类型是多种多样的,而为了对pointee进行赋值,我们又必须给其一个确定的类型,即要对其进行相应的转换。
HandyJSON在这里通过一个结构体容器将对应的属性类型通过:

withUnsafePointer(to: &extensions) { pointer in
        UnsafeMutableRawPointer(mutating: pointer).assumingMemoryBound(to: Any.Type.self).pointee = type
    }

这样的方式替换掉其pointee,然后用这个容器在赋值函数中进行类型判断:

guard let this = value as? Self else {
            return
        }

通过判断传入的数据类型与属性的类型是否匹配,来进行赋值,减少了依次判断类型的繁复代码,通过判定后最终进行赋值:

storage.assumingMemoryBound(to: self).pointee = this

你可能感兴趣的:(iOS-关于HandyJSON的个人浅析)