CorePersistence
CorePersistence 是一个 pod,为 您处理所有 CoreData 的管理。
特性
- 简单集成
- 开箱即用的 CoreData 解决方案
- 在 sqlite 中线程安全的 CRUD 操作
- 无冲突操作
- 解析时的自动类型转换
- 易于使用的谓词
使用方法
CorePersistence 是一个易于使用的 CocoaPod,用作 CoreData 的包装器和简单的开箱即用的 JSON 解析器。所有写入操作都是在后台上下文中完成的,并且通过 OperationsQueue 进行同步。实体唯一性通过定义一个 keypath 来确保,这个 keypath 指向一个属性,该属性将用作唯一 id 约束。这些实体可以通过实现 Parsable
协议来自动解析,并且通过使用 <-
运算符自动执行所有转换、关系链接。
集成
在使用 CorePersistence
的功能之前,需要首先通过调用以下方法进行初始化:
func initializeStack(with modelName: String, handleMigration: (() -> Void)? = nil)
它需要你的 .xcdatamodeld
名称,以及一个闭包,在发生重型转换时触发。**目前,在重型迁移的情况下,当前 .sqlite
文件将被删除,并重新创建为新模型版本**。
持久化
当创建目标为持久化的模型时,你必须实现 Persistable
协议。以下是实现此协议的一个示例实体:
User+CoreDataClass.swift
@objc(User)
final public class User: ParsableManagedObject {
public static var idKeyPath: WritableKeyPath<User, String> {
return \Self.uuid
}
}
User+CoreDataProperties.swift
extension User {
@nonobjc public class func fetchRequest() -> NSFetchRequest<User> {
return NSFetchRequest<User>(entityName: "User")
}
@NSManaged public var uuid: String
@NSManaged public var firstName: String?
@NSManaged public var lastName: String?
}
CRUD
创建
static func create(in store: StoreManager,
updateIfEntityExists: Bool,
updateClosure: @escaping (_ entity: Self, _ context: NSManagedObjectContext) -> Void,
completeClosure: ((Self) -> Void)?)
在 NSPersistentStore
的后台上下文中创建一个 Persistable
实体,使用 store
参数。如果触发 updateClosure
,则在一个后台线路上传递一个新创建的对象,或者如果对象已存在,则传递要更新的对象。
参数
- store: 包含
NSPersistentStore
的StoreManager
实例。默认为在CorePersistence
中定义的一个。 - updateIfEntityExists: 当在数据库中找到重复项时,该方法将执行的动作由该标志定义。如果设置为
true
,则获取现有的一个并再调用一次updateClosure
来更新现有实体,如果设置为false
,则将省略更新。 - updateClosure: 当创建新实体或检索现有实体时触发。
- entity: 需要更新的新创建的实体。
- context: 创作用于此上下文。可用于创建关系实体。
- completeClosure: 在保存后台上下文后,异步在主线程上分发完整的块,同时从主上下文重新检索一个新鲜的对象。
示例
User.create(updateClosure: { (userToUpdate, context) in
userToUpdate.uuid = "b4c7900b-10f0-45ff-8692-0ff1e5ce2ac4"
userToUpdate.firstName = "Mark"
userToUpdate.lastName = "Twain"
userToUpdate.birthDate = Date()
userToUpdate.address = Address(entityID: "a8b6c763-eb10-4e82-b872-5504ee4c762c", context: context)
}, completeClosure: { persistedUser in
print(persistedUser)
})
init(entityID: EntityID, in store: StoreManager, context: NSManagedObjectContext)
独占用于初始化关系实体。应在调用 create
或 update
时使用,并且已有上下文可用。
参数
- entityID: 每个实体都必须有的唯一 ID,且只能有一个实体有这个特定的一个。
- store: 包含
NSPersistentStore
的StoreManager
实例。默认为在CorePersistence`
中定义的一个。 - context: 执行创建的上下文。
示例
User.create(updateClosure: { (userToUpdate, context) in
userToUpdate.uuid = "b4c7900b-10f0-45ff-8692-0ff1e5ce2ac4"
userToUpdate.address = Address(entityID: "a8b6c763-eb10-4e82-b872-5504ee4c762c", context: context)
}, completeClosure: { persistedUser in
print(persistedUser)
})
user.update(updateClosure: { (userToUpdate, context) in
userToUpdate.address = Address(entityID: "a8b6c763-eb10-4e82-b872-5504ee4c762c", context: context)
}, completeClosure: { updatedUser in
print(updatedUser)
}
static func createTemporary(in store: StoreManager,
updateClosure: @escaping (Self, NSManagedObjectContext) -> Void)
创建一个临时对象,该对象将在 updateClosure
闭包执行之后销毁。
参数
- 存储: 包含
NSPersistentStore
的StoreManager
实例。默认参数是 StoreManagers 的default
,其中包含默认的NSPersistentStore
。 - updateClosure: 新临时对象用于编辑的闭包。
- entity: 要更新的新创建的实体。
- context: 在上执行创建的上下文。可用于创建关系实体。
示例
User.createTemporary { (temporaryUser, context) in
temporaryUser.uuid = "b4c7900b-10f0-45ff-8692-0ff1e5ce2ac4"
temporaryUser.firstName = "Mark"
temporaryUser.lastName = "Twain"
}
读
static func get(entityID: EntityID,
from store: StoreManager,
sourceContext: NSManagedObjectContext) -> Self?
从使用 store
参数定义的 StoreManager
实例中检索单个实体,默认从 store
的主上下文中,但可以通过 sourceContext
参数传入不同的上下文。
参数
- entityID: 每个实体都必须有的唯一 ID,且只能有一个实体有这个特定的一个。
- store: 包含
NSPersistentStore
的StoreManager
实例。默认是在Persistence
中定义的。 - sourceContext: 在其中查找
entityID
的NSManagedObjectContext
实例。默认是mainContext
。 - 返回: 带有
entityID
的现有对象,如果找不到则返回 nil。
示例
let user = User.get(entityID: "8fda9bf4-4631-4290-ac4b-7ce62a3aacd6")
static func get(from store: StoreManager,
using predicate: NSPredicate,
comparisonClauses: [ComparisonClause],
sourceContext: NSManagedObjectContext) -> [Self]
从使用 store
参数定义的 StoreManager
实例中检索多个实体,默认从 store
的主上下文中,但可以通过 sourceContext
参数传入不同的上下文。
参数
- store: 包含
NSPersistentStore
的StoreManager
实例。默认为在CorePersistence
中定义的一个。 - predicate: 用于检索实体的查询谓词。
- comparisonClauses:
ComparisonClause
实例数组。 - sourceContext: 在其中查找
entityID
的NSManagedObjectContext
实例。默认是mainContext
。 - 返回:来自
sourceContext
的新对象,符合predicate
条件,并按comparisonClauses
排序
示例
let usersWithNameMark = User.get(using: \User.firstName == "Mark" && \User.birthDate <= Date(),
comparisonClauses: [.ascending(\User.birthDate)])
static func getAll(from store: StoreManager,
comparisonClauses: [ComparisonClause],
sourceContext: NSManagedObjectContext) -> [Self]
检索所有由 store
参数定义的 StoreManager
实例中的实体,默认从主上下文的 store
检索,但也可以通过 sourceContext
参数传递不同的上下文。
参数
- store: 包含
NSPersistentStore
的StoreManager
实例。默认为在CorePersistence
中定义的一个。 - comparisonClauses:
ComparisonClause
实例数组。 - sourceContext: 在其中查找
entityID
的NSManagedObjectContext
实例。默认是mainContext
。 - 返回:所有现有对象
示例
let allUsers = User.getAll(comparisonClauses: [.descending(\User.birthDate)])
更新
func update(in store: StoreManager,
updateClosure: @escaping (_ entity: Self, _ context: NSManagedObjectContext) -> Void,
completeClosure: ((Self) -> Void)?)
在更新闭包中更新对象。
参数
- store: 包含
NSPersistentStore
的StoreManager
实例。默认是在Persistence
中定义的。 - updateClosure:用于编辑对象的闭包
- entity: 要更新的新创建的实体。
- context: 创作用于此上下文。可用于创建关系实体。
- completeClosure:在主线程上保存对象的闭包
示例
user.update(updateClosure: { (user, context) in
user.firstName = "New Name"
let newOrder = Order(entityID: "b8b3183d-2ea9-477d-99c5-98f7e7707ef4", context: context)
user.orders.insert(newOrder)
}, completeClosure: { updatedUser in
print(updatedUser)
})
删除
func delete(from store: StoreManager,
sourceContext: NSManagedObjectContext,
completeClosure: (() -> Void)?)
从指定 context
参数的 NSManagedObjectContext 中删除实体。
参数
- store: 包含
NSPersistentStore
的StoreManager
实例。默认为在CorePersistence
中定义的一个。 - 上下文:源
NSManagedObjectContext
。默认为主上下文 - 完成闭包:在上下文保存后触发的事件
示例
user.delete {
print("Entity deleted")
}
static func delete(from store: StoreManager,
with options: DeleteOptions,
completeClosure: (() -> Void)?)
按照在 DeleteOptions
中定义的 predicate
条件条件删除实体结果集合。
参数
- store: 包含
NSPersistentStore
的StoreManager
实例。默认为在CorePersistence
中定义的一个。 - 选项:一个
DeleteOptions
实例,可以包含NSPredicate
、ComparisonClause
实例、offset
和执行上下文。 - 完成闭包:在上下文保存后触发的事件
示例
User.delete(with: DeleteOptions(predicate: \User.birthDate < Date())) {
print("Finished deleting")
}
解析
要启用模型解析,它们必须实现 Parsable
协议。自动符合 Persistable
协议,所以如果实体应该被持久化和解析,只需要实现 Parsable
协议。
有一个需要实现的方法
func mapValues(from map: MappingValues)
在从 parse(...)
的调用中调用的方法。在这个方法中,可以使用自定义运算符 <-
定义从 MappingValues
的解析。**警告:此方法不应手动调用或从主线程调用**。也不应手动设置 id,即 id = 3,因为 parse(...)
方法在调用此方法之前从 JSON 字典中获取 id,这可能会破坏 CoreData 中主键的唯一性。
参数
- map:包裹
[String: Any]
字典的包装器,执行字典的基本操作,且对NSManagedObjectContext
有感知。
示例
final class Entity: ParsableManagedObject {
public func mapValues(from map: MappingValues) {
title <- map["title"]
date <- (map["date"], { anyDate in
return Date.transform(anyDate, dateFormats: ["yyyy-MM-dd"])
})
}
运算符 <-
用于将 MappingValues
对象的值转换并设置到实体的属性中。它从 Any 自动转换为 Transformable
类型。
可转换类型
- Int, Int16, Int32
- Double
- Date
- Optional
- 前面类型的数组
- 前面类型的字典
- 符合
Transformable
协议的枚举
特性
- 自动将不兼容的基本类型(Int、Int16、Int32、Double、String)转换为合适的属性类型。
- 如果尝试将实体id解析为任何类型的关系,将创建适当的实体并将其设置为关系或添加到
Set
- 如果传递数组或字典进行解析到一个单个实体关系时,它将解析为单个实体;如果它是
一对一
或一对多
关系,将创建一个常规的Set
- 不会为字典中没有相应键设置的属性设置值。
在JSON中定义主键
在调用 mapValues
之前,主键会被自动解析。该键默认设置为通过 idKeyPath
计算得到的字符串值。这意味着在 JSON 中,主键的值将自动解析为与之同名的键所代表的值。如果这两个键不同,即 JSON 中的键为 id
而属性名为 uuid
,则必须实现一个计算属性 jsonKey
,以便解析方法知道哪个键包含主键。
示例
{
"user_id": 123456
}
final class Entity: ParsableManagedObject {
@NSManaged var id: Int
public static var idKeyPath: WritableKeyPath<User, Int> {
return \Self.id
}
// Here we need to implement `jsonKey` because key `user_id` and variable `id` which holds primary key differ
public static var jsonKey: String {
return "user_id"
}
}
final class Entity: ParsableManagedObject {
@NSManaged var user_id: Int
public static var idKeyPath: WritableKeyPath<User, Int> {
return \Self.user_id
}
// In this case it's not needed because they have the same name
//public static var jsonKey: String {
// return "user_id"
//}
}
方法
static func parse(json: JSONObject, in store: StoreManager, completeClosure: ((Self?) -> Void)?)
用于对 Parsable
类型进行解析的方法。给定一个 json
字典,实体使用 mapValues(:)
进行解析,然后在后台上下文中持久化到 NSPersistentStore
。如果存储中已存在相同对象,将对其进行更新,以保持 entityID
的唯一性。
参数
- json:要解析的数据的字典。[String: Any] 类型。
- store: 包含
NSPersistentStore
的StoreManager
实例。默认为在CorePersistence
中定义的一个。 - completeClosure:解析完成后,使用来自
store
的主上下文中的新实体触发completeClosure
。
示例
let json: [String: Any] = [
"uuid": "a8b6c763-eb10-4e82-b872-5504ee4c762c",
"date": "2000-04-11"
]
User.parse(json: json) { parsedEntity in
print(parsedEntity)
}
static func parse(jsonArray: [JSONObject], in store: StoreManager, completeClosure: (([Self]) -> Void)?)
用于对 Parsable
类型进行解析的方法。给定一个包含字典的 json
数组,实体使用 mapValues(:)
进行解析,然后在后台上下文中持久化到 NSPersistentStore
。如果存储中存在任何对象,则这些对象将进行更新,以保持 entityID
的唯一性。
参数
- jsonArray:应解析的数据包含的 [String: Any] 字典的数组。
- store: 包含
NSPersistentStore
的StoreManager
实例。默认为在CorePersistence
中定义的一个。 - completeClosure:解析完成后,使用一个由存储库主上下文中的实体组成的新数组触发
completeClosure
。
示例
let jsonArray: [[String: Any]] = [
["uuid": "a8b6c763-eb10-4e82-b872-5504ee4c762c",
"date": "2000-04-11"],
["uuid": "649e5991-c591-4055-8649-d1e6264ee768",
"date": "1996-02-08"]
]
User.parse(jsonArray: jsonArray) { parsedEntities in
print(parsedEntity)
}
断言
NSPredicate
使用中存在一个问题,那就是没有自动完成或编译时检查。在 CorePersistence 中,有一些工具操作符可以将键路径和条件创建到 NSPredicates 中。
let userID = a8b6c763-eb10-4e82-b872-5504ee4c762c
// Example 1:
NSPredicate(format: "userID == %@ AND numberOfOrders != 0", userID)
// is is the same as:
\User.uuid == userID && \User.numberOfOrders != 0
// Example 2:
NSPredicate(format: "numberOfOrders >= 0")
// is the same as
\User.numberOfOrders >= 0
let uuids = ["669cdb66-58ba-4579-a951-1b0acac7aae5", "b8b3183d-2ea9-477d-99c5-98f7e7707ef4"]
// Example 3:
NSPredicate(format: "uuid IN %@", uuids)
// is the same as
\User.uuid === uuids
比较从句
ComparisonClause
被代替 NSSortDescriptor
以使用键路径而非字符串。有两种静态变量可以使用。
public static func ascending<EntityType: PersistableManagedObject, PropertyType>(_ keyPath: KeyPath<EntityType, PropertyType>) -> ComparisonClause
public static func descending<EntityType: PersistableManagedObject, PropertyType>(_ keyPath: KeyPath<EntityType, PropertyType>) -> ComparisonClause
示例
results = Results<User>()
.filterBy(\User.address != nil)
.sortBy(.ascending(\User.birthDate), .descending(\User.address))
结果
结果对象是围绕 NSFetchedResultsController
的一种包装,封装了其功能,并改为使用闭包机制在上下文中传递更新,而不是多个委托方法以及大量模板代码。主要变化是刷新闭包会在所有更改都发生时触发,而不是逐个触发。
示例
results = Results<User>()
.filterBy(\User.address != nil)
.sortBy(.ascending(\User.birthDate), .descending(\User.address))
.registerForChanges { (changes) in
print("Changes: \(changes.map { ($0.type, $0.object.uuid) })")
}
如果结果定义在当前作用域内,并且在外部作用域内没有保留引用,则不会发生刷新,因为对象将被回收。
方法
public init(context: NSManagedObjectContext = StoreManager.default.mainContext)
初始化器,用于获取类型为 EntityType
的所有实体,并按 idKeyPath
排序。
参数
- context: 来源上下文
示例
results = Results<User>()
public func filterBy(_ predicate: NSPredicate) -> Self
过滤之前获取的结果。
参数
- predicate: 用于定义过滤规则的谓词
示例
results = Results<User>().filterBy(\User.birthDate <= Date())
public func sortBy(_ comparisons: ComparisonClause...) -> Self
对之前获取的结果进行排序。
参数
- 比较:可以在
ascending
或descending
中选择的一类ComparisonClause
实例
示例
results = Results<User>().sortBy(.ascending(\User.date()), .descending(\User.lastName))
public func registerForChanges(closure: @escaping (_ changes: [ResultsRefresher<EntityType>.Change]) -> Void) -> Self{
用于注册变更,这些变更在 Results 的初始化中指定的 NSManagedObjectContext
上发生。当发生变更时,将触发 closure
并传递累积的变更。变更包含一个变更 type
(插入、更新、移动、删除),在插入和移动类型的情况下可能不为 nil 的 newIndexPath
,当更新、移动和删除时将不为 nil 的 indexPath
,以及 EntityType
类型的对象。**注意**: 如果 Results 实例在其创建的范围内没有保存,则 ResultsRefresher
闭包将与 Results
一起解除分配,并且不会发生任何变更。
参数
- 闭包:当上下文发生变更时将被触发
- 变更:类型为
Change
的数组,定义在ResultsRefresher
中,包含变更type
(插入、更新、移动、删除),在插入和移动类型的情况下可能不为 nil 的newIndexPath
,在更新、移动和删除时将不为 nil 的indexPath
,以及EntityType
类型的对象。
示例
results = Results<User>()
.registerForChanges { (changes) in
print("Changes: \(changes.map { ($0.type, $0.object.uuid) })")
}
public func toArray() -> [EntityType]
将结果对象转换为类型为 EntityType
的数组
示例
let entityArray = Results<User>()
.filterBy(\User.address != nil)
.sortBy(.ascending(\User.birthDate), .descending(\User.address))
.registerForChanges { (changes) in
print("Changes: \(changes.map { ($0.type, $0.object.uuid) })")
}.toArray()
日志记录
存在几种实用日志记录方法,可以帮助记录不同严重性的事件。
🔵🔵🔵 Verbose log 🔵🔵🔵
public static func verbose(_ message: String)
🟠🟠🟠 Warning log 🟠🟠🟠
public static func warning(_ message: String)
🔴🔴🔴 Error log 🔴🔴🔴
public static func error(_ message: String)
示例
要运行示例项目,首先克隆仓库,然后从示例目录运行 pod install
命令。
要求
- iOS 10.0+
- Xcode 8.3+
- Swift 5+
安装
CorePersistence 通过 CocoaPods 提供。要安装它,只需将以下行添加到您的 Podfile 中。
pod 'CorePersistence'
作者
Milos Babic,[email#5728;prot;ected
许可
CorePersistence 在 MIT 许可下提供。有关更多信息,请参阅 LICENSE 文件。