SwiftSerialization

一个用于序列化、持久化和恢复 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 类示例

类定义,以及如何使其符合 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)
    }
    
}

需要遵守的规则基本上是

如果您将 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)
    }
    
}

SerializationDatabase 文档

初始化器

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

DataObject 文档

初始化器

/// 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?