持久化 0.1.2

持久化 0.1.2

Cristian Barril维护。



持久化 0.1.2

  • Cristian Barril

目录

Persistence

使用面向协议编程(Protocol Oriented Programming, POP)和带有关联类型的协议(Protocols with Associated Types, PATs)封装持久化逻辑的框架。这是一个示例框架,展示了如何使用

  • Cocoapods
  • Core Data
  • Realm
  • 面向协议编程
  • 带有关联类型的协议
  • 依赖注入
  • 单元测试

支持的数据库

  • Core Data
  • Realm

安装

使用Cocoapods,将其添加到您的Podfile中

所有数据库

pod 'Persistence'

仅Core Data数据库

pod 'Persistence/CoreData'

仅Realm数据库

pod 'Persistence/Realm'

如何使用

要使用此框架,您只需要了解一种一种协议。这使得从一种数据库更改为另一种数据库变得非常容易。

协议

DatabaseProtocol

这是任何数据库都必须实现的基协议。其理念是提供数据库应提供的最常见的功能。对于其他任何用途,您可以恢复数据库上下文并实现自定义代码。

public protocol DatabaseProtocol {
	...
    func create<ReturnType: DatabaseObjectTypeProtocol>() -> ReturnType?
    func recover<ReturnType: DatabaseObjectTypeProtocol>(key: String, value: String) -> [ReturnType]?
    func delete(_ object: DatabaseObjectType) -> Bool
    func getContext() -> DatabaseContextType
}

协议扩展

可保存的

CoreDataManager 向 DatabaseProtocol 添加了额外的功能方法

extension DatabaseProtocol where Self == CoreDataManager {
    public func save() throws {
    	...
    }
}

可更新的

RealmManager 向 DatabaseProtocol 添加了额外的功能方法

extension DatabaseProtocol where Self == RealmManager {
    public func update<T>(_ object: T) -> Bool where T : DatabaseObjectTypeProtocol {
    	...
    }
}

注意:这只是一个示例。您可以随意提取此存储库的版本,并创建自己的版本。

构建器

为了创建或恢复数据库,您必须使用针对所需数据库指定的 DatabaseBuilder 结构。此结构需要在每种情况下初始化所有必要信息(Core Data 需要的与 Realm 不同)

Core Data 构建器

使用此结构实例化以创建 Core Data 数据库。您可以拥有尽可能多的。只需确保使用不同的 "名称"。

您需要提供的信息包括

  • databaseName:表示数据库名称的 String 参数。
  • bundle:存放 NSManagedObjectModel 文件的 Bundle,在迁移情况下需要。
  • modelURL:指向 NSManagedObjectModel 文件路径的 URL。
public struct CoreDataBuilder: CoreDataBuilderProtocol {
    public typealias Database = CoreDataManager
    
    public let databaseName: String
    public let bundle: Bundle
    public let modelURL: URL
        
    public func create() throws -> CoreDataManager {
		...
    }
}

使用示例

let modelURL = Bundle.main.url(forResource: "MyModel", withExtension:"momd")
let databaseBuilder = CoreDataBuilder(databaseName: "CoreDataDatabaseName", bundle: Bundle.main, modelURL: modelURL)
let database: CoreDataManager = try? databaseBuilder.create()

领域构建器

实例化此结构体以创建 Realm 数据库。您可以有很多这样的实例。只需确保使用不同的 "名称"。

您需要提供的信息包括

  • databaseName:表示数据库名称的 String 参数。
  • passphrase: 用于加密数据库的字符串键。不能为空字符串。
  • schemaVersion: 当前版本号,为无符号整型。默认值为 0。(可选参数)
  • migrationBlock: 当模型变更时所需的迁移块。默认为 nil。(可选参数)
public struct RealmBuilder: RealmBuilderProtocol {
    public typealias Database = RealmManager
    
    public let databaseName: String
    public let passphrase: String
    public let schemaVersion: UInt64
    public let migrationBlock: MigrationBlock?
    
    public func create() throws -> RealmManager {
	    ...
    }
}

使用示例

let databaseBuilder = RealmBuilder(databaseName: "RealmDatabaseName", passphrase: "Passphrase")
let database: RealmManager = try? databaseBuilder.create()

注意:这只是一个示例。您可以随意提取此存储库的版本,并创建自己的版本。

使用

常见

创建

例如创建一个新的 User 实体

let newObject: User? = database.create()

恢复

例如恢复所有 User 实体

let recoveredObjects: [User]? = database.recover()

例如恢复一个特定的 User 实体。可以用于任何属性,因此返回一个数组。例如:返回所有名为 "John" 的 User

let recoveredObjects: [User]? = database.recover(key: "name", value: "John")

删除

例如删除一个特定的 User 实体。如果操作成功则返回 true,否则返回 false

let result = database.delete(objectToDelete)

核心数据

保存

自定义用于保存上下文的核心数据方法

try? database.save()

领域

更新

自定义用于更新特定 用户 实体的领域方法。如果操作成功则返回 true,否则返回 false

let result = database.update(newObject)

添加自定义代码

要使用数据库的其他功能,您可以恢复上下文以使用它

let context = database.getContext()

迁移

核心数据

  • 轻量迁移:只需像往常一样初始化数据库,框架会自动迁移。

  • 重量级迁移:需要在数据库的同一捆绑中创建一个 NSMappingModel 来进行迁移。如果需要,你还可以在 NSMappingModel 中设置一个类型为 NSEntityMigrationPolicy 的类,并为迁移过程添加自定义逻辑。

Realm

当模型发生变化时,您需要遵循以下步骤

  • 增加方案版本
  • 在 Builder 结构体中创建迁移块。以下是一些示例
migrationBlock: { migration, oldSchemaVersion in
                if (oldSchemaVersion < 1 && self.lastSchemaVersion >= 1) {
                    print("Automatic migration")
                    // Nothing to do!
                    // Realm will automatically detect new properties and removed properties
                    // And will update the schema on disk automatically
                }
                
                if (oldSchemaVersion < 2 && self.lastSchemaVersion >= 2) {
                    // The enumerateObjects(ofType:_:) method iterates
                    // over every Person object stored in the Realm file
                    migration.enumerateObjects(ofType: Person.className()) { oldObject, newObject in
                        // combine name fields into a single field
                        let firstName = oldObject!["firstName"] as! String
                        let lastName = oldObject!["lastName"] as! String
                        newObject!["fullName"] = "\(firstName) + \(lastName)"
                    }
                }
                
                if (oldSchemaVersion < 3 && self.lastSchemaVersion >= 3) {
                    // The renaming operation should be done outside of calls to `enumerateObjects(ofType: _:)`.
                    migration.renameProperty(onType: Person.className(), from: "age", to: "yearsSinceBirth")
                }
            }