CorePersistence 1.1.0

CorePersistence 1.1.0

SynchronicSolutions 维护。



  • SynchronicSolutions

CorePersistence

Version Platform

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: 包含 NSPersistentStoreStoreManager 实例。默认为在 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)

独占用于初始化关系实体。应在调用 createupdate 时使用,并且已有上下文可用。

参数
  • entityID: 每个实体都必须有的唯一 ID,且只能有一个实体有这个特定的一个。
  • store: 包含 NSPersistentStoreStoreManager 实例。默认为在 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 闭包执行之后销毁。

参数
  • 存储: 包含 NSPersistentStoreStoreManager 实例。默认参数是 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: 包含 NSPersistentStoreStoreManager 实例。默认是在 Persistence 中定义的。
  • sourceContext: 在其中查找 entityIDNSManagedObjectContext 实例。默认是 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: 包含 NSPersistentStoreStoreManager 实例。默认为在 CorePersistence 中定义的一个。
  • predicate: 用于检索实体的查询谓词。
  • comparisonClauses: ComparisonClause 实例数组。
  • sourceContext: 在其中查找 entityIDNSManagedObjectContext 实例。默认是 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: 包含 NSPersistentStoreStoreManager 实例。默认为在 CorePersistence 中定义的一个。
  • comparisonClauses: ComparisonClause 实例数组。
  • sourceContext: 在其中查找 entityIDNSManagedObjectContext 实例。默认是 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: 包含 NSPersistentStoreStoreManager 实例。默认是在 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: 包含 NSPersistentStoreStoreManager 实例。默认为在 CorePersistence 中定义的一个。
  • 上下文:源 NSManagedObjectContext。默认为主上下文
  • 完成闭包:在上下文保存后触发的事件
示例
user.delete {
    print("Entity deleted")
}

static func delete(from store: StoreManager,
                   with options: DeleteOptions,
                   completeClosure: (() -> Void)?)

按照在 DeleteOptions 中定义的 predicate 条件条件删除实体结果集合。

参数
  • store: 包含 NSPersistentStoreStoreManager 实例。默认为在 CorePersistence 中定义的一个。
  • 选项:一个 DeleteOptions 实例,可以包含 NSPredicateComparisonClause 实例、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: 包含 NSPersistentStoreStoreManager 实例。默认为在 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: 包含 NSPersistentStoreStoreManager 实例。默认为在 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

对之前获取的结果进行排序。

参数
  • 比较:可以在ascendingdescending中选择的一类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 文件。