HandyJSON仓库: https://github.com/alibaba/HandyJSON
HandyJSON
花了一天半的时间大概阅读学习了一下阿里巴巴开源的HandyJSON
库, 只能说是简单的了解一下, Swift Runtime
相关的代码没有深入了解. 但是, 收获还是满满的. 一直想深入学习一下面向协议开发的思想, HandyJSON在这方面做得就很不错, HandyJSON是一个完全面向协议开发的库, 主要的逻辑功能都被封装在不同层次协议中, 定义的协议基本都提供了默认的实现.
1.HandyJSON的层次结构
首先看HandyJSON的目录结构如下图所示.
Transforms
文件夹下主要包含swift类型和json支持类型相互转换的方法, 比如URL类型转json支持的String类型, urlString转URL类型. 协议的层次结构如下图所示:
Reflection
文件夹, 主要包含映射的具体实现过程(目前没有仔细阅读).
其余文件主要包含一些配置文件, 工具类, json转model具体的分类转发递归映射等. 如下图所示:
2.HandyJSON中主要的文件以及其作用
2.1 Export.swift
Export文件对外抛出了几个协议:
- HandyJSON: 实现序列化反序列化等;
- HandyJSONEnum: enum的序列化和反序列化;
- HandyJSONCustomTransformable: 实现协议可自定义转换方式;
2.2 Configuration.swift
- DeserializeOptions: 定义反序列化选项, 包含
caseInsensitive
,defaultOptions
两个值. - HandyJSONConfiguration: HandyJSON配置选项. 包括debug模式和反序列化设置. 变量全部是static类型.
2.3 Logger.swift
根据HandyJSONConfiguration的设置, 打印logo, Error或者Debug或者Verbose.
2.4 Serializer.swift
扩展HandyJSON协议, 将HandyJSON类型转换成JSON类型. 可以将遵守HandyJSON协议的类型转换成转换成JSONType或者JSONString, 转换jsonStirng的过程调用了toJSON()方法. 就是说先转换成json类型的对象, 再转换成json类型的string.
另外扩展了Collection
, 代码如下所示. 下面代码优点是包含了递归调用. 这样不管json有多少层次都能够方便的取出所有的值.
public extension Collection where Iterator.Element: HandyJSON {
public func toJSON() -> [[String: Any]?] {
return self.map{ $0.toJSON() }
}
public func toJSONString(prettyPrint: Bool = false) -> String? {
let anyArray = self.toJSON()
if JSONSerialization.isValidJSONObject(anyArray) {
do {
let jsonData: Data
if prettyPrint {
jsonData = try JSONSerialization.data(withJSONObject: anyArray, options: [.prettyPrinted])
} else {
jsonData = try JSONSerialization.data(withJSONObject: anyArray, options: [])
}
return String(data: jsonData, encoding: .utf8)
} catch let error {
InternalLogger.logError(error)
}
} else {
InternalLogger.logDebug("\(self.toJSON()) is not a valid JSON Object")
}
return nil
}
}
2.5 Deserializer.swift
扩展SwiftJSON类型, 将json字符串或者json对象序列化成一个类对象.
扩展中提供很多deserialize
方法, 这里的方法做一些基本的判断处理, 之后具体的映射过程被放在_ExtendCustomModelType
协议中.
2.6 TransformType.swift
定义了json支持的数据类型和swift中数据类型的转换.
2.7 Measuable.swift
_Measurable
协议, 定义支持操作内存基本的方法.
2.8 Transformable.swift
_Transformable
继承自_Measurable
, 主要作用是将不同类型的转换过程分发到不同的实现中去.
学习与进阶
1. associatedtype和typealias
在定义协议时候可以通过associatedtype
关键字声明一个或者多个关联类型作为协议定义的一部分, 然后在协议的实现中通过typealias
给关联类型赋值是非常有用的, 这样可以将协议中方法参数具体类型的声明延迟到具体的方法实现中去.
2.url转码
有时候url中包含汉子会导致打不开网页, 找不到网址等等的麻烦, 可以通过下面的方法addingPercentEncoding
给String转码, 这样汉子会变成带有%号的符号, 这样就可以正常访问了.
guard let escapedURLString = URLString.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed) else {
return nil
}
3.关于协议的用法
如下代码是苹果API中RawRepresentable
的定义和部分实现. 这段代码好在在协议中定义RawValue
类型, 并将RawValue具体类型的定义延迟到了具体实现中. 在实现协议时候可以通过where
关键字, 判断RawValue类型并做不同的实现.
public protocol RawRepresentable {
associatedtype RawValue
public init?(rawValue: Self.RawValue)
public var rawValue: Self.RawValue { get }
}
extension RawRepresentable where Self.RawValue == Bool {
public func encode(to encoder: Encoder) throws
}
4.Self
关键字
'Self' is only available in a protocol or as the result of a method in a class
- Self可以用于协议(protocol)中限制相关的类型
- Self可以用于类(Class)中来充当方法的返回值类型
//第一种用法
protocol Copyable {
func copy() -> Self
func clamp(intervalToClamp: Self) -> Self
}
//第二种用法
class A: Copyable {
class func calssFunc() -> Self {
let type = self
print(type)
let result = type.init()
return result
}
}
在协议中Self
代表本类型的一个实例, Self.self
可以代表一个类型本身.
5.RawRepresentable协议
一个可以用来表示另一种类型的类型. enum实现了RawRepresentable协议, 因此class EnumTransform<T: RawRepresentable>
中的T
可以看成是enum类型(当然可以是任何实现RawRepresentable协议的类型).
//RawRepresentable协议的声明
public protocol RawRepresentable {
associatedtype RawValue
public init?(rawValue: Self.RawValue)
public var rawValue: Self.RawValue { get }
}
6.swift预编译设置
如下代码所示, 在swift中一样可以通过#if
, #else
, #endif
这样的方法判断不同的系统, 手机型号等等等
#if os(iOS) || os(tvOS) || os(watchOS)
import UIKit
#else
import Cocoa
#endif
7.声明协议标准
如下所示, 如果一个协议有默认实现时候, 一般情况下我们可以将协议方法的定义和实现全部放在扩展中, 这样遵守协议时候就不需要实现任何方法(纯swift中协议中方法必须全部实现).有看到swift语言API和很多三方库都是这么做的.
protocol _TestProtocol {}
extension _TestProtocol {
func testRun() {
print("run")
}
}
extension UIView: _TestProtocol {
func testRun() {
print("UIView run")
}
}
8.associatedtype和typealias关键字
8.1 重命名
typealias
关键字从来为已经存在的类型重新定义名字, 通过命名, 可以让代码变得更加清晰. 主要功能是增加代码的可读性.
typealias与泛型
, typealias是单一的, 也就是说你必须指定将某个特定的类型通过typealias赋值为新的名字, 而不能将整个泛型类型进行重命名.
//如下代码是不能编译通过的
class Person<T> {}
typealias Woker = Person
typealias Worker = Person<T>
//如下, 如果我们在别名中也引入泛型, 则是可以编译通过的
class Person<T> {}
typealias Woker = Person
typealias Worker<T> = Person<T>
8.2 组合协议类型
如下所示, 可以通过&
运算符将多个协议组合在一起并重命名.
protocol Cat {}
protocol Dog {}
typealias Pat = Cat & Dog
8.3 associatedtype关联类型
定义一个协议时, 有时候声明一个或者多个关联类型作为协议的一部分会十分有用. 关联类型为协议中的某个类型提供了一个占位名(或者说别名), 其代表的实际类型在协议被采纳时才会被指定. 你可以通过associatedtype
关键字来指定关联类型.
//模型
struct Model {
let age: Int
}
//协议,使用关联类型
protocol TableViewCell {
associatedtype T
func updateCell(_ data: T)
}
//遵守TableViewCell
class MyTableViewCell: UITableViewCell, TableViewCell {
typealias T = Model
func updateCell(_ data: Model) {
// do something ...
}
}
疑问
1.TransformType协议中func transformFromJSON(_ value: Any?) -> Object?
value为什么不用JSON
声明呢? 用JSON
声明之后, 遵循协议时候不是不用转换类型了么?
public protocol TransformType {
associatedtype Object
associatedtype JSON
func transformFromJSON(_ value: Any?) -> Object?
func transformToJSON(_ value: Object?) -> JSON?
}