HandyJSON的基本原理就是从类信息里获取所有属性的特征,包括名称,属性在内存里的偏移量、属性的个数、属性的类型等等,然后将服务端返回来的数据用操作内存的方式将数值写入对应的内存,来实现json转model。
咱们现在就开始从HandyJSON的源码来分析,是怎么将服务端返回的数据解析出来并赋值给Model的,第一讲先说怎么从类信息里获取变量名称、类型等,先看两个常见的题目
题目1
- 一个经典的问题,如下:声明一个Animal类,有一个eat方法,然后再声明Cat、Dog类继承自Animal,并且重写eat方法
class Animal {
func eat() {
print("Animal eat")
}
}
class Cat: Animal {
override func eat() {
print("cat eat")
}
}
class Dog: Animal {
override func eat() {
print("Dog eat")
}
}
然后声明一个方法,参数类型是Animal,然后调用animal的eat
func printfEat(animal:Animal) {
animal.eat()
}
printfEat(animal: Dog())
打印结果:Dog eat
咦,方法的类型参数是Animal,他怎么知道我传进去的是dog?
问题2
- 再看一个问题,如下:声明一个类Class1,然后再声明一个结构体Struct1,里面都有两个变量a、b。然后看看Class1、Struct1初始化的实例占用多大内存
class Class1 {
let value1: Int64 = 10
let value2: Int64 = 12
}
struct Struct1 {
let value1: Int64 = 10
let value2: Int64 = 12
}
print(class_getInstanceSize(Class1.self))//因为是引用类型,所用不能直接用MemoryLayout。否则得出的是指针的大小
print(MemoryLayout.size)
打印结果:
32
16
为啥呢,结构体和类里都只有两个Int类型,应该都只有16个字节,为啥类会有32个字节。现在咱们打印一下他们存的啥
var class1 = Class1()
let opaquePointer = Unmanaged.passUnretained(class1).toOpaque()
let class1Point = UnsafeRawPointer(opaquePointer)
print(class1Point)
var struct1 = Struct1()
let struct1Point = withUnsafePointer(to: &struct1) { $0 }
print(struct1Point)
打印结果:
class1 的地址是:0x0000000174221680
struct1 的地址是:0x000000016fd7df58
(lldb) x/4g 0x0000000174221680//打印类里面的32个字节啥是
0x174221680: 0x00000001000f6500 0x0000000000000002
0x174221690: 0x000000000000000a 0x000000000000000c
x/2g 0x000000016fd7df58打印结构体里面的16个字节啥是
0x16fd7df58: 0x000000000000000a 0x000000000000000c
结构体存储的是变量value1、value2的值,也就是 a、c(十进制表示是 10,12)
类中存储的前16个字节不是变量value1、value2的值,后16个字节才是
那前16个字节是啥呢
结论
类的实例中,除了存变量属性外,还会在最开始的16字节存储别的信息,前8个字节是类信息(metadata)的地址,也就是自己属于哪个类,就指向哪个类。
后面的8个自己是指的引用计数,再往后面的数据才是变量属性的值。
这也就能解释咱们问题1中的现象,当调用printfEat函数时,这个函数时怎么知道传进来的是dog实例,因为传进来的实例,自己带着自己的信息,也就是带着我是属于哪个类
咱们怎么确定类的实例里带的信息确实是类的地址呢,咱们来验证一下,咱们再直接打印一下Class1这个类的地址
func typePoint(anyType: Any.Type) {
let point: UnsafePointer = unsafeBitCast(anyType, to: UnsafePointer.self)
print(point)
}
typePoint(anyType: Class1.self)
打印结果: 0x00000001000f6500
之前实例里存储的是:
x/4g 0x0000000170220320
0x170220320: 0x00000001000f6500 0x0000000000000002
0x170220330: 0x000000000000000a 0x000000000000000c
所以这个结论是正确的
接下来咱们来说HandyJSON,HandyJSON是强依赖 metadata 结构的,也就是依赖某个类信息的结构,如果官方把这个结构一改,HandyJSON就无效了,然后这个类信息长啥样呢,或者说0x00000001000f6500这个地址指向的内容是啥呢,如下图,我是通过HandyJSON代码逻辑捋出来的
再说几个小知识点
1. 前面用到的查看内存信息的小指令x/4g
x:表示用16进制打印(d是十进制)
4:表示打印4组数据
g:表示每组数据8个字节(b – byte 1字节,h – half word 2字节,w – word 4字节和最后的g – giant word 8字节。)
所以x/4g就是以十六进制表示,打印四组数据,每组8个字节,就出现了之前的
(lldb) x/4g 0x0000000174221680
0x174221680: 0x00000001000f6500 0x0000000000000002
0x174221690: 0x000000000000000a 0x000000000000000c
如果我写x/2w则是下面这样
x/2w 0x0000000170220320
0x170220320: 0x000f6500 0x00000001
2. HandyJSON帮我们做的类型转换
当我们mode的类型写的是Int,但是服务端返回的是字符串、我model写的是字符串,结果服务端给我返回了布尔。这些情况HandyJSON都帮咱们做了转换,比如
extension IntegerPropertyProtocol {
static func _transform(from object: Any) -> Self? {
switch object {
case let str as String:
return Self(str, radix: 10)
case let num as NSNumber:
return Self(num)
default:
return nil
}
}
func _plainValue() -> Any? {
return self
}
}
extension String: _BuiltInBasicType {
static func _transform(from object: Any) -> String? {
switch object {
case let str as String:
return str
case let num as NSNumber:
// Boolean Type Inside
if NSStringFromClass(type(of: num)) == "__NSCFBoolean" {
if num.boolValue {
return "true"
} else {
return "false"
}
}
return formatter.string(from: num)
case _ as NSNull:
return nil
default:
return "\(object)"
}
}
func _plainValue() -> Any? {
return self
}
}
extension Bool: _BuiltInBasicType {
static func _transform(from object: Any) -> Bool? {
switch object {
case let str as NSString:
let lowerCase = str.lowercased
if ["0", "false"].contains(lowerCase) {
return false
}
if ["1", "true"].contains(lowerCase) {
return true
}
return nil
case let num as NSNumber:
return num.boolValue
default:
return nil
}
}
func _plainValue() -> Any? {
return self
}
}