ModelAdaptor
模型适配器:一次定义,处处可用!其最终目标是在数据解析、数据库存储等地方,只需要定义一次数据模型就可以解析并使用。目前仅支持ObjectMapper数据解析、SQLite.swift数据存储。
这个库的灵感来源于Java
语言的注解特性,感兴趣的可以点击了解Android Jetpack Room库的简单使用,了解如何使用注解来简化数据库存储。
然后依赖于Swift 5.1提供的Property wrapper
特性,感兴趣的可以看一下这篇文章:Swift中的Property wrappers
我们现在来看一个简单使用示例,看看ModelAdaptor
是如何简化我们的代码的!
使用示例
定义Model
遵循ModelAdaptorModel
协议,非可选值普通类型使用@Field
进行注解,可选值类型使用@FieldOptional
进行注解。
class CustomModel: ModelAdaptorModel {
@Field(key: "level")
var vipLevel: Int = 1
@FieldOptional
var accountID: Int?
required init?(map: Map) {
}
}
经过这一步,ObjectMapper
层的数据解析就已经完成。无需实现func mapping(map: Map)
方法和添加类似self.vipLevel <- map["level"]
的代码。
数据库DAO定义
创建CustomDAO
类,遵循ModelAdaptorDAO
协议,将关联类型Entity
设置为CustomModel
。然后实现协议要求的提供的connection
和table
属性。至此,整个数据库层的定义就已完成。不再需要自己编写增删改查的样板代码。
class CustomDAO: ModelAdaptorDAO {
typealias Entity = CustomModel
var connection: Connection = try! Connection("\(NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0])/db.sqlite3")
var table: Table = Table("user")
required init() {
}
}
开始使用
通过JSON字典数据创建model。
let jsonDict = ["accountID" : 123, "level" : 10]
let model = CustomModel(JSON: jsonDict)!
创建dao实例并创建数据库表单
let dao = CustomDAO()
dao.createTable()
插入数据
try? dao.insert(entity: model)
删除数据
try? dao.delete(model.$accountID.expression == 123)
更新数据
model.vipLevel = 100
try? dao.update(entity: model, model.$accountID.expression == 123)
查询数据
//查询全部
let queryAll = try? dao.queryAll()
//条件查询
let queryOne = try? dao.query(model.$accountID.expression == 123)
使用总结
以上就是一个简单的使用场景,需要通过@Field
、@FieldOptional
注解,在ObjectMapper
和SQLite.swift
两端就可以无缝使用了。尤其是数据库操作,无需编写大量的样板代码。
详细说明
对于普通情况使用起来非常方便,对于一些特殊的数据类型,就需要进行一些兼容处理。下面分别通过ObjectMapper
和SQLite.swift
两方面的说明。
ObjectMapper
特殊处理
自定义codingKey
数据解析时定义的key,确定优先级从高到低:key>codingParams.key>propertyName
- 使用key
@FieldOptional(key: "nick_name")
var nickName: String?
这里的codingKey就是nick_name
。
- 使用
codingParams.key
@FieldOptional(codingParams: .init(key: "nick_name_custom", convertor: NilTransform<String>()))
var nickName: String?
这里的codingKey就是nick_name_custom
。 因为CodingParams
是一个泛型类型,所以即使不需要自定义convertor,也需要传递一个NilTransform类型实例,防止编译器报错。
- 使用属性名
@FieldOptional
var nickName: String?
这里的codingKey就是nickName
。
自定义convertor
@Field(codingParams: .init(convertor: DateTransform()))
var registerDate: Date = Date()
nested、delimiter、ignoreNil自定义
@Field(codingParams: .init(key: nil, convertor: NilTransform<String>(), nested: nil, delimiter: ".", ignoreNil: false))
var userName: String = "名字"
复杂类型自定义map过程
//todo:列出支持的类型,不支持的类型列表 对于数据类型为数组、字典、Set等,需要使用@FieldCustom注解。然后实现func customMap(map: Map)
方法进行自定义转换,如下所示:
@FieldCustom
var nests: [NestModel] = [NestModel]()
@FieldCustom
var customDict: [String: NestModel]?
@FieldCustom
var customDictInt: [Int : NestModel]?
@FieldCustom
var customDictAarray: [String: [NestModel]]?
var customSet: Set<String>?
func customMap(map: Map) {
self.nests <- map["nests"]
self.customDict <- map["custom_dict"]
self.customDictAarray <- map["custom_dict_array"]
}
SQLite.swift
特殊处理
自定义storageParams.key
同codingKey一样,可以通过默认属性名、默认自定义key、storageParams.key完成
@Field(storageParams: .init(key: "user_name"))
var userName: String = "名字"
自定义storageParams.isNewField和defaultValue
在首次建表后,对于新增属性需要将isNewField设为true,这样在dao调用createTable
方法时,这将针对该属性调用addColumn
方法,将该新增属性添加至已有表中。与isNewField结合使用defaultValue。
@Field(key: "amount", storageParams: .init(isNewField: true, defaultValue: 100))
var amount: Double = 6
存储自定义类型
//todo:sqlite支持的类型列表 遵循SQLiteValueProvider
协议并实现相关方法
//定义NestModel
struct NestModel: ModelAdaptorModel, SQLiteValueProvider {
@FieldOptional(key: "nest_name")
var nestName: String?
@Field(key: "age")
var nestAge: Int = 0
init?(map: Map) {
initExpressions()
}
init() {
initExpressions()
}
init?(value: String) {
self.init(JSONString: value)
}
func value() -> String? {
return self.toJSONString()
}
init?(stringValue: String) {
self.init(JSONString: stringValue)
}
func stringValue() -> String? {
return value()
}
}
//在CustomModel中使用
@FieldOptional
var nest: NestModel?
存储数组、字典、Set等数据类型
存储数组
只需要Array.Element实现SQLiteValueProvider
即可。例如[NestModel]
、[Int]
。对于String
、Int
、Double
、Date
、Data
等基本类型,已经默认实现了SQLiteValueProvider
协议。自定义类型需要自己实现。
存储字典
只需要Dictionay.key
和Value
实现SQLiteValueProvider
即可。例如[String: NestModel]
、[Int : NestModel]
、[String: [NestModel]]
。
存储SET
需要自己实现处理存储过程,实现ModelAdaptorCustomStorage
协议,如下所示:
extension CustomModel: ModelAdaptorCustomStorage {
static let customSetExpression = Expression<String?>("custom_set")
func createColumn(tableBuilder: TableBuilder) {
tableBuilder.column(CustomModel.customSetExpression)
}
func addColumn(table: Table) { }
func setters() -> [Setter] {
guard let set = customSet else {
return []
}
guard let data = try? JSONSerialization.data(withJSONObject: Array(set), options: []) else {
return []
}
return [CustomModel.customSetExpression <- String(data: data, encoding: .utf8)]
}
func update(with row: Row) {
guard let string = row[CustomModel.customSetExpression] else {
return
}
let data = Data(string.utf8)
guard let stringArray = try? JSONSerialization.jsonObject(with: data, options: []) as? [String] else {
return
}
self.customSet = Set(stringArray)
}
}
DAO层使用
ModelAdaptorDAO
协议默认实现了常用的增删改查方法:
func createTable(ifNotExists: Bool)
func insert(entity: Entity) throws
func insert(entities: [Entity]) throws
func deleteAll() throws
func delete(_ predicate: SQLite.Expression<Bool>) throws
func delete(_ predicate: SQLite.Expression<Bool?>) throws
func update(entity: Entity, _ predicate: SQLite.Expression<Bool>) throws
func update(entity: Entity, _ predicate: SQLite.Expression<Bool?>) throws
func query(_ predicate: SQLite.Expression<Bool>) throws -> Entity?
func query(_ predicate: SQLite.Expression<Bool?>) throws -> Entity?
func queryAll() throws -> [Entity]?
如果需要实现其他数据库操作,可以参考示例代码:
//CustomDAO添加的自定义方法
func customUpdate(entity: Entity) throws {
let statement = table.update(entity.$vipLevel.expression <- entity.vipLevel)
try connection.run(statement)
}
SQLite.swift
,只处理ObjectMapper
不需要实现ModelAdaptorMappable
协议即可。
struct OnlyMap: ModelAdaptorMappable {
@FieldOptional(key: "nick_name")
var nickName: String?
@Field
var age: Int = 6
init?(map: Map) {
}
}
总结
对于新的特性总会感到非常兴奋,如果能利用它们提高工作效率那就更完美了。最初了解过Java的注解功能时,觉得十分强大。恰好Swift 5.1带来了Property Wrapper
特性,抱着试一试的态度,制作了ModelAdaptoer
库。
目前ModelAdaptoer
处于测试阶段,已在小项目上进行实践。感兴趣的朋友,可以一起优化和促进它的发展。虽然目前有一些限制,但带来的便利也是非常巨大的。期待您的加入,让ModelAdaptoer
变得更加强大。
安装
Cocoapods
pod 'ModelAdaptor'