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<Self>.deserializeFrom(dict: dict, designatedPath: designatedPath)
    }

    public static func deserialize(from json: String?, designatedPath: String? = nil) -> Self? {
        return JSONDeserializer<Self>.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<Int>.self))
        if let kind = type(of: self).kind, kind != self.kind {
            return nil
        }
    }

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

...
protocol PointerType : Equatable {
    associatedtype Pointee
    var pointer: UnsafePointer<Pointee> { get set }
}
...
init<T>(pointer: UnsafePointer<T>) {
        func cast<T, U>(_ value: T) -> U {
            return unsafeBitCast(value, to: U.self)
        }
        self = cast(UnsafePointer<Pointee>(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<Int>(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.numberOfFields {
                var nameAndType = NameAndType()
                _getFieldAt(selfType, i, { (name, type, nameAndTypePtr) in
                    let name = String(cString: name)
                    let type = unsafeBitCast(type, to: Any.Type.self)
                    let nameAndType = nameAndTypePtr.assumingMemoryBound(to: NameAndType.self).pointee
                    nameAndType.name = name
                    nameAndType.type = type
                }, &nameAndType)
                if let name = nameAndType.name, let type = nameAndType.type {
                    result.append(Property.Description(key: name, type: type, offset: fieldOffsets[i]))
                }
            }
            return result
        }

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..<contextDescriptor.numberOfFields).map {
                return UnsafePointer<Int>(pointer)[vectorOffset + $0]
            }
        } else {
            return (0..<contextDescriptor.numberOfFields).map {
                return Int(UnsafePointer<Int32>(pointer)[vectorOffset * (is64BitPlatform ? 2 : 1) + $0])
            }
        }

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

if self.kind == .class {
            return (0..<contextDescriptor.numberOfFields).map {
                return UnsafePointer<Int>(pointer)[vectorOffset + $0]
            }
        } else {
            return (0..<contextDescriptor.numberOfFields).map {
                return Int(UnsafePointer<Int32>(pointer)[vectorOffset * (is64BitPlatform ? 2 : 1) + $0])
            }
        }

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

@_silgen_name("swift_getFieldAt")
func _getFieldAt(
    _ type: Any.Type,
    _ index: Int,
    _ callback: @convention(c) (UnsafePointer<CChar>, 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<Int>.size == MemoryLayout<Int64>.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<T>) throws -> Result(UnsafeMutablePointer<T>) throws -> Result>)获取内存地址转化为UnsafeMutablePointer<Byte>类型数据。

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

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
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,670评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,928评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,926评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,238评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,112评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,138评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,545评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,232评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,496评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,596评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,369评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,226评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,600评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,906评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,185评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,516评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,721评论 2 335

推荐阅读更多精彩内容