目录
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")
}
}