一个用于序列化、持久化和恢复 Swift 对象的包。
任何符合 Storable
协议的对象都可以轻松地写入和读取数据库。
// Lets define a database first
// Realistically this would be managed at the application level
let database = SerializationDatabase()
// Here's the object we want to read/write
let person = Person(name: "Andre", height: 188.0)
写入数据库。成功返回 true
,失败返回 false
。
// Writes our person object to the database
// Generates a random record ID
database.write(Record(data: person))
// You can also provide your own record ID
// (Overrides any record with the same ID)
database.write(Record(id: "myID", data: person))
有三种从数据库读取数据的方法。
// Reads a specific Person object by their record ID
let readPerson: Person? = database.read(id: "myID")
// Reads all saved Person objects
let readPeople: [Person] = database.read()
// Reads all saved Person record IDs
let readPeopleIDs: [String] = database.readIDs(Person.self)
我们可以统计记录数。如果需要,我们可以指定特定的对象类型。
// Counts all records in the database
let count = database.count()
// Counts the number of People records
let peopleCount = database.count(Person.self)
我们可以删除记录,甚至清空数据库。
// Delete a record with specific record ID
// (Returns true if a record was deleted)
database.delete(id: "myID")
// Delete all Person records
// (Returns the number of records deleted)
database.delete(Person.self)
// Delete everything
// (Returns the number of records deleted)
database.clearDatabase()
如果需要,我们还可以使用事务。只要尚未提交,事务期间所做的所有更改都可以回滚。
database.startTransaction()
let person1Saved = database.write(Record(data: person1))
let person2Saved = database.write(Record(data: person2))
if person1Saved && person2Saved {
// We can commit the transaction to finalise the changes
database.commitTransaction()
} else {
// Or we can rollback to undo all changes made during the transaction
database.rollbackTransaction()
}
一切都是异步运行的 - 即使来自多个并发线程。
DispatchQueue.global().async {
let readPerson: Person? = database.read(id: "myID")
DispatchQueue.main.async {
// Update UI when done
}
}
类定义,以及如何使其符合 Storable
协议以进行序列化。
class Person: Storable {
private(set) var firstName: String
private(set) var lastName: String
init(firstName: String, lastName: String) {
self.firstName = firstName
self.lastName = lastName
}
// MARK: - Serialization
private enum Field: String {
case firstName
case lastName
}
required init(dataObject: DataObject) {
self.firstName = dataObject.get(Field.firstName.rawValue)
self.lastName = dataObject.get(Field.lastName.rawValue)
// NOTE: You can also override the default return value if the value can't be found
// self.lastName = dataObject.get(Field.lastName.rawValue, onFail: "MISSING")
}
func toDataObject() -> DataObject {
return DataObject(self)
.add(key: Field.firstName.rawValue, value: self.firstName)
.add(key: Field.lastName.rawValue, value: self.lastName)
}
}
class Student: Person {
private(set) var homework = [Homework]()
private(set) var debt: Double
private(set) var teacher: Teacher
private(set) var subjectNames: [String]
init(firstName: String, lastName: String, debt: Double, teacher: Teacher, subjectNames: [String]) {
self.debt = debt
self.teacher = teacher
self.subjectNames = subjectNames
super.init(firstName: firstName, lastName: lastName)
}
func giveHomework(_ homework: Homework) {
self.homework.append(homework)
}
// MARK: - Serialization
private enum Field: String {
case debt
case homework
case teacher
case subjectNames
}
required init(dataObject: DataObject) {
self.debt = dataObject.get(Field.debt.rawValue)
self.homework = dataObject.getObjectArray(Field.homework.rawValue, type: Homework.self)
self.teacher = dataObject.getObject(Field.teacher.rawValue, type: Teacher.self)
self.subjectNames = dataObject.get(Field.subjectNames.rawValue)
super.init(dataObject: dataObject)
}
override func toDataObject() -> DataObject {
return super.toDataObject()
.add(key: Field.debt.rawValue, value: self.debt)
.add(key: Field.homework.rawValue, value: self.homework)
.add(key: Field.teacher.rawValue, value: self.teacher)
.add(key: Field.subjectNames.rawValue, value: self.subjectNames)
}
}
class Teacher: Person {
private(set) var salary: Double
init(firstName: String, lastName: String, salary: Double) {
self.salary = salary
super.init(firstName: firstName, lastName: lastName)
}
// MARK: - Serialization
private enum Field: String {
case salary
}
required init(dataObject: DataObject) {
self.salary = dataObject.get(Field.salary.rawValue)
super.init(dataObject: dataObject)
}
override func toDataObject() -> DataObject {
return super.toDataObject()
.add(key: Field.salary.rawValue, value: self.salary)
}
}
class Homework: Storable {
public let answers: String
private(set) var grade: Int?
init(answers: String, grade: Int?) {
self.answers = answers
self.grade = grade
}
// MARK: - Serialization
private enum Field: String {
case answers
case grade
}
required init(dataObject: DataObject) {
self.answers = dataObject.get(Field.answers.rawValue)
self.grade = dataObject.get(Field.grade.rawValue)
}
func toDataObject() -> DataObject {
return DataObject(self)
.add(key: Field.answers.rawValue, value: self.answers)
.add(key: Field.grade.rawValue, value: self.grade)
}
}
序列化适用于 typealias
定义,但语法略有不同。
class Person // ...
protocol HasDebt // ...
typealias Student = Person & HasDebt
// ...
// In init(dataObject: DataObject)
self.students = dataObject.getObjectArray(Field.students.rawValue, type: Person.self) as! [any Student]
// In toDataObject() -> DataObject
.add(key: Field.students.rawValue, value: self.students as [Person])
您的类会随着时间而改变。
如果您从类中删除了属性,但之前已经保存了它,只需不要从 DataObject
中读取它。
如果您之前保存了对象,然后在它们的类定义中添加了新属性,您需要在类初始化器中定义类如何处理缺失的数据。
private var firstName: String
// ...
// By default, self.firstName will be set to "" if no value is returned
self.firstName = dataObject.get(Field.firstName.rawValue)
// self.firstName will be set to "MISSING" if no value is returned
self.firstName = dataObject.get(Field.firstName.rawValue, onFail: "MISSING")
如果属性是可选的并且没有返回值,它将被设置为 nil
。
private var firstName: String?
// ...
// self.firstName will be set to nil if no value is returned
self.firstName = dataObject.get(Field.firstName.rawValue)
您可能会更改类和属性的名称。您必须考虑这些重构。
这是您之前的类的样子
class Person: Storable {
private(set) var name: String
init(name: String) {
self.name = name
}
// MARK: - Serialization
private enum Field: String {
case name
}
required init(dataObject: DataObject) {
self.name = dataObject.get(Field.name.rawValue)
}
func toDataObject() -> DataObject {
return DataObject(self)
.add(key: Field.name.rawValue, value: self.name)
}
}
需要遵守的规则基本上是
Legacy.addClassRefactor
。Field
的 case,请在属性的 .get
方法调用中包含 legacyKeys
参数。如果您将 Person
重构为 Human
,并将 name
重构为 firstName
,则结果应如下所示
// On application startup
Legacy.addClassRefactor(old: "Person", new: "Human")
// ...
class Human: Storable {
private(set) var firstName: String
init(firstName: String) {
self.firstName = firstName
}
// MARK: - Serialization
private enum Field: String {
case firstName
}
required init(dataObject: DataObject) {
self.firstName = dataObject.get(Field.firstName.rawValue, legacyKeys: ["name"])
}
func toDataObject() -> DataObject {
return DataObject(self)
.add(key: Field.firstName.rawValue, value: self.firstName)
}
}
init()
/// True if a transaction is ongoing
var transactionActive: Bool
/// Write a record to the database. If the id already exists, replace it.
/// - Parameters:
/// - record: The record to be written
/// - Returns: If the write was successful
func write<T: Storable>(_ record: Record<T>) -> Bool
/// Retrieve all storable objects of a specified type.
/// - Returns: All saved objects of the specified type
func read<T: Storable>() -> [T]
/// Retrieve the storable object with the matching id.
/// - Parameters:
/// - id: The id of the stored record
/// - Returns: The storable object with the matching id
func read<T: Storable>(id: String) -> T?
/// Retrieve all the record IDs of all objects of a specific type.
/// - Parameters:
/// - allOf: The type to retrieve the ids from
/// - Returns: All stored record ids of the provided type
func readIDs<T: Storable>(_ allOf: T.Type) -> [String]
/// Delete all instances of an object
/// - Parameters:
/// - allOf: The type to delete
/// - Returns: The number of records deleted
func delete<T: Storable>(_ allOf: T.Type) -> Int
/// Delete the record with the matching id.
/// - Parameters:
/// - id: The id of the stored record to delete
/// - Returns: If any record was successfully deleted
func delete(id: String) -> Bool
/// Clear the entire database.
/// - Returns: The number of records deleted
func clearDatabase() -> Int
/// Count the number of records saved.
/// - Returns: The number of records
func count() -> Int
/// Count the number of records of a certain type saved.
/// - Parameters:
/// - allOf: The type to count
/// - Returns: The number of records of the provided type currently saved
func count<T: Storable>(_ allOf: T.Type) -> Int
/// Begin a database transaction.
/// Changes are still made immediately, however to finalise the transaction, `commitTransaction` should be executed.
/// All changes made during the transaction are cancelled if `rollbackTransaction` is executed.
/// If a new transaction is started before this one is committed, this transaction's changes are rolled back.
/// - Parameters:
/// - override: Override (roll back) the current transaction if one is currently active already - true by default
/// - Returns: True if the transaction was successfully started
func startTransaction(override: Bool) -> Bool
/// Commit the current transaction. All changes made during the transaction are finalised.
/// - Returns: True if there was an active transaction and it was committed
func commitTransaction() -> Bool
/// Rollback the current transaction. All changes made during the transaction are undone.
/// - Returns: True if there was an active transaction and it was rolled back
func rollbackTransaction() -> Bool
/// Constructor.
/// - Parameters:
/// - object: The Storable object this will represent
init(_ object: Storable)
/// Constructor.
/// - Parameters:
/// - rawString: The raw JSON string to populate this with, generated from another DataObject
init(rawString: String)
/// This DataObject's raw data
var rawData: Data
func add(key: String, value: String) -> Self
func add(key: String, value: String?) -> Self
func add(key: String, value: [String]) -> Self
func add(key: String, value: [String?]) -> Self
func add(key: String, value: Int) -> Self
func add(key: String, value: Int?) -> Self
func add(key: String, value: [Int]) -> Self
func add(key: String, value: [Int?]) -> Self
func add(key: String, value: Double) -> Self
func add(key: String, value: Double?) -> Self
func add(key: String, value: [Double]) -> Self
func add(key: String, value: [Double?]) -> Self
func add(key: String, value: Bool) -> Self
func add(key: String, value: Bool?) -> Self
func add(key: String, value: [Bool]) -> Self
func add(key: String, value: [Bool?]) -> Self
func add(key: String, value: Date) -> Self
func add(key: String, value: Date?) -> Self
func add(key: String, value: [Date]) -> Self
func add(key: String, value: [Date?]) -> Self
func add<T: Storable>(key: String, value: T) -> Self
func add<T: Storable>(key: String, value: T?) -> Self
func add<T: Storable>(key: String, value: [T]) -> Self
func add<T: Storable>(key: String, value: [T?]) -> Self
func get(_ key: String, onFail: String = "", legacyKeys: [String] = []) -> String
func get(_ key: String, legacyKeys: [String] = []) -> String?
func get(_ key: String, legacyKeys: [String] = []) -> [String]
func get(_ key: String, legacyKeys: [String] = []) -> [String?]
func get(_ key: String, onFail: Int = 0, legacyKeys: [String] = []) -> Int
func get(_ key: String, legacyKeys: [String] = []) -> Int?
func get(_ key: String, legacyKeys: [String] = []) -> [Int]
func get(_ key: String, legacyKeys: [String] = []) -> [Int?]
func get(_ key: String, onFail: Double = 0.0, legacyKeys: [String] = []) -> Double
func get(_ key: String, legacyKeys: [String] = []) -> Double?
func get(_ key: String, legacyKeys: [String] = []) -> [Double]
func get(_ key: String, legacyKeys: [String] = []) -> [Double?]
func get(_ key: String, onFail: Bool, legacyKeys: [String] = []) -> Bool
func get(_ key: String, legacyKeys: [String] = []) -> Bool?
func get(_ key: String, legacyKeys: [String] = []) -> [Bool]
func get(_ key: String, legacyKeys: [String] = []) -> [Bool?]
func get(_ key: String, onFail: Date = Date(), legacyKeys: [String] = []) -> Date
func get(_ key: String, legacyKeys: [String] = []) -> Date?
func get(_ key: String, legacyKeys: [String] = []) -> [Date]
func get(_ key: String, legacyKeys: [String] = []) -> [Date?]
func getObject<T>(_ key: String, type: T.Type, legacyKeys: [String] = []) -> T where T: Storable
func getObjectOptional<T>(_ key: String, type: T.Type, legacyKeys: [String] = []) -> T? where T: Storable
func getObjectArray<T>(_ key: String, type: T.Type, legacyKeys: [String] = []) -> [T] where T: Storable
func toRawString() -> String?