初识SQLite.swift
在使用Objective-C
编程语言构建项目的时候,对于SQLite数据库的操作,基本上使用的是FMDB。等到Swift发布之后,开始了Swift
与Objective-C
的混合开发,依然选择的是FMDB
进行数据库操作。直到进行纯Swift
开发,才开始寻找新的SQLite封装库。从Github的排名来看,就SQLite.swiftstar数量最高了。阅读完readme之后,感觉不错就入手了。
借助于Swift
语言强大的泛型系统、类型安全和自定义操作符等特性,把表单定义、数据库的增删改查操作封装得特别友好。未使用过得朋友,建议点击查看SQLite.swift。对于熟悉SQLite.swift
的朋友,可以直接查看我封装的SQLiteValueExtension库。
当前存储数组、字典的方案
比如属性是[BasicInfoModel]
自定义类型数组,先定义字符类型的Expression:Expression<String?>
。
在存储的时候,借助于ObjectMapper库,把数组转换为String
,代码如下:
let string = basic.infos?.toJSONString() ?? ""
let insert = config.insert(infos <- string)
try? connection.run(insert)
在从数据库读取的时候,也需要把String
转换为[BasicInfoModel]
类型,代码如下:
for data in try! connection.prepare(config) {
let basic = BasicDataModel(JSON: [String : Any]())
basic?.infos = Array<BasicInfoModel>.init(JSONString: data[parlayRules])
return basic
}
通过上面的代码可以看到最大的问题,就是数组与String类型的转换代码,散落在业务代码里面。如果有多个属性都是数组,那么同样的样板代码会写多次。如何优雅地解决这个问题呢?
好了,下面进入主题,如何通过SQLite.swift
优雅地存储数组和字典数据类型。我会从以下3个部分进行讲解:
SQLite3
数据库支持的数据类型- 遵从
Value
协议支持自定义类型存储 - 探讨如何实现存储数组和字典数据类型
SQLite3
数据库支持的数据类型
对于SQLite3
数据库仅支持以下基础类型,换言之,只要是其他的数据类型,都需要转换成其中某个类型再进行存储。从数据库取的时候,再转换为对应的数据类型。
Swift Type | SQLite Type |
---|---|
Int64 |
INTEGER |
Double |
REAL |
String |
TEXT |
nil |
NULL |
SQLite.Blob |
BLOB |
遵从Value
协议支持自定义类型存储
对于自定义类型想要存储到数据库,SQLite.swift
定义了Value
协议,来规范转换过程。这是官方文档:custom-types
下面是一个枚举类型支持Value
的示例代码:
enum Gender: String, Value {
case female
case male
typealias Datatype = String
static var declaredDatatype: String { String.declaredDatatype }
static func fromDatatypeValue(_ datatypeValue: String) -> Gender {
return Gender(rawValue: datatypeValue) ?? .female
}
var datatypeValue: String { rawValue }
}
探讨如何实现存储数组和字典数据类型
通过上面的铺垫之后,我们有了思路。那就是让数组和字典遵从Value
协议。
1.数组和字典转换成什么类型
首先要确定的是把数组和字典转换成什么类型,我的选择是String
。
2.如何转换为String
然后要确定的是如何转换为String
。先拿数组来说明,我们可以通过JSONSerialization
类把符合规范的数组转换为Data
,然后再转换为String
。示例代码如下:
if let data = try? JSONSerialization.data(withJSONObject: stringArray, options: []) {
return String(data: data, encoding: .utf8) ?? ""
}
3.如何转换为符合规范的JSONObject
然后要解决的是如何把存储任意类型的数组,转换为符合规范的JSONObject。在软件领域有一个包治百病的方案,就是“没有加一层抽象不能解决的问题,如何有,那就再加一层”。这个时候,引入StringValueExpressible
协议,让Array.Elment
遵从于该协议,让其实现与String的互相转换。StringValueExpressible
定义如下:
public protocol StringValueExpressible {
associatedtype ValueType = Self
static func fromStringValue(_ stringValue: String) -> ValueType
var stringValue: String { get }
}
通过上面的操作,我们就可以让[StringValueExpressible]
转换为[String]
。
4.数组遵从Value
的实现代码
extension Array: Value where Element: StringValueExpressible {
public typealias Datatype = String
public static var declaredDatatype: String { String.declaredDatatype }
public static func fromStringValue(_ stringValue: String) -> Self {
var result = [Element]()
if let object = try? JSONSerialization.jsonObject(with: Data(stringValue.utf8), options: []) as? [String] {
for string in object {
let value = Element.fromStringValue(string) as! Element
result.append(value)
}
}
return result
}
public var stringValue: String {
let stringArray = self.map { $0.stringValue }
if let data = try? JSONSerialization.data(withJSONObject: stringArray, options: []) {
return String(data: data, encoding: .utf8) ?? ""
}
return ""
}
public static func fromDatatypeValue(_ datatypeValue: Datatype) -> Self {
return fromStringValue(datatypeValue)
}
public var datatypeValue: Datatype {
return stringValue
}
}
我们通过where
条件语句进行了Array.Elment
遵从StringValueExpressible
协议的限制。
最后,可以总结为,只要数组中的元素遵从于StringValueExpressible
协议,该数组就遵从了Value
协议,就可以通过SQLite.swift
进行数据库存储。然后封装的SQLiteValueExtension库,内部已经让Int
、Double
、Bool
、String
、Data
、Date
等类型遵从了StringValueExpressible
协议。所以,例如[Int]
、[Date]
等数组类型,可以直接存储了。
存储数组、字典示例代码
其中BasicInfoModel
是一个遵从Value
和StringValueExpressible
协议的自定义数据类。
class BasicDAO: DataBaseAccessObject {
static let config = Table("config")
static let normalFloat = Expression<Float?>("normal_float")
static let normalInt = Expression<Int?>("normal_int")
static let normalModel = Expression<BasicInfoModel?>("normal_model")
static let normalString = Expression<String?>("normal_string")
static let intArray = Expression<[Int]?>("int_array")
static let stringArray = Expression<[String]?>("string_array")
static let modelArray = Expression<[BasicInfoModel]?>("model_array")
static let intStringDict = Expression<[Int:String]?>("int_string_dict")
static let stringModelDict = Expression<[String:BasicInfoModel]?>("string_model_dict")
class func createTable() throws {
try connection.run(config.create(ifNotExists: true) { t in
t.column(normalFloat)
t.column(normalInt)
t.column(normalString)
t.column(intArray)
t.column(intStringDict)
t.column(stringArray)
t.column(normalModel)
t.column(modelArray)
t.column(stringModelDict)
})
}
class func insertEntity(_ basic: BasicDataModel) throws {
let insert = config.insert(normalFloat <- basic.normalFloat,
normalInt <- basic.normalInt,
normalString <- basic.normalString,
normalModel <- basic.normalModel,
intArray <- basic.intArray,
stringArray <- basic.stringArray,
modelArray <- basic.modelArray,
intStringDict <- basic.intStringDict,
stringModelDict <- basic.stringModelDict)
try connection.run(insert)
}
class func queryRows() throws -> [BasicDataModel]? {
do {
let rows = try connection.prepare(config)
var result = [BasicDataModel]()
for data in rows {
let basic = BasicDataModel(JSON: [String : Any]())!
basic.normalFloat = data[normalFloat]
basic.normalInt = data[normalInt]
basic.normalString = data[normalString]
basic.normalModel = data[normalModel]
basic.intArray = data[intArray]
basic.stringArray = data[stringArray]
basic.modelArray = data[modelArray]
basic.stringModelDict = data[stringModelDict]
basic.intStringDict = data[intStringDict]
result.append(basic)
}
return result
} catch let error {
print("queryError:\(error.localizedDescription)")
}
return nil
}
}
如何使用SQLiteValueExtension
通过cocoapods引入即可:
pod 'SQLiteValueExtension'