【问题标题】:Why doesn’t NSFetchRequest.shouldRefreshRefetchedObjects work?为什么 NSFetchRequest.shouldRefreshRefetchedObjects 不起作用?
【发布时间】:2019-10-31 05:19:35
【问题描述】:

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

默认情况下,当您获取对象时,它们会保持当前状态 属性值,即使持久存储中的值具有 改变了。使用参数 YES 调用此方法意味着当 fetch 被执行,获取对象的属性值为 使用持久存储中的当前值进行更新。这是一个更 确保托管对象属性值的便捷​​方法 与商店一致,而不是使用 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 似乎正是我想要的;它似乎什么也没做。最好的解决方法似乎是单独刷新对象,但我猜这是低效的。

【问题讨论】:

  • 在此示例中,要么不保留对readEntity 的引用,要么在获取之前将其标记为“错误”。
  • @paiv 我知道丢弃readEntity(如上一个示例)将解决它不是最新的问题。但是这个问题是关于使用shouldRefreshRefetchedObjects,它承诺更新与获取请求匹配的现有对象,而无需提前确定哪些对象。
  • 如果保持强引用,对象不会被“重取”,标志shouldRefreshRefetchedObjects不适用,这是我的理解。
  • @paiv 是的,上面的代码显示没有刷新。您知道shouldRefreshRefetchedObjects 确实适用的任何情况吗?

标签: macos core-data macos-mojave


【解决方案1】:

简短的回答是:这是框架中的一个错误。问题中的代码应该可以工作,但不能工作,因为 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 中,以防范围相关。我将获取结果保存在变量中,而不是使用_ 以确保结果不会意外地被释放或其他东西。正如预期的那样,这些都没有任何区别。

赏金要求

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

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

至于如何实现它,如果这个设置正是你所需要的,那么没有确切的选择。刷新值的选项包括,无特定顺序

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-03-07
    • 2019-08-06
    • 2016-07-05
    • 2011-08-06
    • 2013-01-18
    • 2012-07-09
    相关资源
    最近更新 更多