ValueTransformer如其名,就是进行值转换的,将一个数据结构转换为另外的数据结构,比如当后台传入模型与显示模型存在较大差异时使用。
ValueTransformer类介绍
本身是一个抽象类,用来进行值转换,使用的时候需要继承创建子类实现其方法。
转换器基本信息方法
向外部传递基本的转换结果类型, 是否允许反转等,注意是类方法哟。
/// 转换输出类型类
open class func transformedValueClass() -> AnyClass
/// 是否允许反转, 默认是true, 如果重写的时候也设置为允许,则应该实现对应反转方法
open class func allowsReverseTransformation() -> Bool
值转换实现方法
转换方法内部应该实现自定义转换和返回。
/// 转换方法实现
open func transformedValue(_ value: Any?) -> Any?
/// 反转方法实现
open func reverseTransformedValue(_ value: Any?) -> Any?
基于命名注册与使用
/// 实际使用
open class func setValueTransformer(_ transformer: ValueTransformer?, forName name: NSValueTransformerName)
官网使用实例:
class ClassNameTransformer: ValueTransformer {
override class func transformedValueClass() -> AnyClass {
return NSString.self
}
override class func allowsReverseTransformation() -> Bool {
return false
}
override func transformedValue(_ value: Any?) -> Any? {
return (value as AnyObject).className
}
}
extension NSValueTransformerName {
static let classNameTransformerName = NSValueTransformerName(rawValue: "ClassNameTransformer")
}
/// 注册
ValueTransformer.setValueTransformer(ClassNameTransformer(), forName: .classNameTransformerName)
/// 实例化 [以下是笔者自行添加]
let transformer = ValueTransformer(forName: .classNameTransformerName)
/// 实际转换
let transformerValue = trnsformer.transformedValue(xxx)
实际应用
订单详情后台模型转为数组模型
比如,现在有一个订单详情页面如下:
后台返回数据格式如下:
{
"amount": 2,
"status": 1,
"statusName": "待支付",
"account": 20198978977,
"time": "2019-12-12",
"payFee": 100,
"discountFee": 10,
"payTypeName": "支付宝支付",
"orderNo": "239810678"
}
常规做法
最基本的做法是,创建一个对应后台的模型,使用storyboard逐个添加label,然后映射代码赋值。
可扩展和数据驱动性方案
但是。。。。but, 这个页面现在是这样,那以后了,万一明天需求大大又需要增加一行了,如果数据源上着手了。这个时候就很容易想到UITableView + 数组驱动,一行就相当于一个cell,原谅我们项目里万物页面皆可TableView。
那么问题来了,后台返回的数据格式就不能满足要求,这个时候就可以转换器将后台模型转为数组,这样如果后期增加减少行,只需要在转换器中操作即可,这个转换器就达到了中间操作层作用。
后台模型
struct OrderInfoModel: Codable {
var amount: Int = 0
/// 订单状态
var status: Int = 0
var statusName: String = ""
var account: Int = 0
/// 支付时间
var time: String = ""
var payFee: Double = 0
/// 优惠价格
var discountFee: Double = 0
var payTypeName: String = ""
var orderNo: String = ""
}
Transformer文件
typealias OrderInfoItem = (title: String, content: String)
class OrderInfoTansformer: ValueTransformer {
override class func transformedValueClass() -> AnyClass {
return OrderInfoItem.self as! AnyClass
}
override class func allowsReverseTransformation() -> Bool {
/// 这里没有反转需求,所以设置为false
return false
}
/// 将OrderInfo
override func transformedValue(_ value: Any?) -> Any? {
guard let model = value as? OrderInfoModel else {
return nil
}
let dataSource: [(title: String, content: String)] = [
("订单号:", model.orderNo),
("订单状态",model.status == 1 ? "待付款" :"待使用"),
("支付方式", model.payTypeName),
("支付价格","\(model.payFee)")]
return dataSource
}
//这里我不需要反转
// override func reverseTransformedValue(_ value: Any?) -> Any? {
//
// }
}
extension NSValueTransformerName {
static let orderInfo = NSValueTransformerName("OrderInfoTansformer")
}
实际转换使用
private func transformer() {
let decoder = JSONDecoder()
let orderInfo = try! decoder.decode(OrderInfoModel.self, from: orderInfoJson.data(using: .utf8)!)
// 先注册
ValueTransformer.setValueTransformer(OrderInfoTansformer(), forName: .orderInfo)
// 实例化
let transformer = ValueTransformer(forName: .orderInfo)
// 实际转换
if let dataSource = transformer?.transformedValue(orderInfo) {
print("转换成功\n\(dataSource)")
}
}
最后打印结果:
转换成功
(
"(title: \"\U8ba2\U5355\U53f7\Uff1a\", content: \"239810678\")",
"(title: \"\U8ba2\U5355\U72b6\U6001\", content: \"\U5f85\U4ed8\U6b3e\")",
"(title: \"\U652f\U4ed8\U65b9\U5f0f\", content: \"\U652f\U4ed8\U5b9d\U652f\U4ed8\")",
"(title: \"\U652f\U4ed8\U4ef7\U683c\", content: \"100.0\")"
)
自定义结构转换
在使用了ValueTransformer之后,我觉得诶,有点麻烦,其实核心代码在transformedValue()
方法中。我为什么要定义一个类了。于是想想能不能给他用泛型来搞定。
操刀
定义转换结构体
注意这个结构体是通用的,实现转换的地方只需要关心闭包。
// 转换类型
struct Transformer<Old,New> {
let closure: (Old) -> New
func valueTransformer(old: Old) -> New {
return closure(old)
}
}
在Model或者ViewModel或者任何你喜欢的地方补全转换方法。我这里直接在model添加一个计算属性。
这里相当于ValueTransformer的自定义类转换过程,但是代码量会少很多
typealias OrderItem = (title: String, content: String) // 别名一下枚举,提高代码简洁可读性。
struct OrderInfoModel: Codable {
...
var transformerToTuple: [OrderItem] {
let transformer = Transformer<OrderInfoModel,[OrderItem]> {
oleValue -> [OrderItem] in
// 转换过程
let dataSource: [OrderItem] = [
("订单号:", oleValue.orderNo),
("订单状态",oleValue.status == 1 ? "待付款" :"待使用"),
("支付方式", oleValue.payTypeName),
("支付价格","\(oleValue.payFee)")]
return dataSource
}
return transformer.valueTransformer(old: self)
}
}
具体使用的地方
private func transformer() {
let decoder = JSONDecoder()
let orderInfo = try! decoder.decode(OrderInfoModel.self, from: testJson.data(using: .utf8)!)
let dataSource = orderInfo.transformerToTuple // 一句代码即可获得
print(dataSource)
}
有同学可能要问,垃圾,直接定义一个方法转换不就完了吗? 没任何问题,就像系统为啥要定义那么多类一样,就是想告诉你,你使用这个类大概是干什么,以后你的小伙伴见到Transformer就明白了,这里是进行模型变换。