为什么NSFetchRequest.shouldRefreshRefetchedObjects不起作用?

时间:2019-06-17 15:18:36

标签: macos core-data macos-mojave

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

  

默认情况下,当您获取对象时,它们会保持当前状态   属性值,即使持久性存储中的值具有   改变了。使用参数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)
}

我知道refreshAllObjects(),但是:

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

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

1 个答案:

答案 0 :(得分:2)

简短的回答是:这是框架中的一个错误。问题中的代码应该可以工作,但不能,因为 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})"
)

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

我还尝试了一些其他似乎不太可能有帮助但我不能不尝试就排除的方法。我在初始提取时使用了 shouldRefreshRefetchedObjects,因为为什么不呢。我尝试在第一次获取时保留 readEntity,而不是让它超出范围。我尝试将 writeContext.performAndWait 嵌套在 readContext.performAndWait 中,以防作用域相关。我将获取结果保存在变量中,而不是使用 _ 来确保结果不是意外的 deallocard 或其他东西。正如预期的那样,这些都没有任何区别。

赏金要求

<块引用>

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

如果没有框架的源代码,就不可能知道它不起作用的确切原因。没有这个我们只能推测。它显然坏了,看起来好像坏了,因为它没有检查 shouldRefreshRefetchedObjects。如果它不适合您(而且看起来它不可能适合您),那么请向 Apple 提交错误并希望一切顺利。

至于如何实现它,如果此设置正是您所需要的,则没有确切的替代方案。刷新值的选项包括(无特定顺序)

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