为什么 NSFetchRequest.shouldRefreshRefetchedObjects 不起作用?

Mic*_*sai 6 macos core-data macos-mojave

我正在尝试在一个上下文中更新和保存托管对象,然后在另一个上下文中访问更新的属性值。的文档shouldRefreshRefetchedObjects

默认情况下,当您获取对象时,它们会保持其当前属性值,即使持久存储中的值已更改。使用参数 YES 调用此方法意味着在执行提取时,将使用持久存储中的当前值更新所提取对象的属性值。与依次对多个对象使用 refreshObject:mergeChanges: (NSManagedObjetContext) 相比,这是确保托管对象属性值与存储一致的更方便的方法。

所以我认为通过将其设置为true我可以在重新获取后获取当前值,而无需手动刷新单个对象。然而,情况似乎并非如此。在 macOS 10.14.5 上,获取请求将根据存储中的属性值选择合适的对象,但内存中的对象仍然具有陈旧的值。

下面是一些示例代码来说明问题。我希望它打印Old New New,但它打印Old Old New

import Foundation
import CoreData

class Entity: NSManagedObject {
    @NSManaged var attribute: String
}

let attribute = NSAttributeDescription()
attribute.name = "attribute"
attribute.attributeType = .stringAttributeType
let entityDescription = NSEntityDescription()
entityDescription.name = "Entity"
entityDescription.properties = [attribute]
entityDescription.managedObjectClassName = Entity.className()
let model = NSManagedObjectModel()
model.entities = [entityDescription]

let coordinator = NSPersistentStoreCoordinator(managedObjectModel: model)
try! coordinator.addPersistentStore(ofType: NSInMemoryStoreType, configurationName: nil, at: nil, options: [:])

let writeContext = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
writeContext.persistentStoreCoordinator = coordinator
let readContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
readContext.persistentStoreCoordinator = coordinator

let writeEntity = Entity(entity: entityDescription, insertInto: writeContext)
writeContext.performAndWait {
    writeEntity.attribute = "Old"
    try! writeContext.save()
}

var readEntity: Entity? = nil
readContext.performAndWait {
    let request = NSFetchRequest<Entity>(entityName: entityDescription.name!)
    readEntity = try! readContext.fetch(request).first!
    // Initially the attribute should be Old, and that's what's printed
    print(readEntity!.attribute)
}

writeContext.performAndWait {
    writeEntity.attribute = "New"
    try! writeContext.save()
}

readContext.performAndWait {
    let request = NSFetchRequest<Entity>(entityName: entityDescription.name!)
    request.shouldRefreshRefetchedObjects = true
    _ = try! readContext.fetch(request)
    // Now the attribute should be New, but it is still Old
    print(readEntity!.attribute)

    readContext.refresh(readEntity!, mergeChanges: false)
    _ = try! readContext.fetch(request)
    // However, manually refreshing and fetching again does update it to New
    print(readEntity!.attribute)
}
Run Code Online (Sandbox Code Playgroud)

我知道refreshAllObjects(),但是:

  1. 可能会影响更多现在不需要更新的对象。
  2. 不提供对合并更改的控制。
  3. 发布变更通知。

shouldRefreshRefetchedObjects似乎正是我想要的;它似乎什么也没做。最好的解决方法似乎是单独刷新对象,但我猜这是低效的。

Tom*_*ton 5

简短的回答是:这是框架中的一个错误。问题中的代码应该可以工作,但不能,因为它shouldRefreshRefetchedObjects不像宣传的那样工作。

我还尝试了代码的一些其他变体。我将其更改为使用 SQLite 持久存储,以便我可以打开 SQLite 调试并查看它是否告诉我任何有趣的事情。获取意外结果的 fetch 会在 Xcode 控制台中打印这些消息:

CoreData: annotation:  with values: (
    "<JunkCDCmd.Entity: 0x100704760> (entity: Entity; id: 0x9e9e44e129d3565d 
    <x-coredata://3F8EC946-9EF7-4A62-AEF2-A8670185E23C/Entity/p1>; 
    data: {\n    attribute = Old;\n})"
)
Run Code Online (Sandbox Code Playgroud)

它正在变得Old,这不是这里所期望的。结果很可能来自托管对象上下文的缓存,这表明shouldRefreshRefetchedObjects框架没有检查该结果。

我还尝试了其他一些似乎不太有帮助的事情,但我不能不尝试就排除它们。我shouldRefreshRefetchedObjects在最初的获取中使用了,因为为什么不呢。我尝试避免readEntity第一次获取,而不是让它超出范围。我尝试将其嵌套writeContext.performAndWait在 内部readContext.performAndWait,以防范围相关。我将获取结果保存在变量中,而不是使用它_来确保结果不会意外地释放卡或其他东西。正如预期的那样,这些都没有任何区别。

赏金要求

彻底解释为什么实际上这不起作用,以及如何在它应该起作用的任何地方实施它。

如果没有框架的源代码,就不可能知道它不起作用的确切原因。如果没有的话,我们只能猜测。它明显坏了,而且看起来好像坏了,因为它没有检查shouldRefreshRefetchedObjects。如果它不适合你(而且看起来它不太可能适合你),那么请向 Apple 提交错误并希望得到最好的结果。

至于如何实现,如果这个设置正是您所需要的,那么没有确切的替代方案。刷新值的选项包括(排名不分先后)

  • refresh(_:mergeChanges:)通过使用withfalse作为第二个参数,强制对象在获取之前成为错误,如问题中所示。如果您有一堆受影响的对象,这可能会很尴尬。
  • refresh(_:mergeChanges:)使用withtrue作为第二个参数直接刷新对象。您无需重新获取。如果您有一堆受影响的对象,这可能会很尴尬。
  • 使用 刷新所有已注册的对象refreshAllObjects(),这具有问题中描述的缺点。
  • 合并通知中的更改NSManagedObjectContextDidSave(也称为Notification.Name.didSaveObjectsNotification)。