确定保存 NSManagedObjectContext 是否会导致对持久存储的更改

Determine if saving NSManagedObjectContext will result in changes to persistent store

换句话说,是否可以通过丢弃当前上下文来确定是否有更改会丢失?现在我的 UI 表示如果 context.hasChanges == TRUE 有数据丢失的风险,但我认为 hasChanges 看起来像这样:

- (BOOL) hasChanges {
    return self.updatedObjects > 0 || self.insertedObjects.count > 0 || self.deletedObjects.count > 0;
}

...和 ​​updatedObjects 包含仅 编辑过 的对象,即使属性未更改其原始值也是如此。 updatedObjects 还包含具有瞬态变化的对象。保存这些对象不会修改持久存储。

如果我的用户实际上没有更改任何内容,我不想提示他们进行保存,那么确定持久属性是否发生更改的最佳方法是什么?

也许您可以使用 committedValuesForKeys: 获取假定更改的键的旧值并与新值进行比较。像这样:

@interface NSManagedObject (tofustew)
- (BOOL)isActuallyUpdated;
@end

@implementation NSManagedObject (tofustew)

- (BOOL)isActuallyUpdated {
    NSArray *keys = self.changedValues.keyEnumerator.allObjects;
    NSDictionary *newValues = [self dictionaryWithValuesForKeys:keys];
    NSDictionary *oldValues = [self committedValuesForKeys:self.changedValues.keyEnumerator.allObjects];
    return [oldValues isEqualToDictionary:newValues];
}

@end

@interface NSManagedObjectContext (tofustew)
- (BOOL)hasActualChanges;
@end

@implementation NSManagedObjectContext (tofustew)

- (BOOL)hasActualChanges {
    if (self.insertedObjects.count > 0 || self.deletedObjects.count > 0)
        return YES;
    for (NSManagedObject *object in self.updatedObjects) {
        if (object.isActuallyUpdated) {
            return YES;
        }
    }
    return NO;
}

@end

这个 SO post 解决了一个类似的问题:

Identifying which fields have changed before CoreData saves

以及相关的 Apple 文档:https://developer.apple.com/library/mac/documentation/Cocoa/Reference/CoreDataFramework/Classes/NSManagedObject_Class/#//apple_ref/occ/instm/NSManagedObject/changedValues

This method only reports changes to properties that are defined as persistent properties of the receiver, not changes to transient properties or custom instance variables. This method does not unnecessarily fire relationship faults.

就像过滤 updatedObjects 一样简单:

NSPredicate *predicate = [NSPredicate predicateWithFormat:@"changedValues[SIZE] > 0"];
NSSet *objectsWithChangedValues = [context.updatedObjects filteredSetUsingPredicate:predicate];

几行代码产生了预期的结果,但我仍然不明白为什么 updatedObjects 会这样工作...

由于您只关心是否存在更改,而不是确切地找出发生了什么更改,因此我将使用 NSManagedObject 上的 hasPersistentChangedValues 方法来过滤上下文的字段。例如,

NSPredicate *hasChanges = [NSPredicate predicateWithFormat:@"hasPersistentChangedValues = YES"];
BOOL changesExist = ([[context.updatedObjects filteredSetUsingPredicate:hasChanges] count] > 0);

相同的逻辑适用于插入和删除的对象。

我最终在 Swift 中编写了以下方法:

/// Checks whether there are actually changes that will change the persistent store.
/// The `hasChanges` method would return `true` for transient changes as well which can lead to false positives.
var hasPersistentChanges: Bool {
    return !insertedObjects.isEmpty || !deletedObjects.isEmpty || updatedObjects.first(where: { [=10=].hasPersistentChangedValues }) != nil
}

结合以下 saveIfNeeded 方法,这将导致只需要保存:

/// Saves the context, only if it has any changes and if it has a place to save the changes to (parent or persistent store). Calling this method instead of the regular save() will increase performance. It is also useful to use this function when having a memory store, since this configuration doesn't have a persistent store but may use parent contexts to save their changes to.
/// - throws: A Core Data NSError when saving fails.
/// - returns: Whether the save was needed.
@discardableResult public func saveIfNeeded() throws -> Bool {
    let hasPurpose = parent != nil || persistentStoreCoordinator?.persistentStores.isEmpty == false
    guard hasPersistentChanges && hasPurpose else {
        // Saving won't do anything now, except for decreasing performance. Skip it for now.
        return false
    }

    try save()

    return true
}