lufei's Studio.

HandyJSON 原理浅析

字数统计: 1.5k阅读时长: 5 min
2021/02/03 Share

前言

在 apple 推出 Codable 之前,切换到 swift 的开发者,面对 json 解析处理的问题时,应该都会感受到不是很方便。

由于 swift 本身的语言限制,我们在 OC 时代习惯的那些解析方式并不能很方便的套用在 swift 上,幸好,HandyJSON 在那时出现了,并且 HandyJSON 解决问题的方式也很让人很赞叹,通过直接在内存中赋值的方式实现了一个纯 swift 的 json 解析库。

虽然现在 swift 的开发者们拥有了官方支持的 Codable,但是 HandyJSON 的开发者当时做出的这个创新的 json 解决方案,还是很值得学习的,于是就有了今天的这篇博客。

HandyJSON 简单使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import HandyJSON

class BaseResponse<T: HandyJSON>: HandyJSON {
var code: Int?
var data: T?
required init() {}
}

struct SampleData: HandyJSON {
var id: Int?
}

let sample = SampleData(id: 2)
let resp = BaseResponse<SampleData>()
resp.code = 200
resp.data = sample

/// 从对象实例转换到JSON字符串
let jsonString = resp.toJSONString()!
print(jsonString)

/// 从字符串转换为对象实例
if let mappedObject = JSONDeserializer<BaseResponse<SampleData>>.deserializeFrom(json: jsonString) {
print(mappedObject.data!.id!)
}

具体建议大家还是去看看作者的介绍和源码。

HandyJSON 如何实现

通过查阅别的大神和作者自己的介绍,实现过程大概是这样:

找到你自定义的模型类型的属性和属性的类型,然后再准备你要反序列化的 json,将 json 转换成 Native 的基础类型,比如字典,数组,或者 json 里面类型能映射过来的类型,然后创建一个你自定义模型的实例,然后找到这个实例的内部内存布局的头指针,然后根据属性所占的内存大小,进行赋值然后偏移,找下一个属性。

说起来,也不算很长的一句话,但其实,要想真的实现这个过程,其实是很不容易的,想到这里,再一次对 HandyJSON 的作者表达敬意。

几个需要了解的 swift 知识

Mirror

swift 的 Mirror 是用来查看任意类型的实例的子结构和显示样式的。

值得注意的是,这是一套完全与 iOS runtime 无关的机制

Virtual Method Table

Virtual Method Table 是 swift 中用来在运行时对方法进行绑定的机制。

内存分配

  • Stack(栈),存储值类型的临时变量,函数调用栈,引用类型的临时变量指针

  • Heap(堆),存储引用类型的实例

MemoryLayout

MemoryLayout 是 Swift3.0 推出的一个工具类,用来计算数据占用内存的大小。

三个有用的属性,都是 Int:

  • alignment:内存对齐原则

  • size:一个 T 数据类型实例占用连续内存字节的大小

  • stride:在一个 T 类型的数组中,其中任意一个元素从开始地址到结束地址所占用的连续内存字节的大小就是 stride

内存对齐

许多计算机系统对基本数据类型的合法地址做出了一些限制,要求某种数据类型对象的地址必须是某个值 K (通常是 2、4 或者 8 ) 的倍数。这种对齐限制简化了形成处理器和内存系统之间接口的硬件设计。

对齐原则是任何 K 字节的基本对象的地址必须是 K 的倍数。

MetaData

swift 运行时会为程序中使用的每种类型(包括泛型类型的每个实例化)保留 MetaData 。调试器工具可以使用这些 MetaData 来发现有关类型的信息。 对于非泛型的普通类型,这些 MetaData 由编译器静态生成。 对于泛型类型的实例,MetaData 由运行时根据需要延迟创建。每种类型都有唯一的 MetaData ;如果类型相等,则两个 MetaData 指针值相等

这个也是 HandyJSON 用来获取类型信息的关键依据。

swift 指针操作

由于需要操作内存,指针的操作必不可少,常用的 swift 指针类型如下:

  • unsafePointer 等同于 const T *

  • unsafeMutablePointer 等同于 T *

  • unsafeRawPointer 等同于 const void *

  • unsafeMutableRawPointer 等同于 void *

我来试试

纸上谈兵,总是不够的,最好还是自己试试。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/// test class
class Human {
var age: Int = 0
/// from HandyJSON
func headPointerOfClass() -> UnsafeMutablePointer<Int8> {
let opaquePointer = Unmanaged.passUnretained(self as AnyObject).toOpaque()
let mutableTypedPointer = opaquePointer.bindMemory(to: Int8.self, capacity: MemoryLayout<Human>.stride)
return UnsafeMutablePointer<Int8>(mutableTypedPointer)
}
}

/// from HandyJSON
var is64BitPlatform: Bool {
return MemoryLayout<Int>.size == MemoryLayout<Int64>.size
}

/// from HandyJSON
var contextDescriptorOffsetLocation: Int {
return is64BitPlatform ? 8 : 11
}

/// test
let human = Human()
let intFromJson = 1000

/// human Heap void * pointer
let humanRawPtr = UnsafeMutableRawPointer(human.headPointerOfClass())

let humanAgePtr = humanRawPtr.advanced(by: contextDescriptorOffsetLocation + MemoryLayout<Int>.size).assumingMemoryBound(to: Int.self)
print(human.age)

/// write
humanAgePtr.initialize(to: intFromJson)
print(human.age)

print(MemoryLayout<Int>.size)

上面简单实现了 swift class 的属性的内存赋值,其他的类型和更复杂的实现,大家可以参考作者源码。

总结

虽然现在因为官方推出了支持的框架,使得 HandyJSON 可能不会像之前那么流行,不过这样独特的解决问题的思路,非常值得学习,并且对于像作者这样优秀的开发者,我真的非常佩服和羡慕。

这次学习,真是让我受益匪浅,也对自己浅薄的知识储备感到惭愧,希望自己再努力一点QAQ

参考

[HandyJSON] 设计思路简析

关于实现原理和Swift版本兼容性问题

Swift 对象内存模型探究(一)

Swift Method Dispatching

Swift 中的消息派发

Swift库二进制接口(ABI)兼容性研究

HandyJSON是如何实现的?

Type Metadata

swift-evolution

Swift 5 Type Metadata 详解

CATALOG
  1. 1. 前言
  2. 2. HandyJSON 简单使用示例
  3. 3. HandyJSON 如何实现
  4. 4. 几个需要了解的 swift 知识
    1. 4.1. Mirror
    2. 4.2. Virtual Method Table
    3. 4.3. 内存分配
    4. 4.4. MemoryLayout
    5. 4.5. 内存对齐
    6. 4.6. MetaData
  5. 5. swift 指针操作
  6. 6. 我来试试
  7. 7. 总结
  8. 8. 参考