为什么 NSFetchRequest.shouldRefreshRefetchedObjects 不起作用?
Why doesn’t NSFetchRequest.shouldRefreshRefetchedObjects work?
我正在尝试在一个上下文中更新和保存托管对象,然后在另一个上下文中访问更新后的属性值。 shouldRefreshRefetchedObjects
says:
的文档
By default when you fetch objects, they maintain their current
property values, even if the values in the persistent store have
changed. Invoking this method with the parameter YES means that when
the fetch is executed, the property values of fetched objects are
updated with the current values in the persistent store. This is a more
convenient way to ensure that managed object property values are
consistent with the store than by using refreshObject:mergeChanges:
(NSManagedObjetContext) for multiple objects in turn.
所以我认为通过将其设置为 true
我可以在重新获取后获得当前值,而无需手动刷新各个对象。然而,情况似乎并非如此。在 macOS 10.14.5 上,获取请求将根据存储中的 属性 值 select 正确的对象,但内存中的对象仍然具有陈旧的值。
下面是一些示例代码来说明问题。我希望它打印 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()
,但是:
- 可能会影响更多现在不需要更新的对象。
- 不提供对合并更改的控制。
- 发布更改通知。
shouldRefreshRefetchedObjects
似乎正是我想要的;它似乎什么也没做。最好的解决方法似乎是单独刷新对象,但我猜这是低效的。
简短的回答是:这是框架中的错误。问题中的代码应该有效,但没有,因为 shouldRefreshRefetchedObjects
不像宣传的那样有效。
我还尝试了一些其他代码变体。我将其更改为使用 SQLite 持久存储,以便我可以打开 SQLite 调试并查看它是否告诉我任何有趣的事情。获取意外结果的提取在 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 或其他东西。正如预期的那样,其中 none 产生了任何影响。
赏金要求
A thorough explanation of why in fact this does not work and how to implement it in whatever place it is that it should work.
如果没有框架的源代码,确切 它不起作用的原因是不可能知道的。没有它,我们只能推测。它显然坏了,看起来好像坏了,因为它没有检查 shouldRefreshRefetchedObjects
。如果它不适合你(看起来它不可能适合你)然后向 Apple 提交错误并希望最好。
至于如何实现它,如果这个设置正是你所需要的,那么没有确切的选择。刷新值的选项包括(排名不分先后)
- 通过使用
refresh(_:mergeChanges:)
和 false
作为第二个参数,强制对象在获取之前成为错误,如问题中所示。如果您有一堆受影响的对象,这可能会很尴尬。
- 使用
refresh(_:mergeChanges:)
和 true
作为第二个参数直接刷新对象。您无需重新获取。如果您有一堆受影响的对象,这可能会很尴尬。
- 使用
refreshAllObjects()
刷新所有已注册的对象,这具有问题中描述的缺点。
- 合并来自
NSManagedObjectContextDidSave
通知的更改(a.k.a。Notification.Name.didSaveObjectsNotification
)。
我正在尝试在一个上下文中更新和保存托管对象,然后在另一个上下文中访问更新后的属性值。 shouldRefreshRefetchedObjects
says:
By default when you fetch objects, they maintain their current property values, even if the values in the persistent store have changed. Invoking this method with the parameter YES means that when the fetch is executed, the property values of fetched objects are updated with the current values in the persistent store. This is a more convenient way to ensure that managed object property values are consistent with the store than by using refreshObject:mergeChanges: (NSManagedObjetContext) for multiple objects in turn.
所以我认为通过将其设置为 true
我可以在重新获取后获得当前值,而无需手动刷新各个对象。然而,情况似乎并非如此。在 macOS 10.14.5 上,获取请求将根据存储中的 属性 值 select 正确的对象,但内存中的对象仍然具有陈旧的值。
下面是一些示例代码来说明问题。我希望它打印 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()
,但是:
- 可能会影响更多现在不需要更新的对象。
- 不提供对合并更改的控制。
- 发布更改通知。
shouldRefreshRefetchedObjects
似乎正是我想要的;它似乎什么也没做。最好的解决方法似乎是单独刷新对象,但我猜这是低效的。
简短的回答是:这是框架中的错误。问题中的代码应该有效,但没有,因为 shouldRefreshRefetchedObjects
不像宣传的那样有效。
我还尝试了一些其他代码变体。我将其更改为使用 SQLite 持久存储,以便我可以打开 SQLite 调试并查看它是否告诉我任何有趣的事情。获取意外结果的提取在 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 或其他东西。正如预期的那样,其中 none 产生了任何影响。
赏金要求
A thorough explanation of why in fact this does not work and how to implement it in whatever place it is that it should work.
如果没有框架的源代码,确切 它不起作用的原因是不可能知道的。没有它,我们只能推测。它显然坏了,看起来好像坏了,因为它没有检查 shouldRefreshRefetchedObjects
。如果它不适合你(看起来它不可能适合你)然后向 Apple 提交错误并希望最好。
至于如何实现它,如果这个设置正是你所需要的,那么没有确切的选择。刷新值的选项包括(排名不分先后)
- 通过使用
refresh(_:mergeChanges:)
和false
作为第二个参数,强制对象在获取之前成为错误,如问题中所示。如果您有一堆受影响的对象,这可能会很尴尬。 - 使用
refresh(_:mergeChanges:)
和true
作为第二个参数直接刷新对象。您无需重新获取。如果您有一堆受影响的对象,这可能会很尴尬。 - 使用
refreshAllObjects()
刷新所有已注册的对象,这具有问题中描述的缺点。 - 合并来自
NSManagedObjectContextDidSave
通知的更改(a.k.a。Notification.Name.didSaveObjectsNotification
)。