У меня есть NSManagedObject, NewOrder, для которого я храню только NSManagedObjectID вне контекста. Когда мне нужно получить к нему доступ, я либо в главном контексте, либо в блоке executeAndWait () для persistentContainer.newBackgroundContext()
в BG-потоке, и я использую 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 и около 4 месяцев на CoreData использую Swift, и натолкнулся на неприятности, пытаясь научить его себе. Я уверен, что что-то здесь упущено.
Кто-нибудь знает, почему этот объект не будет обновляться во всех контекстах? Я что-то неправильно понимаю?
Изменения, внесенные с момента размещения вопроса
Согласно комментарию @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, который блокируется при каждом сохранении и разблокируется по окончании сохранения. Метод получения / установки для каждого соответствующего NSManagedObject должен также успешно .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.)
Кажется, очередь улучшилась, но не решила проблему.
Я думаю о рефакторинге приложения, чтобы найти объекты по некоторому уникальному идентификатору, а не по их идентификатору объекта, но я не уверен, что смогу решить проблемы, с которыми я столкнулся.
Любой совет будет оценен.
редактирование:
Я произвел рефакторинг приложения, изменил каждую сущность так, чтобы она имела свойство uuid: UUID, и вместо хранения NSManagedObjectID объектов, к которым я хочу получить доступ позже или в другом контексте, и использую context.existingObject (with: NSManagedObjectID), я сохраняю UUID и сделать запрос выборки на основе UUID. Когда я делаю это таким образом, данные ядра больше не могут выполнить ошибку для объекта, как только объект найден. Контекст представления думает, что должен быть объект в хранилище, но объект не находится в хранилище, когда он смотрит. Это относится к каждому вновь созданному объекту, который создается / сохраняется в частном контексте при доступе из viewContext.