Я новичок в основных данных.У меня есть приложение, которое использует основные данные в качестве локального хранилища.Запись и чтение из основных данных осуществляется фоновыми потоками.Хотя в целом это работает, в редких случаях получаемые данные неверны, т. Е. Свойства извлекаемой сущности равны nil
.
. Чтобы проверить ситуацию, я написал модульный тест, который запускает 2 асинхронных потока: один непрерывно выбирает данные ядра.и другой непрерывно перезаписывает эти данные, сначала удаляя все данные, а затем сохраняя новые данные.
Этот тест довольно быстро провоцирует ошибку, но я понятия не имею, почему.Конечно, я предполагаю, что это проблема многопоточности, но я не понимаю, почему, потому что выборка и удаление + запись выполняются в отдельных управляемых контекстах одного persistentContainer
.
. Извините, код нижедовольно долго, хотя и укорочено, но я думаю, что без этого невозможно определить проблему.
Любая помощь очень приветствуется!
Вот моя функция для извлечения данных:
func fetchShoppingItems(completion: @escaping (Set<ShoppingItem>?, Error?) -> Void) {
persistentContainer.performBackgroundTask { (managedContext) in
let fetchRequest: NSFetchRequest<CDShoppingItem> = CDShoppingItem.fetchRequest()
do {
let cdShoppingItems: [CDShoppingItem] = try managedContext.fetch(fetchRequest)
for nextCdShoppingItem in cdShoppingItems {
nextCdShoppingItem.managedObjectContext!.performAndWait {
let nextname = nextCdShoppingItem.name! // Here, sometimes name is nil
} // performAndWait
} // for all cdShoppingItems
completion(nil, nil)
return
} catch let error as NSError {
// error handling
completion(nil, error)
return
} // fetch error
} // performBackgroundTask
} // fetchShoppingItems
Я прокомментировал строку, которая иногда завершает тест, поскольку name
равен nil
.
Вот мои функции для хранения данных:
func overwriteCD(shoppingItems: Set<ShoppingItem>,completion: @escaping () -> Void) {
persistentContainer.performBackgroundTask { (managedContext) in
self.deleteAllCDRecords(managedContext: managedContext, in: "CDShoppingItem")
let cdShoppingItemEntity = NSEntityDescription.entity(forEntityName: "CDShoppingItem",in: managedContext)!
for nextShoppingItem in shoppingItems {
let nextCdShoppingItem = CDShoppingItem(entity: cdShoppingItemEntity,insertInto: managedContext)
nextCdShoppingItem.name = nextShoppingItem.name
} // for all shopping items
self.saveManagedContext(managedContext: managedContext)
completion()
} // performBackgroundTask
} // overwriteCD
func deleteAllCDRecords(managedContext: NSManagedObjectContext, in entity: String) {
let deleteFetch = NSFetchRequest<NSFetchRequestResult>(entityName: entity)
let deleteRequest = NSBatchDeleteRequest(fetchRequest: deleteFetch)
deleteRequest.resultType = .resultTypeObjectIDs
do {
let result = try managedContext.execute(deleteRequest) as? NSBatchDeleteResult
let objectIDArray = result?.result as? [NSManagedObjectID]
let changes = [NSDeletedObjectsKey: objectIDArray]
NSManagedObjectContext.mergeChanges(fromRemoteContextSave: changes as [AnyHashable: Any], into: [managedContext])
} catch let error as NSError {
// error handling
}
} // deleteAllCDRecords
func saveManagedContext(managedContext: NSManagedObjectContext) {
if !managedContext.hasChanges { return }
do {
try managedContext.save()
} catch let error as NSError {
// error handling
}
} // saveManagedContext