gpt4 book ai didi

swift - persistentContainer.newBackgroundContext()不使用context.existingObject(with :)上的更新对象

转载 作者:行者123 更新时间:2023-11-30 10:42:16 25 4
gpt4 key购买 nike

我有一个NSManagedObject NewOrder,它只存储NSManagedObjectID以便于上下文。当我需要访问它时,我位于主上下文中,或者位于BG线程上persistentContainer.newBackgroundContext()的performAndWait()块中,并且我使用context.existingObject(with:NSManagedObjectID)提取与上下文相关的实例,并保存进行更改后。

由于某种原因,我在进行更改后会获取旧数据。我很确定我已使用try! context.save()保存了更改。

尽可能地,我尝试使用persistentContainer.newBackgroundContext()而不是主上下文,这样我就不会锁定主线程,这意味着很多上下文都可以触摸此NSManagedObjectID变量。奇怪的是,它可以预料地发生在两次newBackgroundContext调用之后,在一个新上下文和下一个上下文之间的完全相同的位置。几乎好像是在回收一个bg上下文,该上下文从未被通知已保存的更改。

看起来像这样:

    DispatchQueue.global(qos: .userInitiated).async
{
let context = persistentContainer.newBackgroundContext()
context.performAndWait
{
let newOrder = try! context.existingObject(with:self.newOrderOID) as! NewOrder
let client = try! context.existingObject(with: self.clientOID) as! Client
print(client.id)//10386
print(newOrder.userID!)// "10386"

//replacing old client object with new one, including data on the newOrder object
context.delete(client)
let newClient = Client(context: context)
self.clientOID = newClient.objectID
print(newClient.id) //10152
newOrder.userID = String(client.id)
try! context.save()
print(newOrder.userID!) //"10152"
}
}

//a bit later in the code, access again to do some reading, NO changes
let semaphore = DispatchSemaphore(value: 0)
DispatchQueue.global(qos: .userInitiated).async
{
let context = persistentContainer.newBackgroundContext()
context.performAndWait
{
let newOrder = try! context.existingObject(with:self.newOrderOID) as! NewOrder
let newClient = try! context.existingObject(with: self.clientOID) as! Client
print(newOrder.userID!)// "10152"
print(newClient.id) //10152
semaphore.signal()
}
}
semaphore.wait()
//IMMEDIATELY AFTER the previous context shows the correct userID
DispatchQueue.global(qos: .userInitiated).async
{
let context = persistentContainer.newBackgroundContext()
context.performAndWait
{
let newOrder = try! context.existingObject(with:self.newOrderOID) as! NewOrder
let newClient = try! context.existingObject(with: self.clientOID) as! Client
print(newOrder.userID!)// "10386" ????
print(newClient.id) //10152 !!
}
}


就阅读文档而言,我的印象是这些背景上下文应该直接植根于数据存储区,就像主要上下文一样,并且应该对数据的每次更改都进行更新商店。因此,如果我保存一个,它将显示所有更改。但是其中只有一部分反映了这些变化:有时对象不会跨所有上下文从存储中删除,并且有时对象中的数据很旧。尤其令人震惊和困惑的是,一个新上下文将具有正确的数据,而在其后创建的另一个新上下文将具有完全不同的数据。

上面的示例是对更复杂的内容的粗略简化,因此可能上面的操作是不可能的,并且在更复杂的代码中存在错误,但是我一直在与一个错误进行战斗近一周,除了找到确切的错误之外,没有任何进展发现它的发生,并发现更新数据和过时数据之间的唯一变化是新的背景环境。

我不了解Objective-C,并且在使用Swift的CoreData上学习了大约4个月,除了尝试自学外,还遇到了其他麻烦。我确定我在这里遗漏了一些东西。

有谁知道为什么这个对象不会更新到所有上下文?我误会了吗?

自问题发布以来进行的更改

根据@Jon Rose的评论,我重构了整个应用程序,以支持在主上下文中执行所有读取任务,并在私有上下文中以队列方式执行所有写入任务,这似乎消除了我所看到的问题。

这是我在CoreData管理器(称为persistenceManager)中添加的代码:

lazy var persistentContainerQueue = OperationQueue()

func enqueueAndSave(shouldWait:Bool = false, closure: @escaping (_ writeContext: NSManagedObjectContext) -> Void)
{
let semaphore = DispatchSemaphore(value: 0)
persistentContainerQueue.addOperation()
{
self.persistentContainer.performBackgroundTask()
{writeContext in
closure(writeContext)
self.save(writeContext)
semaphore.signal()
}
}
if shouldWait
{
semaphore.wait()
}
}


我如何使用它:

persistenceManager.enqueueAndSave(shouldWait: true)
{ writeContext in
let newOrder = try! writeContext.existingObject(with:self.newOrderOID) as! NewOrder
let client = try! writeContext.existingObject(with: self.clientOID) as! Client
print(client.id)//10386
print(newOrder.userID!)// "10386"

//replacing old client object with new one, including data on the newOrder object
writeContext.delete(client)
let newClient = Client(context: writeContext)
self.clientOID = newClient.objectID
print(newClient.id) //10152
newOrder.userID = String(client.id)
try! writeContext.save()
print(newOrder.userID!) //"10152"
}

//a bit later in the code, access again to do some reading, NO changes
persistenceManager.readContext.performAndWait
{
let newOrder = try! persistenceManager.readContext.existingObject(with:self.newOrderOID) as! NewOrder
let newClient = try! persistenceManager.readContext.existingObject(with: self.clientOID) as! Client
print(newOrder.userID!)// "10152"
print(newClient.id) //10152
}
//IMMEDIATELY AFTER the previous context shows the correct userID, now showing correct data :D

persistenceManager.enqueueAndSave(shouldWait: true)
{ writeContext in
let newOrder = try! writeContext.existingObject(with:self.newOrderOID) as! NewOrder
let newClient = try! writeContext.existingObject(with: self.clientOID) as! Client
print(newOrder.userID!)// "10152"
print(newClient.id) //10152
}


编辑:
我仍然遇到丢失数据的问题,这似乎可以通过将NSLock放入persistenceManager中来解决,该NSLock在每次保存时都被锁定,并在保存完成时被解锁。每个相关的NSManagedObject的getter / setter在返回/写入各自变量之前也必须成功.lock()。

编辑:
从应用程序其他区域的viewContext读取数据时,我仍然遇到旧数据。所有写操作都在排队,并且我已将viewContext变量转换为计算属性,该属性将等待队列为空再返回上下文。

基本上,我在私有上下文上写数据并保存,阻塞主线程直到写和保存完成,然后使用 viewContext.existingObject(with objectID: NSManagedObjectID)从我刚刚更改的同一对象中读取数据,并将该数据显示到UILabel。该数据是大约80%的时间是旧数据。实际上,我很难改变它。 pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int)函数是触发写入的函数。

是否使用 self.persistentContainer.performBackgroundTask()似乎无关紧要

要么

let writeContext = self.persistentContainer.newBackgroundContext()
writeContext.performAndWait(){}


我不明白为什么要在私有上下文中写入具有数据存储中特定位置的对象,然后保存,将数据存储中的位置传递到viewContext,以及使用该位置使用该数据存储位置来检索数据会导致除更新对象之外的任何其他对象。特别是如果更改已排队,并且数据读取等待所有更改完成后再继续。就像viewContext在保存时未通知写入上下文所做的更改一样。写上下文和viewContext都直接在数据存储区中获取,并且文档表明它们都应获取更改通知。在大多数应用程序中,这正是发生的情况。除了这些选择器。 (无论如何,到目前为止。谁知道类似的问题可能再次出现在哪里。我对CoreData的可靠性完全失去了信心。)

队列似乎有所改善,但并没有解决问题。

我正在考虑将应用程序重构为通过某个唯一的ID而不是通过其objectID来查找对象,但是我没有信心能够解决我所面临的问题。

任何意见,将不胜感激。

编辑:

我重构了应用程序,将每个实体都更改为具有uuid:UUID属性,而不是存储要稍后或在其他上下文中访问的对象的NSManagedObjectID并使用context.existingObject(with:NSManagedObjectID),而是存储UUID和根据UUID执行提取请求。当我这样做时,一旦找到对象,核心数据将不再能够满足该对象的故障。视图上下文认为商店中应该有一个对象,但是看起来时该对象不在商店中。当从viewContext访问时,这适用于在私有上下文中创建/保存的每个新创建的对象。

最佳答案

将项目回滚到使用context.existingObject(with:NSManagedObjectID),故障执行问题消失了。

使所有具有context.existingObject(with:NSManagedObjectID)计算属性的对象都被调用,并在每个context.refresh(staticObject!, mergeChanges: true)上调用get{},这解决了viewContext在选择器中包含旧数据的问题。

仍然存在一个问题,即与数据顺序无关的选择器之一,因为它来自NSSet中的一对多关系。每次需要将数据存储在内存中供选择器使用时,就需要进行排序。

好吧...我想说这个问题已经解决,但是我被吓到了,因为我以为有那么多次,但事实并非如此。暂时固定。

编辑:
惊喜惊喜,它再次发生。

编辑:
最终做了一个API以“全局”方式访问和变异核心数据变量。已删除context.refresh(staticObject!, mergeChanges: true)。事情似乎运行得更好,如果仍然遇到问题,我会更新。

编辑:
选择器的问题最终变得比我想象的要复杂。基本上,在按任何属性排序之前,我必须先使用哈希进行排序,以确保顺序得到保证。后来我发现哈希不能来自ObjectID或对象本身,因为这些哈希不能稳定。 (它们包括诸如来自哪个上下文的信息,因此不能保证它们在上下文中是相同的。)相反,我使用了从每个单独属性构建的哈希,这些哈希在上下文中是相同的。

编辑:
遇到了这样一个问题,即核心数据是有效的,并且不刷新关系对象已更改的对象,从而导致旧数据。刷新父对象可以解决此问题,并将其合并到我的API中。

编辑:
解决方案仍不完整。因为刷新是在每次访问之后发生的,所以如果在保存之前再次访问变量,赋值将为空,因此在每个集合上,我都保存了上下文。我真的不担心性能,只需要它成为可靠的数据存储。

关于swift - persistentContainer.newBackgroundContext()不使用context.existingObject(with :)上的更新对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56537139/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com